Design and setup Django project for a Ionic application, part 3/3

DAY 1 - September 2018

Developing a Django backoffice for our ionic application


Ok so now it's time to implement our project with our User and Bike tables, and to setup our backoffice (web admin interface) to display, manipulate our data.

Create django application


To create our backoffice application, just write:

python manage.py startapp backoffice

A new directory backoffice has been created with some files inside. We will edit the models.py file to create our User and Bike models:

from django.db import models
from django.core.files import File
from django.contrib.auth.models import PermissionsMixin
from django.contrib.auth.base_user import AbstractBaseUser
from django.db import models
from django.contrib.gis.db import models
from django.contrib.gis.geos import Point
from backoffice.UserManager import UserManager
from django.contrib.auth.hashers import get_hasher, identify_hasher
import uuid
class User(AbstractBaseUser, PermissionsMixin):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    email = models.EmailField(unique=True,db_index=True)
    username = models.CharField(max_length=100, null=True, blank=True)
    facebookId = models.CharField(max_length=100, null=True, blank=True,db_index=True)
    android = models.BooleanField(blank=True, default=False)
    ios = models.NullBooleanField(blank=True, default=False, null=True)
    acceptPush = models.BooleanField(default=False)
    pushToken = models.CharField(max_length=100, null=True, blank=True,db_index=True)
    is_active = models.BooleanField(('active'), default=True)
    is_staff = models.BooleanField(('staff'), default=False)
    valid = models.BooleanField(default=True)
    objects = UserManager()
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []
    class Meta:
        verbose_name = ('User')
        verbose_name_plural = ('Users')
class Bike(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    reference = models.CharField(max_length=100,db_index=True)
    qrCode = models.CharField(max_length=100, null=True, blank=True, db_index=True)
    picture = models.ImageField(upload_to="media/%Y/%m/%d",null=True, blank=True)
    location = models.PointField(null=True, blank=True)
    available = models.BooleanField(default=True)
    valid = models.BooleanField(default=True)
    class Meta:
        verbose_name = ('Bike')
        verbose_name_plural = ('Bikes')

Few remarks:

  • we are extending the default Django User (please refer to this blog), because we will use the email in our application to authenticate the user.
  • We are using UUID as primary key to avoid future performance issues(and not default auto incremented key)

Extend default django User model

As we are extending the User model, we need to deal with the user creation (as explained in the previous link). So we create a new class UserManager.py:

from django.contrib.auth.base_user import BaseUserManager
class UserManager(BaseUserManager):
    use_in_migrations = True
    def _create_user(self, email, password, **extra_fields):
        """
        Creates and saves a User with the given email and password.
        """
        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) 

Change password encryption algorithm

Our next step is to deal with the authentification process. We will not use the default Django encryption system because it will take too much time for a mobile application to encrypt or decrypt password (few seconds). Instead we will use SHA256 encryption algorithm, which is strong enough for a mobile application.
Inside our backoffice directory, we will create a new python package called libs and will add a hashers.py file:

import hashlib
from django.utils.translation import ugettext_noop as _
from django.contrib.auth.hashers import BasePasswordHasher, mask_hash
from django.utils.datastructures import OrderedDict
class SHA256PasswordHasher(BasePasswordHasher):
    algorithm = "sha256"
    def encode(self, password, salt, iterations=None):
        assert password is not None
        return hashlib.sha256(password.encode('utf-8')).hexdigest()
    def verify(self, password, encoded):
        return hashlib.sha256(password.encode('utf-8')).hexdigest() == encoded
    def safe_summary(self, encoded):
        algorithm, iterations, salt, hash = encoded.split('$', 3)
        return OrderedDict([
            (_('algorithm'), 'md5'),
            (_('hash'), mask_hash(hash)),
        ])

Then in our bikebackend directory, we will edit the settings.py file to add:


#Custom User
AUTH_USER_MODEL = 'backoffice.User'
AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.ModelBackend',
    'backoffice.customAuthentification.customAuthentification',
)

and in our backoffice directory, we will add the file customAuthentification.py to deal with the encryption/decryption process:

# -*-coding:utf-8 -*-
from django.contrib.auth.models import User
from .libs.hashers import *
from .models import User
class customAuthentification(object):
    """
    Use the login name and a hash of the password. For example:
    """
    def authenticate(self, username=None, password=None):
        if username:
            try:
                user = User.objects.get(username=username)
                encoder = SHA256PasswordHasher()
                if SHA256PasswordHasher.verify(encoder,password,user.password):
                    return user
                else:
                    return  None
            except User.DoesNotExist:
                return None
        return None
    def get_user(self, user_id):
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None

All these steps could seem a little bit difficult, but you don't need to know them perfectly. Just copy/paste the code each time you will begin a new Django project. The most important part is to focus on the models.py file to define our models and to the admin.py file which will be used to show our administration interface.
Before moving on this administration part, we will modify the settings.py file to indicate that we have added our backoffice application:

# Application definition
INSTALLED_APPS = [
    'backoffice.apps.BackofficeConfig', # <= this is the important line 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ] < code>

Ok now it's time to deal with the administration interface.

Django admin interface

To display and administrate your models on the administration interface, edit the admin.py file and register them:

from django.contrib import admin
# *coding: utf-8*
from django.contrib import admin
from .models import *
admin.site.register(User)
admin.site.register(Bike)

That's it, nothing else to do.
So now we would like to access it, but we need to setup our database ! And at the moment, the Django project is setup with the default sqlite database, which is not compatible with our UUID primary key field.
So on the next coming tutorial day 2, we will configure and deploy our project in the cloud, and then enhance the admin interface a little bit. Stay tuned...