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 🙂