Creating a Django-Oscar App with Dashboard (Part 2)
In this tutorial, you are going to learn how to create a dashboard for Oscar’s e-commerce framework. This tutorial starts where the first part ended.
Creating a Django-Oscar dashboard app to manage boutiques
Let’s create a new app called dashboard inside the boutique app directory:
mkdir boutique/dashboard
Then initialize a new Django app using the following command:
python manage.py startapp dashboard boutique/dashboard
You can delete
admin.py
,models.py
andtests.py
, because these are not required for the Oscar's dashboard app.
Once again after the dashboard app is created, it is necessary to register the app in INSTALLED_APPS
in settings.py
as shown below:
INSTALLED_APPS = [
...
'boutique.dashboard.apps.DashboardConfig',
]
If you run server at this moment it will not work as you need to first complete the app configurations.
In the first part, we had a commented-out line in our myoscarproject/urls.py
. Now that the dashboard app is created we need to uncomment it as shown below:
from django.apps import apps
from django.urls import include, path
from django.contrib import adminurlpatterns = [
...
path('dashboard/boutique/', apps.get_app_config('boutique_dashboard').urls),
...
]
However, at this point label, boutique_dashboard
is not associated with any configuration. Therefore, let's move on and create the Boutique Dashboard Oscar app config.
App configs for the Boutique’s dashboard
Configuration for boutique dashboard app is similar to configs from the first part of this tutorial. With few additions as shown below:
from django.urls import path
from oscar.core.application import OscarDashboardConfig
from oscar.core.loading import get_classclass DashboardConfig(OscarDashboardConfig):
name = 'boutique.dashboard'
label = 'boutique_dashboard'
namespace = 'boutique-dashboard' default_permissions = ['is_staff'] def ready(self):
self.boutique_list_view = get_class(
'boutique.dashboard.views', 'DashboardBoutiqueListView')
self.boutique_create_view = get_class(
'boutique.dashboard.views', 'DashboardBoutiqueCreateView')
self.boutique_update_view = get_class(
'boutique.dashboard.views', 'DashboardBoutiqueUpdateView')
self.boutique_delete_view = get_class(
'boutique.dashboard.views', 'DashboardBoutiqueDeleteView') def get_urls(self):
urls = [
path('', self.boutique_list_view.as_view(), name='boutique-list'),
path('create/', self.boutique_create_view.as_view(),
name='boutique-create'),
path('update/<int:pk>/', self.boutique_update_view.as_view(),
name='boutique-update'),
path('delete/<int:pk>/', self.boutique_delete_view.as_view(),
name='boutique-delete'),
]
return self.post_process_urls(urls)
One important point in this configuration is to change the label
parameter. The Django Oscar's default dashboard app conflicts withDashboardConfig
the Boutique dashboard app. Django's documentation state that:
“AppConfig.label defaults to the last component of
name
."
Therefore, it is necessary to choose a different label boutique_dashboard
in order to "tell" Django that this dashboard app is different from Oscar's built-in dashboard app.
Another difference between dashboard app config from primary boutique app config is the default_permissions
parameter. This parameter sets Oscar's dashboard permissions for this dashboard app. Since the Oscar has multiple user permission levels like one that has Fulfilment Parters, setting this parameter is_staff
disables access to this dashboard for any user except c users like super-users.
Forms for the Boutique’s dashboard app
First, it is necessary to create forms for your custom dashboard app. Create a forms.py
file in boutique/dashboard
directory and add the following code:
from django import forms
from django.db.models import Q
from django.utils.translation import gettext_lazy as _
from oscar.core.loading import get_modelBoutique = get_model('boutique', 'Boutique')class DashboardBoutiqueSearchForm(forms.Form):
name = forms.CharField(label=_('Boutique name'), required=False)
city = forms.CharField(label=_('City'), required=False) def is_empty(self):
d = getattr(self, 'cleaned_data', {})
def empty(key): return not d.get(key, None)
return empty('name') and empty('city') def apply_city_filter(self, qs, value):
words = value.replace(',', ' ').split()
q = [Q(city__icontains=word) for word in words]
return qs.filter(*q) def apply_name_filter(self, qs, value):
return qs.filter(name__icontains=value) def apply_filters(self, qs):
for key, value in self.cleaned_data.items():
if value:
qs = getattr(self, 'apply_%s_filter' % key)(qs, value)
return qs
class DashboardBoutiqueCreateUpdateForm(forms.ModelForm):
class Meta:
model = Boutique
fields = ('name', 'manager', 'city')
In the code above DashboardBoutiqueSearchForm
is a form to filter Boutique instances in the dashboard. We design our form so that it can filter by model's city
and name
fields. The form DashboardBoutiqueCreateUpdateForm
is the create and update form required to create or edit a boutique instance. This form inherits Django's default forms.ModelForm
so it is relatively simple to make it work.
Views for the Boutique’s dashboard app
There are four different views required to deploy a custom Oscar dashboard. These are:
- View to list boutique instances
DashboardBoutiqueListView
- View to create a new boutique instance
DashboardBoutiqueCreateView
- View to update/edit a boutique instance
DashboardBoutiqueUpdateView
- View to delete a boutique instance
DashboardBoutiqueDeleteView
Prior to moving on to the views add the following code to the head of a views.py
the file of the boutique's dashboard app:
from django.contrib import messages
from django.template.loader import render_to_string
from django.urls import reverse_lazy
from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _
from django.views import generic
from oscar.core.loading import get_class, get_modelBoutique = get_model('boutique', 'Boutique')
BoutiqueCreateUpdateForm = get_class(
'boutique.dashboard.forms', 'DashboardBoutiqueCreateUpdateForm')
DashboardBoutiqueSearchForm = get_class(
'boutique.dashboard.forms', 'DashboardBoutiqueSearchForm')
Listing Boutique instances in the dashboard
Listing boutique instances in a custom dashboard app is no different than any other Django app. The list view inherits Django’s generic.ListView
as shown in the following code:
class DashboardBoutiqueListView(generic.ListView):
model = Boutique
template_name = "dashboard/boutique/boutique_list.html"
context_object_name = "boutique_list"
paginate_by = 20
filterform_class = DashboardBoutiqueSearchForm def get_title(self):
data = getattr(self.filterform, 'cleaned_data', {}) name = data.get('name', None)
city = data.get('city', None) if name and not city:
return gettext('Boutiques matching "%s"') % (name)
elif name and city:
return gettext('Boutiques matching "%s" near "%s"') % (name, city)
elif city:
return gettext('Boutiques near "%s"') % (city)
else:
return gettext('Boutiques') def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
data['filterform'] = self.filterform
data['queryset_description'] = self.get_title()
return data def get_queryset(self):
qs = self.model.objects.all()
self.filterform = self.filterform_class(self.request.GET)
if self.filterform.is_valid():
qs = self.filterform.apply_filters(qs)
return qs
The only non-trivial part of the code above is the additional parameter, filterform_class
, which is essentially a parameter that is recognized and processed by Oscar's templates.
Creating Boutique instances in the dashboard
Similarly, the view responsible for creating the boutique instances inherits generic.CreateView
and is shown in the following code:
class DashboardBoutiqueCreateView(generic.CreateView):
model = Boutique
template_name = 'dashboard/boutique/boutique_update.html'
form_class = BoutiqueCreateUpdateForm
success_url = reverse_lazy('boutique-dashboard:boutique-list') def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['title'] = _('Create new boutique')
return ctx def forms_invalid(self, form, inlines):
messages.error(
self.request,
"Your submitted data was not valid - please correct the below errors")
return super().forms_invalid(form, inlines) def forms_valid(self, form, inlines):
response = super().forms_valid(form, inlines) msg = render_to_string('dashboard/boutique/messages/boutique_saved.html',
{'boutique': self.object})
messages.success(self.request, msg, extra_tags='safe')
return response
In the code above, the parameter success_url
is assigned to reverse_lazy
and not reverse
because the URL will be evaluated lazily(or when required). Moreover, Oscar uses Django's built-in messages framework to pass success and fail messages to the templates. The messages are handled in corresponding methods forms_invalid
and forms_valid
.
Updating Boutique instances in the dashboard
View for updating Boutique instance is very similar to create a view and uses the same template but inherits generic.UpdateView
instead.
class DashboardBoutiqueUpdateView(generic.UpdateView):
model = Boutique
template_name = "dashboard/boutique/boutique_update.html"
form_class = BoutiqueCreateUpdateForm
success_url = reverse_lazy('boutique-dashboard:boutique-list') def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['title'] = self.object.name
return ctx def forms_invalid(self, form, inlines):
messages.error(
self.request,
"Your submitted data was not valid - please correct the below errors")
return super().forms_invalid(form, inlines) def forms_valid(self, form, inlines):
msg = render_to_string('dashboard/boutique/messages/boutique_saved.html',
{'boutique': self.object})
messages.success(self.request, msg, extrforms_valida_tags='safe')
return super().forms_valid(form, inlines)
Deleting Boutique instances from the dashboard
Delete view is rather simple compared to others and it inherits Django’s generic.DeleteView
as shown below:
class DashboardBoutiqueDeleteView(generic.DeleteView):
model = Boutique
template_name = "dashboard/boutique/boutique_delete.html"
success_url = reverse_lazy('boutique-dashboard:boutique-list')
Finally, now that views are completed we can move on to templates.
Templates for the Boutique’s dashboard app
For templates let’s first create a directory /src/templates/dashboard
. In this directory, we must implement three-view templates and one message template:
- Template for list view:
/dashboard/boutique/boutique_list.html
- Template for update view:
/dashboard/boutique/boutique_update.html
- Template for delete view:
/dashboard/boutique/boutique_delete.html
- Message template:
/dashboard/boutique/messages/boutique_saved.html
Templates are implemented the same way as was described in the first part of this tutorial except that these templates must extend different base layout,{% extends 'oscar/dashboard/layout.html' %}
. Since templates are long you can find them in the Git repository of this tutorial. After templates are ready the following screen will be available when you go to http://127.0.0.1:8000/dashboard/boutique/
URL:
Adding “Boutiques” navigation item to Django-Oscar’s dashboard
Finally, after Boutiques are ready we need to add a navigation item to Oscar’s dashboard navigation. Luckily, Django-Oscar provides a very easy way to do this. You need to add the following code to the settings but make sure that it comes after importing Oscar’s defaults:
from django.utils.translation import gettext_lazy as _... # Django's Other Settingsfrom oscar.defaults import *OSCAR_DASHBOARD_NAVIGATION.append({
'label': _('Boutiques'),
'icon': 'fas fa-store',
'url_name': 'boutique-dashboard:boutique-list',
})
Once the navigation item is added you will get the following screen when entering your Oscar Dashboard:
Conclusion
At the end of this tutorial, you should be able to create a brand new Django Oscar app with a working dashboard and everything. I hope this tutorial was helpful for the reader and made one’s life easier while learning such an amazing e-commence framework like Django-Oscar.
Source code of this tutorial can be found in my Git repository here
Originally published at https://mmtechslv.com on March 17, 2021.