Each time i have a new mobile application to develop, i need to create the Ionic project and the Django project as backend.
It is always a boring and repetitive task and a lot of copy/paste code from previous projects. So i decided to create a tool which will scaffold the ionic and django projects code for me.
Ionic and Django Kickoff project
I have made this tool available on a dedicated website. You will find basic free plan and paid plans.
To let the magic happens, all we have to do is to create a django models.py file and just upload it.
Then we will receive an email with a download link containing our code ready to use.
The generated code will be using Django 3.0 and Ionic 5.0 (Angular 10 version) + Capacitor 2.0
Design and implement a django models.py file to scaffold your project
Each project needs entities to deal with business cases. Let's say we want to build an eShop app for a restaurant or any kind of shops (such as bakery,...).
Entities, could be:
Entity | Role |
User | Users can register/login into our app |
Shop | Information about the shop such as the address, the opening rules, the closing rules... |
Products | Obviously shops are selling products. |
Categories | A product belongs to a category |
News | Some actualities about the shop to communicate with users |
Order | Users should be able to order products |
These few entities are minimal requirements but we could imagine a lot more such as payment, delivery, dealing with promotion, vat...
And as a minimal requirement you will need to create CRUD methods for each entity (create, read, update, delete) and some other methods to find or filter data.
It's a lot of required code that we need to create twice : in the Ionic application and on the Django backend project.
So since good developers are lazy one, let's see how to let Ionic and Django kickoff website do the job for us.
Django models.py file for our Ionic eShop app
One implementation could be:
# Models for an eShop. # -*- coding: utf-8 -*- from __future__ import unicode_literals import urllib import uuid from urllib.request import urlopen from django.contrib.auth.base_user import BaseUserManager, AbstractBaseUser from django.contrib.auth.models import PermissionsMixin from django.shortcuts import reverse from django.db import models from django.utils.encoding import smart_str from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ from django.utils.datetime_safe import datetime from django.core.exceptions import ValidationError from django.http import HttpResponseRedirect from django.utils import timezone from django.contrib.gis.db import models WEEKDAYS = [ (1, _("Monday")), (2, _("Tuesday")), (3, _("Wednesday")), (4, _("Thursday")), (5, _("Friday")), (6, _("Saturday")), (0, _("Sunday")), ] class UserManager(BaseUserManager): use_in_migrations = True def _create_user(self, email, password, **extra_fields): if not email: raise ValueError('The given email must be set') email = self.normalize_email(email) user = self.model(email=email, **extra_fields) user.set_password(password) user.save(using=self._db) return user def create_user(self, email, password=None, **extra_fields): extra_fields.setdefault('is_superuser', False) return self._create_user(email, password, **extra_fields) def create_superuser(self, email, password, **extra_fields): extra_fields.setdefault('is_superuser', True) if extra_fields.get('is_superuser') is not True: raise ValueError('Superuser must have is_superuser=True.') return self._create_user(email, password, **extra_fields) class User(AbstractBaseUser, PermissionsMixin): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) email = models.EmailField(_('email address'), unique=True) first_name = models.CharField(_('first name'), max_length=30, blank=True) last_name = models.CharField(_('last name'), max_length=30, blank=True) date_joined = models.DateTimeField(_('date joined'), auto_now_add=True) is_active = models.BooleanField(_('active'), default=True) is_staff = models.BooleanField(_('staff'), default=True) avatar = models.ImageField(upload_to='avatars/', null=True, blank=True) objects = UserManager() USERNAME_FIELD = 'email' REQUIRED_FIELDS = [] class Meta: verbose_name = _('user') verbose_name_plural = _('users') def get_full_name(self): full_name = '%s %s' % (self.first_name, self.last_name) return full_name.strip()
class News(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) title = models.CharField(max_length=250) text = models.TextField() image = models.ImageField(upload_to="media") createdAt = models.DateTimeField(auto_now_add=True) updatedAt = models.DateTimeField(auto_now=True) class Meta: verbose_name = _('News') verbose_name_plural = _('News') ordering = ['-createdAt'] def getThumb(self): return mark_safe('<img src="%s" style="width:100px;"/>' % self.image.url) getThumb.short_description = 'Thumbnail' class Shop(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) name = models.CharField(max_length=100, verbose_name=_('name')) logo = models.ImageField(null=True, blank=True) address = models.CharField( max_length=255, default='', blank=True, verbose_name=_('address')) zipCode = models.CharField( max_length=20, default='', blank=True, verbose_name=_('zipCode')) city = models.CharField(max_length=100, default='', blank=True, verbose_name=_('city')) phone = models.CharField(max_length=20, default='', blank=True, verbose_name=_('phone')) location = models.PointField(null=True, blank=True) email = models.EmailField(max_length=200, verbose_name=_('email')) facebookUrl = models.CharField( max_length=100, verbose_name=_('Facebook'), null=True, blank=True) twitterUrl = models.CharField( max_length=100, verbose_name=_('Twitter'), null=True, blank=True) instagramUrl = models.CharField( max_length=100, verbose_name=_('Instagram'), null=True, blank=True) googleUrl = models.CharField( max_length=100, verbose_name=_('Google'), null=True, blank=True) createdAt = models.DateTimeField(auto_now_add=True) updatedAt = models.DateTimeField(auto_now=True) def getThumb(self): if self.logo: return mark_safe('<img src="%s" style="width:100px;"/>' % self.logo.url) else: return mark_safe(self.name) getThumb.short_description = 'Thumbnail' def getLogo(self): if self.logo: return mark_safe('<img src="%s" style="width:100px;"/>' % self.logo.url) else: return mark_safe(self.name) getThumb.short_description = 'Thumbnail' class Meta: verbose_name = _('Shop') verbose_name_plural = _('Shops') def __str__(self): return u'%s' % (self.name) def idString(self): return str(self.id)
class ShopOpeningHours(models.Model): """ Store opening times of merchant premises, defined on a daily basis (per day) using one or more start and end times of opening slots. """ class Meta: verbose_name = _('Shop Opening Hours') # plurale tantum verbose_name_plural = _('Shop Opening Hours') ordering = ['refShop', 'weekday', 'from_hour'] refShop = models.ForeignKey( Shop, on_delete=models.CASCADE, related_name='shopopening', verbose_name=_('refShop')) weekday = models.IntegerField(_('Weekday'), choices=WEEKDAYS) from_hour = models.TimeField(_('Opening')) to_hour = models.TimeField(_('Closing')) def getJour(self): if self.weekday == 1: return _("Monday") elif self.weekday == 2: return _("Tuesday") elif self.weekday == 3: return _("Wednesday") elif self.weekday == 4: return _("Thursday") elif self.weekday == 5: return _("Friday") elif self.weekday == 6: return _("Saturday") elif self.weekday == 0: return _("Sunday") def __str__(self): return _("%(premises)s %(weekday)s (%(from_hour)s - %(to_hour)s)") % { 'premises': self.refShop, 'weekday': self.weekday, 'from_hour': self.from_hour, 'to_hour': self.to_hour } class ShopClosingRules(models.Model): """ Used to overrule the OpeningHours. This will "close" the store due to public holiday, annual closing or private party, etc. """ class Meta: verbose_name = _('Shop Closing Rule') verbose_name_plural = _('Shop Closing Rules') ordering = ['start'] refShop = models.ForeignKey( Shop, on_delete=models.CASCADE, related_name='shopclosing', verbose_name=_('refShop')) start = models.DateField(_('Start')) end = models.DateField(_('End')) reason = models.TextField(_('Reason'), null=True, blank=True) class Category(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) name = models.CharField(max_length=100, verbose_name=_('name')) image = models.ImageField(blank=True) online = models.BooleanField(default=True, verbose_name=_('online')) createdAt = models.DateTimeField(auto_now_add=True) updatedAt = models.DateTimeField(auto_now=True) def idString(self): return str(self.id) class Meta: verbose_name = _('Categorie') verbose_name_plural = _('Categories') def __str__(self): return str(self.name)
class Product(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) name = models.CharField(max_length=100, verbose_name=_('name')) mainImage = models.ImageField(blank=True) shortDescription = models.CharField( max_length=255, null=True, blank=True, verbose_name=_('shortDescription')) description = models.TextField(null=True, blank=True) refCategory = models.ForeignKey( Category, related_name='category_product', on_delete=models.CASCADE, verbose_name=_('refCategory')) priceWithoutVat = models.FloatField( default=0, verbose_name=_('price without vat')) online = models.BooleanField(default=True, verbose_name=_('online')) createdAt = models.DateTimeField(auto_now_add=True) updatedAt = models.DateTimeField(auto_now=True) class Meta: verbose_name = _('Product') verbose_name_plural = _('Products') ordering = ['-createdAt'] def __str__(self): return "%s - %s" % (self.refCategory, self.name) def image(self): return self.mainImage def price(self): return self.priceWithoutVat def getThumb(self): if self.mainImage: return mark_safe('<img src="%s" style="width:100px;"/>' % self.mainImage.url) else: return _('No Logo') getThumb.short_description = 'Thumbnail' def get_product_url(self): return reverse("backoffice:product", kwargs={ 'id': self.id }) def idString(self): return str(self.id)
class Order(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) orderNumber = models.PositiveIntegerField( "Order Number", null=True, default=None, unique=True) refUser = models.ForeignKey(User, on_delete=models.PROTECT) totalPrice = models.FloatField(default=0, verbose_name=_('totalPrice')) status_choix = ( (0, _("Ordered")), (1, _("Paid")), (2, _("Payment refused")), (3, _("Waiting confirmation")), (4, _("Accepted")), (5, _("Declined")), (6, _("To deliver")), (7, _("Delivered")), (8, _("Reception confirmed")), (9, _("To refund")), (10, _("Refunded")), (11, _("Canceled")), (12, _("Error Refund")), (13, _("Unknown technical error")), (14, _("Order aborted")), (15, _("Not delivered")), (16, _("Order modified")), ) orderStatus = models.IntegerField(choices=status_choix, default=0) statusComment = models.TextField(null=True, blank=True) stripeChargeId = models.CharField(max_length=255, default='') createdAt = models.DateTimeField(auto_now_add=True) updatedAt = models.DateTimeField(auto_now=True) class Meta: verbose_name = _('Order') verbose_name_plural = _('Orders') ordering = ['-createdAt', ] def __str__(self): return u'%s (%s - %d)' % (self.orderNumber, self.refUser, self.totalPrice) def idString(self): return str(self.id) def save(self, *args, **kwargs): if self.orderNumber is None: self.get_or_assign_number() super(Order, self).save(*args, **kwargs) def get_or_assign_number(self): if self.orderNumber is None: epoch = timezone.now() epoch = epoch.replace(epoch.year, 1, 1) qs = Order.objects.filter( orderNumber__isnull=False, createdAt__gt=epoch) qs = qs.aggregate(models.Max('orderNumber')) try: epoc_number = int(str(qs['orderNumber__max'])[5:]) + 1 self.orderNumber = int( '{0}{1:06d}'.format(epoch.year, epoc_number)) except (KeyError, ValueError): # the first order this year self.orderNumber = int('{0}000001'.format(epoch.year)) return self.get_orderNumber() def get_orderNumber(self): return '{0}-{1}'.format(str(self.orderNumber)[:5], str(self.orderNumber)[5:]) @classmethod def resolve_number(cls, orderNumber): orderNumber = orderNumber[:4] + orderNumber[5:] return dict(orderNumber=orderNumber)
class OrderProduct(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) refOrder = models.ForeignKey( Order, on_delete=models.CASCADE, related_name='orderproducts') refProduct = models.ForeignKey(Product, on_delete=models.PROTECT) quantity = models.IntegerField(default=1, verbose_name=_('quantity')) pricePaid = models.FloatField(default=0, verbose_name=_('pricePaid')) createdAt = models.DateTimeField(auto_now_add=True) updatedAt = models.DateTimeField(auto_now=True) class Meta: verbose_name = _('OrderProduct') verbose_name_plural = _('OrderProducts') ordering = ['refOrder', ] def __str__(self): return u'%s - %s' % (self.refOrder, self.refProduct) def idString(self): return str(self.id) def save(self, *args, **kwargs): super(OrderProduct, self).save(*args, **kwargs)
Nothing really difficult. I just define the entities that i will use in the application.
Notice: In the generated code we will find all methods to deal with user authentification: account creation, login page, forgotten password and payments with Stripe.
To be able to do that, we need to have a User class in our models ! This is a requirement otherwise the code generation will fail. But when uploading the models.py file with the tool, there is an option to let us specify if we want the tool to add this User class or not.
Upload a models.py file and scaffold your Ionic and Django application
So let's go on Ionic and Django kickoff website and choose a plan : Basic, Silver, Gold or Business. Then we will access to a form in which we can provide some details:

Name of the application | Your ionic and Django package will be named this value |
Domain url | The url on which you will deploy the Django project. You can enter http://127.0.0.1:8000 if you run the django server on your machine for development |
Include user model | As i said, the tool needs to have an User class to generate all the code related to authentification. If you haven't already incluced the User in your models.py file, check this option. |
Specific requirements | The Django project includes and uses some packages (that are specified below the form). You can use this box to enter your specific django packages (if any). |
Fileupload | Just browse to select your models.py file in your disk |
Then you can click the upload button and after few minutes the scaffolded packages for Ionic and django, will be available and you will receive an email with a download link.
If an error occurs, an email with the error will be sent to investigate and check your models.py file.
The package (which is a zip) extracted will contain a directory named with the value choosen in the previous form : eShop.
This directory itself will contain two directories : Backend for Django code and Frontend for the Ionic code.
Let's first focus on the Django scaffolded project.
Django generated package
The Backend directory contains our Django project. The most important directories are api which contains the endpoints our Ionic application will use to get data, and a bo directory containing the models.py file and the admin.py file for our backoffice interface (silver and gold packages only).
Django Rest API generated
The API folder contains our generated API using Django Rest framework. A postman_collection.json file has also been generated so it's easy to import in Postman and have a quick look at all auto generated endpoints:

from django.conf.urls import include, url from .views import * from rest_framework.decorators import api_view from rest_framework.routers import DefaultRouter router = DefaultRouter() urlpatterns = [ url(r'^getSetupIntentMethod/$',setupIntentMethod,name='setupIntentMethod'), url(r'^createpaymentintent/$',createpaymentintent,name='createpaymentintent'), url(r'^createpaymentintentWithPaymentMethod/$',createpaymentintentWithPaymentMethod,name="createpaymentintentWithPaymentMethod"), url(r'^createpaymentmethod/$',createpaymentmethod,name='createpaymentmethod'), url(r'^createpaymentsubscriptionmethod/$',createpaymentsubscriptionmethod,name='createpaymentsubscriptionmethod'), url(r'^stripeSubscriptionStatus/$',stripeSubscriptionStatus,name='stripeSubscriptionStatus'), url(r'^endSubscription/$',endSubscription,name='endSubscription'), url(r'^user/(?P<pk>[0-9A-Fa-f-]+)/$', UserDetailView.as_view()), url(r'^user/$', UserListView.as_view()), url(r'^news/(?P<pk>[0-9A-Fa-f-]+)/$', NewsDetailView.as_view()), url(r'^news/$', NewsListView.as_view()), url(r'^shop/(?P<pk>[0-9A-Fa-f-]+)/$', ShopDetailView.as_view()), url(r'^shop/$', ShopListView.as_view()), url(r'^shopopeninghours/(?P<pk>[0-9A-Fa-f-]+)/$', ShopOpeningHoursDetailView.as_view()), url(r'^shopopeninghours/$', ShopOpeningHoursListView.as_view()), url(r'^shopclosingrules/(?P<pk>[0-9A-Fa-f-]+)/$', ShopClosingRulesDetailView.as_view()), url(r'^shopclosingrules/$', ShopClosingRulesListView.as_view()), url(r'^category/(?P<pk>[0-9A-Fa-f-]+)/$', CategoryDetailView.as_view()), url(r'^category/$', CategoryListView.as_view()), url(r'^product/(?P<pk>[0-9A-Fa-f-]+)/$', ProductDetailView.as_view()), url(r'^product/$', ProductListView.as_view()), url(r'^order/(?P<pk>[0-9A-Fa-f-]+)/$', OrderDetailView.as_view()), url(r'^order/$', OrderListView.as_view()), url(r'^orderproduct/(?P<pk>[0-9A-Fa-f-]+)/$', OrderProductDetailView.as_view()), url(r'^orderproduct/$', OrderProductListView.as_view()), url(r'^stripesubscription/(?P<pk>[0-9A-Fa-f-]+)/$', StripeSubscriptionDetailView.as_view()), url(r'^stripesubscription/$', StripeSubscriptionListView.as_view()), url(r'^appprofile/(?P<pk>[0-9A-Fa-f-]+)/$', AppProfileDetailView.as_view()), url(r'^appprofile/$', AppProfileListView.as_view()), ]
Of course views and serializers have been generated too. The Django project is ready to user and deploy.
Ionic application scaffolded
The Frontend directory contains the source code of the generated Ionic application.
You can install it with
npm install ionic build ionic serve
API and entities generated
The src/app/services directory contains a file apiservice.service.ts and entities.ts with all the required code to deals with the entities and the CRUD API methods. Other endpoints have been generated too to manage Stripe subscription, payments...


The Ionic and Django projects are also already configured to have a secure API with JWT authentication. You can learn more about this on this tutorial.
Generated code in Silver package
With Silver package, the Django admin contains an admin.py file with our models ready to use in the Django admin interface.
The ionic application contains two pages: one for registering and one for login with all required methods for social login too: Apple signin, Google signin and Facebook login.


The project uses ngx-translate for managing internationalization so the application will be i18n ready.
Please notice the Ionic application uses Capacitor and each plugin needs some specific configuration (such as application identifier), so please consult the documentation of each plugin to finalize configuration:
Apple sign in | Documentation |
Google sign in | Documentation |
Facebook login | Documentation |
Ionic generated code with Gold and Business packages
From a generation code point of view, both packages Gold and Business are similar. The only difference is that you can generate as many project as you want with the Business package payment.
Here is the list of available screens/features
Screen | Purpose |
Google Maps | Component which geolocates the user and display a Google Map |
Google places | Component to search Google places with an history of previous searchs |
Rating | A component which displays an Amazon like display view |
Search | A page which implements a search |
YouTube Player | A page which displays a youtube video |
List swipe | A page which displays a swipeable list |
List expandable | A page which displays an expandable list |
Cards | A page which displays cards |
Grid | A page which displays images as grid |
Profile | A profile page |
Setting | An example of setting page design |
Payment | A page for payment (design only) |
Save card | A page which uses Stripe (SCA Ready) to let user enters it's credit card detail and save it for later payment (real code, not a mock) |
Pay | A page example to show how to proceed with payment for a saved card (Stripe SCA ready) |
Pay Stripe | A page example to show you how to pay with Stripe (without a saved card) |
Stripe subscription | A page to show how to subscribe / unsubscribe a User with Stripe |