In this tutorial, you are going to learn how to create a new Django app and integrate it into the e-commerce framework. Particularly, we will create a new sample Django app called and integrate it to Oscar's default front and dashboard. Oscar boutique Getting ready (Django-Oscar) First, it is necessary to create a virtual environment to work in. I use as a virtual environment for its simplicity and ease of use. Create a directory called , move inside and run the following command: pipenv /myoscarapp $ pipenv shell Then install the using pip: django-oscar $ pip install django-oscar[sorl-thumbnail] Now create a brand new Dango project using the following command and rename the created directory to for convenience: src $ django-admin startproject myoscarproject $ mv myoscarproject src Next, configure Django and as described in Oscar's . Run and migrate: settings.py urls.py corresponding docs makemigrations $ python manage.py makemigrations $ python manage.py migrate Test the website: $ python manage.py runserver The following screen should be now available: Creating “boutique” app for Django-Oscar The new app is created as usual using the following command: $ python manage.py startapp boutique Once again as usual after the app is created, it is necessary to register the app in in as shown below: INSTALLED_APPS settings.py INSTALLED_APPS = [ ... , ] 'boutique.apps.BoutiqueConfig' Similarly, your should look like this: urls.py django.apps apps django.urls include, path django.contrib admin urlpatterns = [ path( , include( )), path( , admin.site.urls), path( , apps.get_app_config( ).urls), path( , include(apps.get_app_config( ).urls[ ])), ] from import from import from import 'i18n/' 'django.conf.urls.i18n' 'admin/' #path('dashboard/boutique/', apps.get_app_config('boutique_dashboard').urls), 'boutique/' 'boutique' '' 'oscar' 0 In the code above, line with URL configuration is temporarily commented out and will be turned on when Oscar's dashboard app is forked. boutique_dashboard Models for “boutique” app Create the following model that will represent a single with three fields. boutique django.db models name = models.CharField(max_length= , blank= , null= ) manager = models.CharField(max_length= , blank= , null= ) city = models.CharField(max_length= , blank= , null= ) app_label = from import : class Boutique (models.Model) 255 True True 150 True True 150 True True : class Meta 'boutique' App configs for “boutique” app While the usual Django app’s config Class in inherits Django's default class, Oscar app's must inherit instead. Your should look like this: apps.py django.apps.AppConfig oscar.core.application.OscarConfig apps.py oscar.core.application OscarConfig django.urls path, re_path oscar.core.loading get_class name = namespace = super().ready() self.boutique_list_view = get_class( , ) self.boutique_detail_view = get_class( , ) urls = super().get_urls() urls += [ path( , self.boutique_list_view.as_view(), name= ), re_path( , self.boutique_detail_view.as_view(), name= ), ] self.post_process_urls(urls) from import from import from import : class BoutiqueConfig (OscarConfig) 'boutique' 'boutique' : def ready (self) 'boutique.views' 'BoutiqueListView' 'boutique.views' 'BoutiqueDetailView' : def get_urls (self) '' 'index' r'^view/(?P<pk>\d+)/$' 'details' return It is optional to use and when developing your own app but required when overriding Oscar apps. However, I prefer using Oscar's approach in all cases as I previously encountered various errors when importing modules using statement. get_class get_model import Admin for “boutique” app This step is optional and Oscar’s dashboard is sufficient to add, modify and remove elements to the database. However, for early testing let's register our model in Django's admin. Add the following code to the in the app's directory. Boutique admin.py django.contrib admin oscar.core.loading get_model Boutique = get_model( , ) admin.site.register(Boutique, BoutiqueAdmin) from import from import 'boutique' 'Boutique' : class BoutiqueAdmin (admin.ModelAdmin) pass Now that the model is registered in Django’s admin, go on and add few items for testing. To access Django’s admin you will need to create a super user using command python manage.py createsuperuser Views for “boutique” app There is nothing special in the implementation of views that will deliver context to the front pages. Following is a working based on Django's generic class-based views. views.py django.views generic oscar.core.loading get_model Boutique = get_model( , ) model = Boutique template_name = context_object_name = model = Boutique template_name = context_object_name = from import from import 'boutique' 'Boutique' : class BoutiqueListView (generic.ListView) 'boutique/boutique_list.html' 'boutique_list' : class BoutiqueDetailView (generic.DetailView) 'boutique/boutique_details.html' 'boutique' Front-end templates for “boutique” views First and foremost, let’s override Oscar’s navigation template by adding a URL to our . First, create a directory called in directory. Any template file with the same relative path Oscar's templates from source code will be overridden by Oscar and become a higher priority template. Because Oscar is developed in a very smart and customizable way, it is very easy to add an element to the original Oscar template navigation. The original template HTML file from Oscar's source code can be found in . Accordingly, we need to create a file that will contain the following code: BoutiqueListView oscar /src/templates /templates/oscar/partials/nav_primary.html oscar/partials/nav_primary.html {% "oscar/partials/nav_primary.html" %} extends {% i18n %} load {% nav_items %} block {{ block.super }} < = > li class "nav-item dropdown" < = = = > a class "nav-link" href "#" role "button" {% "Boutiques" %} trans </ > a </ > li {% %} endblock In the code above, we first extend the original Oscar’s template. Then we override the block by adding new elements to Oscar's default front-end navigation. After restarting the server, the following front should show up: nav_items Template for list of boutiques Previously we created a view , which is responsible for delivering the context with a list of instances to the template . Therefore, we first create an HTML file . Notice that this template file is not placed under the directory. This is because we do not override Oscar's template and merely creating a new custom template. However, in our case, it does extend the default Oscar layout template as shown: BoutiqueListView Boutique boutique/boutique_list.html /src/templates/boutique/boutique_list.html /src/templates/oscar {% "oscar/layout.html" %} extends {% i18n %} load {% product_tags %} load {% title %} block {% "Boutiques" %} trans | {{ block.super }} {% %} endblock {% breadcrumbs %} block < = > nav aria-label "breadcrumb" < = > ol class "breadcrumb" < = > li class "breadcrumb-item" < = a href " {{ homepage_url }} "> {% "Home" %} trans </ > a </ > li < = = > li class "breadcrumb-item active" aria-current "page" {% "Boutiques" %} trans </ > li </ > ol </ > nav {% %} endblock {% headertext %} block {% "Boutique" %} trans {% %} endblock {% content %} block {% not boutique_list %} if < > p {% "There are no boutique at the moment." %} trans </ > p {% %} else {% boutique boutique_list %} for in < > p < > h2 < = a href " {% 'boutique:details' boutique.pk %} url "> {{ boutique.name }} The boutique is in: </ > a </ > h2 {{ boutique.city }} </ > p < /> hr {% %} endfor {% %} endif {% content %} endblock The result should look like this: Template for boutique details Now that we have a page with a list of our boutique elements let’s add a page where users can view details of any given boutique. Similarly to the listing template, let’s create a new HTML file with the following code: /src/templates/boutique/boutique_details.html {% "oscar/layout.html" %} extends {% i18n %} load {% product_tags %} load {% title %} block {% "Boutiques" %} trans | {{ block.super }} {% %} endblock {% breadcrumbs %} block < = > nav aria-label "breadcrumb" < = > ol class "breadcrumb" < = > li class "breadcrumb-item" < = a href " {{ homepage_url }} "> {% "Home" %} trans </ > a </ > li < = = > li class "breadcrumb-item" aria-current "page" < = a href " {% 'boutique:index' %} url "> {% "Boutiques" %} trans </ > a </ > li < = = > li class "breadcrumb-item active" aria-current "page" {{ boutique.name }} </ > li </ > ol </ > nav {% %} endblock {% headertext %} block {% "Boutique" %} trans {% %} endblock {% content %} block < > p < > h2 {{ boutique.name }} The boutique is in: </ > h2 < > br {{ boutique.city }} The boutique's manager is Mr/Mrs: < > br < > strong {{ boutique.manager }} </ > strong </ > p {% content %} endblock The result should look like this: At this point the app’s model, configs, and front-end templates are ready. Now we can move on to develop an Oscar dashboard for the app. boutique 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 and tests.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 in as shown below: INSTALLED_APPS settings.py INSTALLED_APPS = [ ... , ] 'boutique.dashboard.apps.DashboardConfig' If you run the 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 . Now that the dashboard app is created we need to uncomment it as shown below: myoscarproject/urls.py django.apps apps django.urls include, path django.contrib admin urlpatterns = [ ... path( , apps.get_app_config( ).urls), ... ] from import from import from import 'dashboard/boutique/' 'boutique_dashboard' However, at this point label, is not associated with any configuration. Therefore, let's move on and create the Boutique Dashboard Oscar app config. boutique_dashboard 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: django.urls path oscar.core.application OscarDashboardConfig oscar.core.loading get_class name = label = namespace = default_permissions = [ ] self.boutique_list_view = get_class( , ) self.boutique_create_view = get_class( , ) self.boutique_update_view = get_class( , ) self.boutique_delete_view = get_class( , ) urls = [ path( , self.boutique_list_view.as_view(), name= ), path( , self.boutique_create_view.as_view(), name= ), path( , self.boutique_update_view.as_view(), name= ), path( , self.boutique_delete_view.as_view(), name= ), ] self.post_process_urls(urls) from import from import from import : class DashboardConfig (OscarDashboardConfig) 'boutique.dashboard' 'boutique_dashboard' 'boutique-dashboard' 'is_staff' : def ready (self) 'boutique.dashboard.views' 'DashboardBoutiqueListView' 'boutique.dashboard.views' 'DashboardBoutiqueCreateView' 'boutique.dashboard.views' 'DashboardBoutiqueUpdateView' 'boutique.dashboard.views' 'DashboardBoutiqueDeleteView' : def get_urls (self) '' 'boutique-list' 'create/' 'boutique-create' 'update/<int:pk>/' 'boutique-update' 'delete/<int:pk>/' 'boutique-delete' return One important point in this configuration is to change the parameter. The Django Oscar's default dashboard app conflicts with the Boutique dashboard app. Django's documentation state that: label DashboardConfig defaults to the last component of name. AppConfig.label Therefore, it is necessary to choose a different label in order to "tell" Django that this dashboard app is different from Oscar's built-in dashboard app. boutique_dashboard Another difference between dashboard app config from primary boutique app config is the parameter. This parameter sets Oscar's dashboard permissions for this dashboard app. Since the Oscar has multiple user permission levels like one that has , setting this parameter disables access to this dashboard for any user except c users like super-users. default_permissions Fulfilment Parters is_staff Forms for the Boutique’s dashboard app First, it is necessary to create forms for your custom dashboard app. Create a file in directory and add the following code: forms.py boutique/dashboard django forms django.db.models Q django.utils.translation gettext_lazy _ oscar.core.loading get_model Boutique = get_model( , ) name = forms.CharField(label=_( ), required= ) city = forms.CharField(label=_( ), required= ) d = getattr(self, , {}) d.get(key, ) empty( ) empty( ) words = value.replace( , ).split() q = [Q(city__icontains=word) word words] qs.filter(*q) qs.filter(name__icontains=value) key, value self.cleaned_data.items(): value: qs = getattr(self, % key)(qs, value) qs model = Boutique fields = ( , , ) from import from import from import as from import 'boutique' 'Boutique' : class DashboardBoutiqueSearchForm (forms.Form) 'Boutique name' False 'City' False : def is_empty (self) 'cleaned_data' : def empty (key) return not None return 'name' and 'city' : def apply_city_filter (self, qs, value) ',' ' ' for in return : def apply_name_filter (self, qs, value) return : def apply_filters (self, qs) for in if 'apply_%s_filter' return : class DashboardBoutiqueCreateUpdateForm (forms.ModelForm) : class Meta 'name' 'manager' 'city' In the code above is a form to filter Boutique instances in the dashboard. We design our form so that it can filter by model's and fields. The form is the create and update form required to create or edit a boutique instance. This form inherits Django's default so it is relatively simple to make it work. DashboardBoutiqueSearchForm city name DashboardBoutiqueCreateUpdateForm forms.ModelForm 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 the file of the boutique's dashboard app: views.py django.contrib messages django.template.loader render_to_string django.urls reverse_lazy django.utils.translation gettext django.utils.translation gettext_lazy _ django.views generic oscar.core.loading get_class, get_model Boutique = get_model( , ) BoutiqueCreateUpdateForm = get_class( , ) DashboardBoutiqueSearchForm = get_class( , ) from import from import from import from import from import as from import from import 'boutique' 'Boutique' 'boutique.dashboard.forms' 'DashboardBoutiqueCreateUpdateForm' '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 as shown in the following code: generic.ListView model = Boutique template_name = context_object_name = paginate_by = filterform_class = DashboardBoutiqueSearchForm data = getattr(self.filterform, , {}) name = data.get( , ) city = data.get( , ) name city: gettext( ) % (name) name city: gettext( ) % (name, city) city: gettext( ) % (city) : gettext( ) data = super().get_context_data(**kwargs) data[ ] = self.filterform data[ ] = self.get_title() data qs = self.model.objects.all() self.filterform = self.filterform_class(self.request.GET) self.filterform.is_valid(): qs = self.filterform.apply_filters(qs) qs : class DashboardBoutiqueListView (generic.ListView) "dashboard/boutique/boutique_list.html" "boutique_list" 20 : def get_title (self) 'cleaned_data' 'name' None 'city' None if and not return 'Boutiques matching "%s"' elif and return 'Boutiques matching "%s" near "%s"' elif return 'Boutiques near "%s"' else return 'Boutiques' : def get_context_data (self, **kwargs) 'filterform' 'queryset_description' return : def get_queryset (self) if return The only non-trivial part of the code above is the additional parameter, , which is essentially a parameter that is recognized and processed by Oscar's templates. filterform_class Creating Boutique instances in the dashboard Similarly, the view responsible for creating the boutique instances inherits and is shown in the following code: generic.CreateView model = Boutique template_name = form_class = BoutiqueCreateUpdateForm success_url = reverse_lazy( ) ctx = super().get_context_data(**kwargs) ctx[ ] = _( ) ctx messages.error( self.request, ) super().forms_invalid(form, inlines) response = super().forms_valid(form, inlines) msg = render_to_string( , { : self.object}) messages.success(self.request, msg, extra_tags= ) response : class DashboardBoutiqueCreateView (generic.CreateView) 'dashboard/boutique/boutique_update.html' 'boutique-dashboard:boutique-list' : def get_context_data (self, **kwargs) 'title' 'Create new boutique' return : def forms_invalid (self, form, inlines) "Your submitted data was not valid - please correct the below errors" return : def forms_valid (self, form, inlines) 'dashboard/boutique/messages/boutique_saved.html' 'boutique' 'safe' return In the code above, the parameter is assigned to 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 and . success_url reverse_lazy forms_invalid 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 instead. generic.UpdateView model = Boutique template_name = form_class = BoutiqueCreateUpdateForm success_url = reverse_lazy( ) ctx = super().get_context_data(**kwargs) ctx[ ] = self.object.name ctx messages.error( self.request, ) super().forms_invalid(form, inlines) msg = render_to_string( , { : self.object}) messages.success(self.request, msg, extrforms_valida_tags= ) super().forms_valid(form, inlines) : class DashboardBoutiqueUpdateView (generic.UpdateView) "dashboard/boutique/boutique_update.html" 'boutique-dashboard:boutique-list' : def get_context_data (self, **kwargs) 'title' return : def forms_invalid (self, form, inlines) "Your submitted data was not valid - please correct the below errors" return : def forms_valid (self, form, inlines) 'dashboard/boutique/messages/boutique_saved.html' 'boutique' 'safe' return Deleting Boutique instances from the dashboard Delete view is rather simple compared to others and it inherits Django’s as shown below: generic.DeleteView model = Boutique template_name = success_url = reverse_lazy( ) : class DashboardBoutiqueDeleteView (generic.DeleteView) "dashboard/boutique/boutique_delete.html" '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 . In this directory, we must implement three-view templates and one message template: /src/templates/dashboard 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, . Since templates are long you can find them in the of this tutorial. After templates are ready the following screen will be available when you go to URL: {% extends 'oscar/dashboard/layout.html' %} Git repository http://127.0.0.1:8000/dashboard/boutique/ Adding “Boutiques” navigation item to Django-Oscar’s dashboard Finally, after 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: Boutiques django.utils.translation gettext_lazy _ oscar.defaults * OSCAR_DASHBOARD_NAVIGATION.append({ : _( ), : , : , }) from import as ... # Django's Other Settings from import '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 Previously published at https://mmtechslv.com/tutorials/django-oscar-new-app-part-1/
Share Your Thoughts