Ionic and Django: generate your code in few seconds

June 2020

Each time i have a new mobile application to develop, i need to create the Ionic project plus 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 generate the code for me.

Ionic and Django Kickoff project

I have made the tool available on a dedicated website. You will find a 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 version) + Capacitor 2.0

Design and implement a django models.py file

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 we can also easily deduce that we will need methods to find or filter our data.

It's a lot of required code that we need to create twice: In the Ionic application and on the Django backend application.

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.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 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 AppUser(models.Model):
    email = models.CharField(max_length=100, unique=True, verbose_name='email')
    password = models.CharField(max_length=255, verbose_name='password')
    userName = models.CharField(max_length=100, null=True, blank=True, unique=True, verbose_name='username')
    firstName = models.CharField(max_length=100, null=True, blank=True, verbose_name='firstName')
    lastName = models.CharField(max_length=100, null=True, blank=True, verbose_name='lastName')
    pushToken = models.CharField(max_length=200, null=True, blank=True)
    facebookToken = models.CharField(max_length=200, null=True, blank=True)
    googleToken = models.CharField(max_length=200, null=True, blank=True)
    appleToken = models.CharField(max_length=200, null=True, blank=True)
    picture = models.ImageField(upload_to="media", null=True, blank=True)
    online = models.BooleanField(default=False)
    lastConnexionDate = models.DateTimeField(null=True, blank=True)
    valid = models.BooleanField(default=True)
    stripeCustomerId = models.CharField(max_length=100, default='NONE', blank=True)
    stripePaymentMethodId = models.CharField(max_length=100, default='NONE', blank=True)
    connection_choice = (
            (0, "ios"),
            (1, "android"),
            (2, "web"),
    )
    connectionType = models.IntegerField(choices=connection_choice, null=True, blank=True)
    createdAt = models.DateTimeField(auto_now_add=True)
    updatedAt = models.DateTimeField(auto_now=True)
    @property
    def last_login(self):
        return self.lastConnexionDate
    def __str__(self):
        return u'%s' % (self.email)
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):
    """
         Tables des produits
         """
    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):
    """
      orders
      """
    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(AppUser, 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):
    """
       Products in the order
      """
    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 and login pages. To be able to do that, the tool needs to know your user entity and can't guess it ! So as requirement and this is very important you need to use this model:

class AppUser(models.Model):
    email = models.CharField(max_length=100, unique=True, verbose_name='email')
    password = models.CharField(max_length=255, verbose_name='password')
    userName = models.CharField(max_length=100, null=True, blank=True, unique=True, verbose_name='username')
    firstName = models.CharField(max_length=100, null=True, blank=True, verbose_name='firstName')
    lastName = models.CharField(max_length=100, null=True, blank=True, verbose_name='lastName')
    pushToken = models.CharField(max_length=200, null=True, blank=True)
    facebookToken = models.CharField(max_length=200, null=True, blank=True)
    googleToken = models.CharField(max_length=200, null=True, blank=True)
    appleToken = models.CharField(max_length=200, null=True, blank=True)
    picture = models.ImageField(upload_to="media", null=True, blank=True)
    online = models.BooleanField(default=False)
    lastConnexionDate = models.DateTimeField(null=True, blank=True)
    valid = models.BooleanField(default=True)
    stripeCustomerId = models.CharField(max_length=100, default='NONE', blank=True)
    stripePaymentMethodId = models.CharField(max_length=100, default='NONE', blank=True)
    connection_choice = (
            (0, "ios"),
            (1, "android"),
            (2, "web"),
    )
    connectionType = models.IntegerField(choices=connection_choice, null=True, blank=True)
    reatedAt = models.DateTimeField(auto_now_add=True)
    updatedAt = models.DateTimeField(auto_now=True)
    @property
    def last_login(self):
        return self.lastConnexionDate
    def __str__(self):
        return u'%s' % (self.email) 

If we don't include this AppUser class in your models.py file, the code generation will fail.

Of course you can add additional fields in the model if you need more fields (but don't remove existing fields)

Ok let's generate our code now !