Search code examples
djangointernationalizationdjango-admin

How to make Django admin honor grammatical cases in languages like Polish?


Django allows overwriting verbose_name and verbose_name_plural of a Model, which allows us to specify correct plural forms of Model names for non-English languages (for example Kandydaci as a plural of Kandydat instead of the default Kandydats which looks weird in Polish).

However, this is far from enough for languages with grammatical cases. For example, Django Admin gleefully displays us something like that:

enter image description here

(Where Zaznacz kandydat do zmiany stands for Select kandydat to change - Kandydat is a name of a Model)

This is incorrect. In this sentence the model name should have been displayed in accusative case, which is kandydata. However, I cannot simply specify that verbose_name of this Model is Kandydata, since would also affect all places where nominative case is expected instead - I've found one such place so far, and this is the heading of the table column:

enter image description here

This is incorrenct since the table heading should be called Kandydat not Kandydata.

How to fix this?


Solution

  • The string 'Select <verbose_name> to change' is declared inside the admin's main view

    self.title = title % force_text(self.opts.verbose_name)

    as a {{ title }} template variable

    {% block content_title %}{% if title %}<h1>{{ title }}</h1>{% endif %}{% endblock %}

    So, touching the built-in view is out of question. But there is another way of doing it! You see, Django Admin templates are full of {% block %}s acting as placeholders in order to override them. Read more at the official docs about overriding Admin templates.

    So, to your question.

    1. Under your project's root directory create a directory templates (if it doesn't exists already).

    2. Under my_project/templates/ create another one, admin.

    3. Under my_project/templates/admin/ create a file change_list.html.

    4. Inside my_project/templates/admin/change_list.html put these:

       {% extends 'admin/change_list.html' %}
      
       {% load myutils_filters %}
      
       {% comment %}
       You have to create an app that will hold all common tags/filters
       that are re-usable across your entire project.
       The model class itself is passed as cl.model
       {% endcomment %}
      
       {% block content_title %}{{ block.super|to_accusative:cl.model }}{% endblock %}
      
    5. Inside your myutils/templatetags/myutils_filters.py file put these:

       from django import template
       from django.utils.html import format_html
       from django.utils.encoding import force_text
      
       register = template.Library()
      
       @register.filter()
       def to_accusative(value, model):
           verbose_name = model._meta.verbose_name  # declared in Meta
           new_name = force_text(model.accusative_case())  # a custom class method (lives in your Model)
           return format_html(value.replace(verbose_name, new_name))
      
    6. Finally, under your app's models.py, under each model class define a classmethod method like this:

       from django.db import models
       from django.utils.translation import ugettext_lazy as _
      
       class MyModel(models.Model):
           # model fields here
      
           def __str__():
               return self.a_field
      
           class Meta:
               verbose_name = 'verbose name'
               verbose_name_plural = 'verbose name plural'
               # accusative_case = 'accusative name case' # BAD. Raises error. Implement a class method intead ;)
      
           @classmethod
           def accusative_case(cls):
               # You'll define the Polish translation as soon as you run makemessages
               return _('Name in english of the accusative case')
      
    7. Run manage.py makemessages, manage.py compilemessages, reload your browser and voila!

    Note: The above may look a little hackish but it works brilliant. Tested locally and works.