How to build a REST API with Django and JWT Auth

It’s been a while since I wrote anything in the blog.

Today on this short tutorial I’ll explain the steps on how to build an api app with Django, using the JWT (JSON Web Token) as a way to identify users.

Libraries we’ll use are:

Docker
Django 1.11
Python 3
Django’s REST Framework
Django’s JWT

So let’s start by creating a Django project with Docker. If you still don’t know how to do that, more info on how to do create a django project with docker here.

Our requirements.txt would something like this:

django==1.11
mysqlclient
djangorestframework
djangorestframework-jwt
requests

*Note: The requests library is just to create some requests (GET, POST…) inside our python code.

For this example, I’ve named the project ‘apiskeleton’ and the app ‘apiapp’.

Step1: Django’s REST Framework

Once we have created the project and app, let’s register our app and the rest framework app also in our settings.

#settings.py

INSTALLED_APPS = [
	# ...
	'apiapp',
    'rest_framework',
	# ...
]

Great, now we’re ready to create some logic in our app.

First off, if you haven’t done already, create a separate User model. This is best practices for Django.

#models/user.py
from django.db import models
from django.contrib.auth.models import AbstractUser


class User(AbstractUser):
    pass

Update the settings.py

#settings.py

# Custom User model
AUTH_USER_MODEL = 'apiapp.User'

Now, let’s create a serializer for our User model. The serializer will allow us to convert a model instance or queryset to a JSON content type. More info on serializers here.

Our serializers.py would look something like this:

#serializers/user.py
from rest_framework import serializers
from appapi.models import User

class UserSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    username = serializers.CharField(required=True, allow_blank=False, max_length=100)
    password = serializers.CharField(required=True, write_only=True)
    email = serializers.CharField(required=True, allow_blank=True, max_length=100)
    is_staff = serializers.BooleanField(required=False, default=False)
    is_superuser = serializers.BooleanField(required=False, default=False)

    def create(self, data):
        """
        Create and return a new `Snippet` instance, given the validated data.
        """
        instance = User.objects.create(
            username=data.get('username'),
            email=data.get('email'),
            is_staff=data.get('is_staff'),
            is_superuser=data.get('is_superuser'),
        )
        instance.set_password(data.get('password'))
        instance.save()
        return instance

    def update(self, instance, data):
        """
        Update and return an existing `Snippet` instance, given the validated data.
        """
        instance.username = data.get('username', instance.username)
        instance.email = data.get('email', instance.email)
        instance.is_staff = data.get('is_staff', instance.is_staff)
        instance.is_superuser = data.get('is_superuser', instance.is_staff)
        instance.set_password(data.get('password'))
        instance.save()
        return instance 

It’s very simple, I know, but I like the readability here! There are probably better ways to make your serializers, for instance, by extending serializers.ModelSerializer, more info on this here, but it’s not the aim of this tutorial right now. Trying to make things as easy and readable as possible.

Now let’s add a GET API route to list all users, something on the lines of /user/. Since it’s listing all users, it should only be allowed for an admin user to use it right? For now, I’ll just call it api_admin_user_index, so I know this is for admins only. Later on we’ll secure these.

#urls.py
urlpatterns = [
    url(r'^user/$', user.api_admin_user_index, name='api_admin_user_index'),
]

Now, let’s create the view for this url:

#views/user.py
from rest_framework.decorators import api_view
from rest_framework.response import Response
from apiapp.models import User
from apiapp.serializers.user import UserSerializer

@api_view(['GET'])
def api_admin_user_index(request):
    """
    get:
    List all users.
    """
    serializer = UserSerializer(User.objects.all(), many=True)
    return Response(serializer.data)

This first parameter in the serializer is the queryset, and the second parameter is to let the serializer know this is a list. More info on this on the BaseSerializer class from Django’s Rest Framework.

Now, if we open a browser and type this route, we should see a list of 0 users, but the route works right? Success!

Now let’s create some users. We should add the POST verb in our view to do that, so it would look something like this:

#views/user.py
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from apiapp.models import User
from apiapp.serializers.user import UserSerializer
from apiapp.utils.jsonreader import JsonReader


@api_view(['GET', 'POST'])
def api_admin_user_index(request):
    """
    get:
    List all users.
    post:
    Create new user.
    """
    if request.method == 'GET':
        serializer = UserSerializer(User.objects.all(), many=True)
        return Response(serializer.data)

    elif request.method == 'POST':
        data = JsonReader.read_body(request)
        serializer = UserSerializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

The POST will create a user through the serializer.

In the code, you can see that I added a JsonReader class. This is a util class I use in most of my projects, it just makes reading the body easier, here’s the code:

#utils/jsonreader.py
import json


class JsonReader:

    @staticmethod
    def read_body(request):
        '''returns a dict from the body of request'''

        data = dict()

        try:
            body = request.body
            data = JsonReader.bytes_to_dict(body)
        except Exception as exc:
            if isinstance(request.data, dict):
                data = request.data
            elif isinstance(request.data, str):
                data = JsonReader.str_to_dict(request.data)

        return data

    @staticmethod
    def str_to_dict(s: str):
        dict = json.loads(s)
        return dict

    @staticmethod
    def dict_to_str(d: dict):
        str = json.dumps(d)
        return str

    @staticmethod
    def bytes_to_dict(b: bytes):
        str = b.decode("utf-8")
        dict = JsonReader.str_to_dict(str)
        return dict

Now we can create some users through our POST method, just sending some JSON with the keys username, password and email, which are the required ones in our serializer. Simple right?

Now it’s time for the rest of the usual verbs, PUT and DELETE. Also, we need a GET for a single user.

The routes would look something like this:

#urls.py
urlpatterns = [
    url(r'^user/$', user.api_admin_user_index, name='api_admin_user_index'),
    url(r'^user/(?P[0-9]+)/$', user.api_admin_user_detail, name='api_admin_user_detail'),
]

And now let’s update the view

#views/user.py
@api_view(['GET', 'PUT', 'DELETE'])
def api_admin_user_detail(request, pk):
    """
    get:
    Detail one user.
    put:
    Update one user.
    delete:
    Delete one user.
    """

    try:
        user = User.objects.get(pk=pk)
    except User.DoesNotExist:
        return Response({'error': "User " + pk + " does not exist"}, status=status.HTTP_404_NOT_FOUND)

    if request.method == 'GET':
        serializer = UserSerializer(user)
        return Response(serializer.data)

    elif request.method == 'PUT':
        data = JsonReader.read_body(request)
        serializer = UserSerializer(user, data=data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    elif request.method == 'DELETE':
        user.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

All of this is great, but now let’s secure these admin calls right? Here’s where JWT comes into play.

Step2: Django’s JWT

JWT is a stateless authentication mechanism as the user state is never saved in the server memory. Django will check for a valid JWT in the Authorization header, and if it is there, the user will be allowed. As JWTs are self-contained, all the necessary information is there, reducing the need to go back and forth to the database.

This allows the user to fully rely on data APIs that are stateless. It doesn’t matter which domains are serving your APIs, as Cross-Origin Resource Sharing (CORS) won’t be an issue since it doesn’t use cookies.

With that being said, let’s start by updating our settings.py with the JWT configuration

#settings.py

# Configure the authentication in Django Rest Framework to be JWT
# http://www.django-rest-framework.org/
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
    ),
}

# Configure the JWTs to expire after 1 hour, and allow users to refresh near-expiration tokens
# https://getblimp.github.io/django-rest-framework-jwt/
JWT_AUTH = {
    # If the secret is wrong, it will raise a jwt.DecodeError telling you as such. You can still get at the payload by setting the JWT_VERIFY to False.
    'JWT_VERIFY': True,

    # You can turn off expiration time verification by setting JWT_VERIFY_EXPIRATION to False.
    # If set to False, JWTs will last forever meaning a leaked token could be used by an attacker indefinitely.
    'JWT_VERIFY_EXPIRATION': True,

    # This is an instance of Python's datetime.timedelta. This will be added to datetime.utcnow() to set the expiration time.
    # Default is datetime.timedelta(seconds=300)(5 minutes).
    'JWT_EXPIRATION_DELTA': datetime.timedelta(hours=1),

    'JWT_ALLOW_REFRESH': True,
    'JWT_AUTH_HEADER_PREFIX': 'JWT',
}

The config is pretty self-explanatory, but there’s more documentation on JWT settings here.

Once this is out of the way, let’s add some JWT route patterns so we can get that JWT token.

#urls.py
urlpatterns = [
    # ...
    url(r'^jwt/refresh-token/', refresh_jwt_token, name='refresh_jwt_token'),
    url(r'^jwt/api-token-verify/', verify_jwt_token, name='verify_jwt_token'),
    url(r'^jwt/api-token-auth/', obtain_jwt_token, name='obtain_jwt_token'),
]

Also, this is not necessary, but instead of exposing the JWT routes, let’s add a login API route that will wrap the obtaining of the JWT token.

#urls.py
urlpatterns = [
    # ...
    url(r'^login/', registration.api_login, name='api_login'),
]

And in our registration view

#views/registration.py
@api_view(['POST'])
def api_login(request):
    """
    post:
    This view is called through API POST with a json body like so:

    {
        "username": "admin",
        "password": "admin"
    }

    :param request:
    :return:
    """
    data = JsonReader.read_body(request)

    response_login = requests.post(
        request.build_absolute_uri(reverse('obtain_jwt_token')),
        data=data
    )
    response_login_dict = json.loads(response_login.content)
    return Response(response_login_dict, response_login.status_code)

Notice that this time I didn’t use the prefix ‘admin’. This is because this will be authorized to anonymous users, unlike the ones we created previously.

So as you can see, it’s pretty straightforward. This view is a wrapper of the JWT route. This way, we can add information to the response if we need to, for instance, the user id, user profile, etc. For now, let’s leave it this way.

If we now test it with a username and password, we should get the token. If you haven’t created any users yet, you could use the POST call we created previously. Otherwise, just add some superusers in the python console or create some fixtures, up to you.

If you’re lazy, here are my fixtures, and the location is

apiapp/fixtures/users.json
[
{
    "model": "apiapp.user",
    "pk": 1,
    "fields": {
        "password": "pbkdf2_sha256$36000$SoF7ueJzOE1I$244qaRI1dReT4DxZKXJi2sRmuKdqPcjeOiUaPdH2UV0=",
        "last_login": null,
        "is_superuser": true,
        "username": "admin",
        "first_name": "",
        "last_name": "",
        "email": "admin@slowcode.io",
        "is_staff": true,
        "is_active": true,
        "date_joined": "2018-08-29T10:09:27.191Z",
        "groups": [],
        "user_permissions": []
    }
},
{
    "model": "apiapp.user",
    "pk": 2,
    "fields": {
        "password": "pbkdf2_sha256$36000$bkA3NXYqXZ4S$zuH97poSj3trZoNRFeROw3PgutbGOHlZunliI8/1jbg=",
        "last_login": null,
        "is_superuser": false,
        "username": "user1",
        "first_name": "",
        "last_name": "",
        "email": "user1@slowcode.io",
        "is_staff": true,
        "is_active": true,
        "date_joined": "2018-08-29T10:10:56.873Z",
        "groups": [],
        "user_permissions": []
    }
},
{
    "model": "apiapp.user",
    "pk": 3,
    "fields": {
        "password": "pbkdf2_sha256$36000$KKujn28LvdGX$OxtfRmIUWNPwbBPsz2iwKwff8klJ5PiPXj3P9N70Hto=",
        "last_login": null,
        "is_superuser": false,
        "username": "user2",
        "first_name": "",
        "last_name": "",
        "email": "user2@slowcode.io",
        "is_staff": false,
        "is_active": true,
        "date_joined": "2018-08-29T10:11:19.576Z",
        "groups": [],
        "user_permissions": []
    }
}
]

To import them, use the command

python manage.py loaddata users

Great, by now you should have some users in your database and you should be able to use our login API we just created. But what do we do with the token we get back?

Well, as mentioned before, this token should be added as a Authorization header in our calls to identify such user.

With JWT we will be able to authorize or prohibit certain calls for certain users, and or we will send different type of info, depending on what user is sending the call.

All of this decision making will be done by a different layer, which I call ‘security voters’ (hereditary from Symfony)

Here’s an example of what a voter would look like:

#security/voters.py
from apiapp.models import User


class AbstractVoter:

    request = None

    def __init__(self, request):
        self.request = request

    def is_logged_in(self):
        if isinstance(self.request.user, User):
            return True

        return False

    def is_superuser(self):
        if self.is_logged_in():
            return self.request.user.is_superuser

        return False


class UserVoter(AbstractVoter):

    def user_can_manage_me(self, user_inst: User):
        if self.is_logged_in():
            if self.is_superuser():
                return True
            if self.request.user == user_inst:
                return True

        return False

Now we can call this voter in our APIs.

#views/user.py
@api_view(['GET', 'POST'])
def api_admin_user_index(request):
    """
    get:
    List all users.
    post:
    Create new user.
    """
    voter = UserVoter(request)
    if not voter.is_superuser():
        return Response({'error': "User API is not allowed by non admin user"}, status=status.HTTP_403_FORBIDDEN)

    #...


@api_view(['GET', 'PUT', 'DELETE'])
def api_admin_user_detail(request, pk):
    """
    get:
    Detail one user.
    put:
    Update one user.
    delete:
    Delete one user.
    """
    voter = UserVoter(request)
    if not voter.is_superuser():
        return Response({'error': "User API is not allowed by non admin user"}, status=status.HTTP_403_FORBIDDEN)

    #...

It can also be useful for API calls that are not for superusers. Let’s create the route for a user detail.

#urls.py
urlpatterns = [
	#...
    url(r'^(?P[0-9]+)/$', user.api_user_detail, name='api_user_detail'),
]

And now in the view.
In the GET verb, Unless the user wants to see himself, it should be denied.

#views/user.py
@api_view(['GET', 'PUT'])
def api_user_detail(request, pk):
    """
    get:
    Detail one user.
    put:
    Update one user.
    """
    try:
        user_inst = User.objects.get(pk=pk)
    except User.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    voter = UserVoter(request)
    if not voter.user_can_manage_me(user_inst):
        return Response({'error': "User API is not allowed"}, status=status.HTTP_403_FORBIDDEN)

    if request.method == 'GET':
        serializer = UserSerializer(user_inst)
        return Response(serializer.data)

    elif request.method == 'PUT':
        data = JsonReader.read_body(request)
        if 'is_staff' in data:
            if not voter.is_superuser():
                return Response({'error': "Non admin cannot update admin attributes"}, status=status.HTTP_403_FORBIDDEN)
        serializer = UserSerializer(user_inst, data=data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Conclusion

On this post we’ve seen how to implement the Django’s REST Framework together with Django’s JWT library.

We’ve seen that JWT is a powerful and easy way to identify users that use our API.

If you have any questions or something is confusing, let me know and I’ll do my best to clarify.

I’ve also uploaded the code on github so you can clone the project and try it out for yourself.

git clone https://github.com/joeymasip/django-apiskeleton.git

I use it as a base for API projects, as I usually always need some User API to start with.

Happy coding 🙂

Docker for Django

This blog post is a small guide for getting started with your Django environment with Docker. Since I got very positive feedback from the blog post about Docker and Symfony4, I decided to do the same with Docker and Django.

In this example we’re going to work with Django 1.11 (LTS), Python3 and MySql 5.6

Before we start, you’ll need to install Docker in your machine. You can download it from the official website.

Once Docker is installed, I strongly recommend playing with the getting started guide. Here the guide for macs and here the guide for windows. However, if you’re lazy like me, just use this command to make sure it’s installed.

docker --version

Once all of that is out of the way, we can start with the Django project and the Docker environment that will run it.

We have two cases I’m going to tap on.

Case 1: I’m creating a Django project from scratch and I want to set up a development environment with Docker.

Step1: Clone the docker-django repository which has the docker configuration files.

git clone https://github.com/joeymasip/docker-django.git

Step2: Create the Django project.

First off, let’s start docker containters with the project we just downloaded.

#cd to the location where you cloned the project
cd ~/Development/docker-django
#start the containers
docker-compose up -d 

This command starts the containers. The parameter -d makes them run in the background. If you omit the -d you’ll see the log.

Docker should start building (if it’s the first time for these images) and running with the containers in the background.

If you now try to run

docker ps

in your console, you’ll see that MySql container is running, but Django’s is not. This is normal, as we haven’t installed Django yet in our project.

Now we’ll create the django project with the following command (replace project_name for your project name)

docker-compose run django django-admin.py startproject project_name .

Note: Do not forget the . in the end

Step3: Create the Django application.

Now we’ll create the django application with the following command (replace app_name for your app’s name)

docker-compose run django python manage.py startapp app_name

Now, try running the same

docker-compose up -d

Now, if you run the docker ps

docker ps

This time, Django’s container will have been started.

So right now, if you just open your browser and type http://127.0.0.1:8000/ you should see it working, so you’re already set up to develop!

First off though, let’s update settings.py so we can use our mysql container instead of Django’s sqlite.

Step4: Update settings.

First let’s add your app_name in the installed apps

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    
    'app_name',
]

Now let’s configure your settings.py so the database settings points to the database service from docker.

Change:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

To:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'docker_django_db',
        'USER': 'dbuser',
        'PASSWORD': 'dbpw',
        'HOST': 'mysql',
        'PORT': '3306',
        'TEST': {
            'NAME': 'docker_django_db_test',
        },
    }
}

Step5: Create the User model.

Under your app_name/models.py file, just create a User model that extends from Django’s Auth model.

#app_name/models.py
from django.db import models
from django.contrib.auth.models import AbstractUser


class User(AbstractUser):
    pass

Once this is done, just tell your project that we’ll be using our User model. This is best practices in case you ever need to make changes to the User model. So in your project_name settings.py, add this line:

AUTH_USER_MODEL = 'app_name.User'

Now our User is plugged in.

Step6: Run migrations.

To run migrations, we need to first enter the python django bash.

docker-compose exec django bash

Once in, we can make the migrations if it’s the first time we create the app, as we won’t have any.

python manage.py makemigrations

And also run them

python manage.py migrate

If you want to create an admin user to log in into Django’s admin panel,

python manage.py createsuperuser

That’s it!

Now just open your browser and type

http://127.0.0.1:8000/
http://127.0.0.1:8000/admin/

You’re already set up to develop, so happy coding with docker!

Case 2: I already have a Django project

Step1: Clone the docker-django repository which has the docker configuration files.

git clone https://github.com/joeymasip/docker-django.git

Step2: Move all files to your already created Django project.

1. The docker folder containing python + django and a MySQL container config for it.
2. The docker-compose.yml file
3. The .env file

Step3: Start the docker images inside your Django project folder.

cd into your Django project folder and type the following command

docker-compose up -d

This command starts the containers. The parameter -d makes them run in the background. If you omit the -d you’ll see the log.

Docker should start building (if it’s the first time for these images) and running with the containers in the background.

Step4: Update database settings.

So if you’re using the docker-compose.yml out of the box, your database name, user and pw need updating in your project’s settings.

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'docker_django_db',
        'USER': 'dbuser',
        'PASSWORD': 'dbpw',
        'HOST': 'mysql',
        'PORT': '3306',
        'TEST': {
            'NAME': 'docker_django_db_test',
        },
    }
}

Step5: Run migrations.

docker-compose exec django bash

Once in, we can run the migrations.

python manage.py migrate

Now, open a new chrome tab and type the following URL. The port is the one we set up in the docker-compose.yml

http://127.0.0.1:8000/
http://127.0.0.1:8000/admin/
http://127.0.0.1:8000/whatever-slug-you-want-from-your-project

You should see it working.

You’re already set up to develop, so happy coding with docker!

Translatable models for translatable website in Django

This post is about using model translations (without translation files) and showing some ways to send that translations to the front end where they can be rendered.

This post is not about installing modeltranslations in Django, there’s a comprehensive guide on how to do that here.

Study case: Translatable texts that need to be editable in the django admin backend, without translation files.

Once we’ve got the modeltranslations module installed, we need to create the model we want to translate.

I’ve created the model String, but you can call it whatever you want.

#your_app/models/string.py
from django.db import models

class String(models.Model):
    key = models.CharField(max_length=80)
    value = models.CharField(max_length=255, blank=True, null=True)

    def __str__(self):
        return self.key

Now we should register our model in the translation.py, and so we can specify what field(s) will be the translatable one.

#your_app/translation.py
class StringTranslationOptions(TranslationOptions):
    fields = ('value',)


translator.register(String, StringTranslationOptions)

In a database level, this model table called your_app_string will contain the translations for every key in a column for each language: value_en, value_es, …

If you have i18n patterns in your url settings, Django will automatically know what locale to serve you through it’s own middleware system. For more info on this, there’s a comprehensive guide here.

Otherwise, you can set the locale manually through the request, up to you. More info on this here.

Once Django already knows what’s the current locale, we just need to get the strings from the database. The modeltranslations module will automatically give us the value translated to the current locale.

Translating the front end

We are able to transfer the strings to the frontend templates through many ways. For instance, we could load the strings on every view before rendering the template. However, this is bit of a code smell, as code is too coupled and it’s not very mantainable. We can do better like so:

1. Using a Context Processor

#your_app/context_processors/trans.py
from your_app.models import String


def trans(request):
    trans = {}
    for string in String.objects.all():
        trans[string.key] = string.value

    return {'trans': trans}

Your’ll need to register the context processor in settings.py

#settings.py
TEMPLATES = [
    {	
    	'...',
        'OPTIONS': {
            'context_processors': [
            	'...',
                'your_app.context_processors.trans',
            ],
        },
    },
]

What this will do is, after every render function in our views, it’ll include the array of all translations. That’s why you don’t need to load any extra file in the template.

Now in the front end, you can use it like so:

{{ trans.hello_world }}

{{ trans.hello_world_wrong_key }}

This is similar to loading the strings just before every render in the views, however we’ve removed the coupling by centralizing the code in one place.

The main problem though is that if there’s a key that’s not included, Django will just ignore that key, not giving us any feedback if a translation is missing. This is annoying if we’re developing, as we won’t catch missing translations for certain keys.

2. Using a filter (templatetag)

#your_app/templatetags/trans.py
@register.filter(name='trans')
def trans(field):

    translations = {}
    for string in String.objects.all():
        translations[string.key] = string.value

    try:
        trans = translations[field.__str__()]
    except KeyError:
        trans = '[*' + field + '*]'

    return trans

Now in the front end, you can use it like so. This time, different from context_processor, we won’t need to register it in the settings.py but we do need to load it in the template.

{% load trans %}

{{ 'hello_world' | trans }}

{{ 'hello_world_wrong_key' | trans }}

This one gives feedback if the key doesn’t exist, but as you can see, it loads all the strings every time it finds a key to be rendered. This is very inefficient!

3. Using a simple tag (templatetag)

Very similar to the filter, but there’s a suttle difference, this one can load the context.

#your_app/templatetags/trans.py
@register.simple_tag(name='trans', takes_context=True)
def trans(context, field):
    translations = context.request.session.get('translations')

    if not translations:
        translations = {}
        for string in String.objects.all():
            translations[string.key] = string.value
        context.request.session['translations'] = translations

    try:
        trans = translations[field.__str__()]
    except KeyError:
        trans = '[*' + field + '*]'

    return trans

Loading the context allows us to save data in memory, so it doesn’t have to load the translations from the database every single time there’s a string to be translated.

In the front end we still need to load the tag.

{% load trans %}

{% trans 'hello_world' %}

{% trans 'hello_world_wrong_key' %}

This one is not only clean and efficient, it also gives feedback if a translation key doesn’t exist in the database. Furthermore, since we now have the context (it can’t be loaded with a filter), the translations will be loaded from the ddbb just once (when it tries to translate the first key). When there’s a second key to be translated, it’ll get the array of translations from the context and just give the value translated, or the key if it can’t find it.

So there you have it, three different ways to present the translations in the front end with a custom model translation.

Other useful resources:

Django translations
Django model translations

How to add attributes to form widgets in Django templates

This blog post is about templates in Django, specifically filters or pipes, whatever you call them – in Symfony we used to call them Twig Filters – and how to create them.

Let’s put I have to add a class “form-control” from bootstrap, and another class “select2” from the select2 library into a form widget. Since the form widget is rendered in the backend, it’s not clear how to add attributes before it’s rendered. In this example I had to create a pipe to add attributes to the form widget.

In Symfony’s Twig, it’s pretty simple to add attributes, like so:

{{ form_widget(form.start, {'attr': {'class': 'form-control select2'}} ) }}

In Django’s template we’ll need a bit of a workaround and create the pipe.

Creating the filter file, add_attr.py

from django import template
register = template.Library()


@register.filter(name='add_attr')
def add_attr(field, css):
    attrs = {}
    definition = css.split(',')

    for d in definition:
        if ':' not in d:
            attrs['class'] = d
        else:
            key, val = d.split(':')
            attrs[key] = val

    return field.as_widget(attrs=attrs)

This file should live in a folder called templatetags in your Django app.

Using the pipe in the template

To use it in a Django template, firstly add this widget in the top of the template, similar to the load static widget.

{% load add_attr %}

And now you can use it on any form widget as a function, like so

{{ form.start|add_attr:"class:form-control select2" }}

And that’s it! Your widget will have the classes form-control and select2.

Hope this helps. Alternatively, you can use a django library called django-widget-tweaks that helps you to do this, find the info here.

Happy coding! 🙂

From Symfony to Django

Lately I’ve been playing with Django (in a real project, not tutorials) and all I have to say is… Oh My God!! I’m so impressed with Django’s clarity, speed of development and “cleanliness”, that I’m even planning for it to become my main ‘go-to’ framework! Don’t get me wrong, I’ve been working with Symfony for a few years and it’s still going to be the main framework I use for PHP projects, and probably the most comfortable I’ll feel working with for a while, but right now Django is like a new toy and I’m really excited to share this with you!

Furthermore, Django seems to perform far better in performance tests (less CPU resources, faster response…) than Symfony. Check here for more info on this.

This blog post is not a ‘Django getting started guide’, there’s plenty of those online already. Please visit Django’s oficial page for more info on this.

This blog post is for Symfony developers who wish to try Django out and are unaware of the Django project structure, file structure, etc. I’m going to go through the basics so you get a feel of what it’s like to code in Django and hopefully you feel comfortable enough to give it a go 🙂

Routing

Routing files are the ones which have mapped the routes for our application. Symfony and Django alike will try to match one of these routes to handle it.

Symfony annotations in controller

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;

/**
 * Displays a form to edit an existing customer entity.
 *
 * @Route("/{id}/edit", name="app_customer_edit")
 */

Symfony YAML: routing.yml

app_customer_edit:
    path:     /{id}/edit
    defaults: { _controller: AppBundle:Customer:edit }

In Symfony you can include specific routing of a bundle like so:

app:
    resource:   "@AppBundle/Resources/config/routing.yml"
    prefix:     /

Django: urls.py

Django routing in a config file called urls.py

from app.views import customer

urlpatterns = [
    url(r'^(?P\d+)/edit$', customer.update_customer, name='customer_edit'),
]

You can also include specific routing in Django

urlpatterns = [
    url(r'^customer/', include('app.urls.customer')),
]

Controllers

Once matched a certain route, Controllers are the ones that receive the request and return a response, with whatever logic you may need in between, using services and so on.

Symfony: CustomerController.php

A Controller in Symfony, which has the action that will fire if the route matches

/**
 * Customer controller.
 *
 * @Route("customer")
 */
class CustomerController extends CoreController
{

    /**
     * Displays a form to edit an existing customer entity.
     *
     * @Route("/{id}/edit", name="app_customer_edit")
     */
    public function editAction(Request $request, Customer $customer)
    {
    
        //custom logic
    
        return $this->render('AppBundle:Customer:edit.html.twig', array(
                'param1' => $param1,
                //...
            ));
    }

}

Django: views.py

In Django, the Controller is called View, so the views.py will have the function we specified in the urls.py

def update_customer(request, pk):

    //custom logic

    return render(request, 'customer/edit.html', {
        'param1': param1,
    })

Template rendering

The templating engine is very very similar, so you won’t have any trouble adapting to the new one.

Symfony: edit.html.twig

Twig is the template engine in Symfony. The Resources/views folder in Symfony which contains all the twigs.

For instance, to create an anchor for a Symfony route in twig:

< a href="{{ path('app_customer_edit', { 'id': customer.id }) }}"> Edit the customer < /a>

Django: edit.html

Django uses it’s own templating engine, and the folder containing all the html files is called templates. Code looks very similar.

< a href="{% url "customer_edit" customer.id %}"> Edit the Customer < /a>

Static files

Symfony: public/

The Resources/public folder in Symfony which contains all the js/css files for later deployment.

To load some of those files in the template in Twig

< link rel="stylesheet" href="{{ asset('bundles/app/vendor/bootstrap/css/bootstrap.min.css') }}">
< script type="text/javascript" src="{{ asset('bundles/app/vendor/bootstrap/js/bootstrap.min.js') }}">

Django: static/

In Django, these files are gathered in a folder called static.

{% load static %}

< link rel="stylesheet" href="{% static "vendor/bootstrap/dist/css/bootstrap.min.css" %}">
< script src="{% static "vendor/bootstrap/dist/js/bootstrap.min.js" %}">

Entities

Entities are mapped models that translate into database tables. The idea is to abstract the database completely, so you can either use Mysql, Mongo, PostgreSql,… totally independent from the code.

Symfony: Customer.php

Using Doctrine, the Customer entity under the Entity folder in Symfony would look something like this:

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity()
 * @ORM\Table(name="app_customer")
 */
class Customer
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=50, nullable=true)
     */
    protected $name;

    /**
     * @var string
     *
     * @ORM\Column(name="surname", type="string", length=80, nullable=true)
     */
    protected $surname;

    /**
     * @var Address
     *
     * @ORM\OneToOne(targetEntity="Address", cascade={"persist"})
     * @ORM\JoinColumn(nullable=true)
     */
    protected $address;


    //more attributes...


	/**
     * @return string
     */
    public function __toString()
    {
        return trim((string) $this->getName(). ' '.  $this->getSurname());
    }
    
    /**
     * Get id
     *
     * @return integer
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set name
     *
     * @param string $name
     *
     * @return Customer
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Set surname
     *
     * @param string $surname
     *
     * @return Customer
     */
    public function setSurname($surname)
    {
        $this->surname = $surname;

        return $this;
    }

    /**
     * Get surname
     *
     * @return string
     */
    public function getSurname()
    {
        return $this->surname;
    }

    /**
     * Set address
     *
     * @param \AppBundle\Entity\Address $address
     *
     * @return Customer
     */
    public function setAddress(\AppBundle\Entity\Address $address = null)
    {
        $this->address = $address;

        return $this;
    }

    /**
     * Get address
     *
     * @return \AppBundle\Entity\Address
     */
    public function getAddress()
    {
        return $this->address;
    }

    //more getters and setters...

Django: models.py

In Django, much slimmer. No need of any getters and setters, as it has it’s own model API that handles that.

from django.db import models
from app.models.address import Address


class Customer(models.Model):
    name = models.CharField('Name', max_length=50)
    surnames = models.CharField('Surnames', max_length=80)
    address = models.OneToOneField(
        Address,
        on_delete=models.CASCADE
    )

    def __str__(self):
    	return self.name + ' ' + self.surnames

Forms

Symfony: FormType.php

In Symfony we’ll use a FormType, which usually extends from AbstractType. We also need to tell Symfony what class it’s mapping through the configureOptions method.

use Symfony\Component\Form\AbstractType;

class CustomerType extends AbstractType
{
    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name', null, [
                'label' => 'Nom',
            ])
            ->add('items', CollectionType::class, [
                'entry_type' => CustomerItemType::class,
                'allow_add'    => true,
                'allow_delete' => true,
                'by_reference' => false
            ])
            ...
         ;
    }

    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => Customer::class,
            'cascade_validation' => true,
        ));
    }

    /**
     * {@inheritdoc}
     */
    public function getBlockPrefix()
    {
        return 'appbundle_customer';
    }
}

To build the form from the controller we would use something like this

$form = $this->createForm(CustomerType::class, $customer);

This basically calls the form builder service and builds an CustomerType form with the $customer instance.

Django: forms.py

In Django it’s much simpler! We also need to tell Django what model class it’s mapping through the Meta assignment.

from django import forms

class CustomerForm(forms.ModelForm):

    class Meta:
        model = Customer
        exclude = ('',)

To build the form from the view we would use something like this

form_customer = CustomerForm(request.POST, instance=customer_inst)

Same as Symfony, this will create the form with the customer_inst instance.

ORM – Object Relational Manager

The ORM allows us to access objects from the database in a ‘object manner’, so we don’t need to worry how to access that DDBB, it’s totally independent.

Symfony: Doctrine

In Symfony we use Doctrine, so let’s find an Customer given a request id

public function editAction(Request $request)
{
	$em = $this->getDoctrine()->getManager();
	$customer = $em->getRepository('AppBundle:Customer')->find($request->get('id'));
	$customers = $em->getRepository('AppBundle:Customer')->findAll();
	$address = $customer->getAddress();

	// Find an customer depending on a address
	$customerInst = $em->getRepository('AppBundle:Customer')->findOneByAddress($addressInst);
}

Django: API

In Django it uses it’s own API.

def update_customer(request, pk):
    customer = Customer.objects.get(pk=your_object_id)
    customers = Customer.objects.all()
    address = customer.address

    # Find an customer depending on a address
    customer_inst = Customer.objects.get(address=address_inst)

Summary

I’m not saying Django is better than Symfony or the other way round. Each and every framework/technology has it’s place, and as developers, I believe we should be open minded and have as many tools as possible under the belt. So I strongly recommend every Symfony dev to try Django out if you haven’t done yet. I assure you that you’ll love everything about it, even the great admin it gives you out of the box 🙂