How much do you care to write good Django code?
I’ve been having often conversations with fellow Djangonauts. I always ask one little question during the talk…
Have you ever read the Django’s “Writing code” article ?
I’ve been doing Django hire interviews lately. Before I criticize or start review the code, I always ask the question… Unfortunately, most of the developers are unaware of that document. They don’t even know if It exists.
If you navigate through the Contributing to Django page, you’ll see the Writing code section under the list items. Probably, most users never contrive to look at the docs unless they’re planning to contribute source code. Rules of writing Django code is already out there…
Coding style
If you click the link, you’ll navigate to coding style page in Django documentation. I’m skipping Python style part since you need to know that already!
Sorting imports
Document tells us to sort our import
statements with isort
.
$ python -m pip install isort
$ isort -rc .
More detailed information can ben found here. You can also integrate this with your code editing tool/ide etc. I mostly use this configuration:
[settings]
line_length = 60
multi_line_output = 3
use_parentheses = true
include_trailing_comma = true
quiet = true
force_grid_wrap = 0
known_django = django
sections = FUTURE,STDLIB,DJANGO,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
Small example after sort process:
import logging
from collections import Counter
from django.db import models, router, transaction
from django.db.models.deletion import Collector
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from ..utils import console
from .signals import post_undelete, pre_undelete
When you integrate isort
to your editor or maybe a git-hook, you don’t need
to worry about the order of imports anymore…
import logging
from django.contrib.auth.models import (
AbstractBaseUser,
BaseUserManager,
PermissionsMixin,
)
from django.db import models
from django.utils.translation import ugettext_lazy as _
from baseapp.utils import save_file as custom_save_file
Also, sorting imports are explained very well in the Django docs:
# future
from __future__ import unicode_literals
# standard library
import json
from itertools import chain
# third-party
import bcrypt
# Django
from django.http import Http404
from django.http.response import (
Http404, HttpResponse, HttpResponseNotAllowed, StreamingHttpResponse,
cookie,
)
# local Django
from .models import LogEntry
# try/except
try:
import yaml
except ImportError:
yaml = None
CONSTANT = 'foo'
class Example:
# ...
What about models ?
Django model, represents a table in database and single entity of data. That’s
why model name should be singular, not plural! If you want to store blog
posts in a database, you must name It to Post
not . Also, model name
is a Python Class. You must follow the naming convention of Python which is
Posts
InitialCaps
Field names should be singular except ManyToMany
or similar field types. Naming
should be snake_case
. There are good examples, DOes, DON’Ts in the
docs page. Here is a good one:
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=20)
last_name = models.CharField(max_length=40)
I’ve seen two most common behavior about relation models such as ForeignKey
and MonyToMany
fields.
Please use to='ModelName'
convention
Fields takes kwargs
and args
. Most of the candidates did the same writing:
from django.db import models
from django.utils.translation import ugettext_lazy as _
from .models import Category
class Post(models.Model):
category = models.ForeignKey(
Category,
on_delete=models.CASCADE,
related_name='posts',
verbose_name=_('category'),
)
Please define relation fields with using to
keyword argument:
from django.db import models
from django.utils.translation import ugettext_lazy as _
class Post(models.Model):
category = models.ForeignKey(
to='Category',
on_delete=models.CASCADE,
related_name='posts',
verbose_name=_('category'),
)
This helps in two ways:
- You are safe from circular import situation. You are free to import any model in anywhere…
- This is more pythonic and readable… We define all others such as
on_delete
,related_name
why not showing which model is targeted ?
The order of model inner classes
Django docs tell:
- All database fields
- Custom manager attributes
class Meta
def __str__()
def save()
def get_absolute_url()
- Any custom methods
Please follow the convention. Again, you are not writing code to contribute Django. You are writing code for your self, your company, your family, your anything… If there are some conventions, let’s follow them… Why ?
Convention over configuration
I heard this term for the first time from Ruby on Rails community. Let’s
keep the conventions also in choices
fields too:
If choices is defined for a given model field, define each choice as a list of tuples, with an all-uppercase name as a class attribute on the model
Example:
from django.db import models
from django.utils.translation import ugettext_lazy as _
class Post(models.Model):
STATUS_OFFLINE = 0
STATUS_ONLINE = 1
STATUS_DELETED = 2
STATUS_DRAFT = 3
STATUS_CHOICES = [
(STATUS_OFFLINE, _('Offline')),
(STATUS_ONLINE, _('Online')),
(STATUS_DELETED, _('Deleted')),
(STATUS_DRAFT, _('Draft')),
]
Document also covers;
- Template style
- View style
- Use of
django.conf.settings
and few other miscellaneous information, please read It when you need It.