Django style guide

Models

  • Always include created_at fields (and maybe updated_at) on models.

  • Model attributes shouldn’t perform DB queries.

Services and selectors

See https://github.com/HackSoftware/Django-Styleguide#cookie-cutter and https://www.youtube.com/watch?v=yG3ZdxBb1oo .

Django model properties

When writing functions on Django models, if it executes a query, it should not be a property. The only properties on a model should be those that don’t need any additional work on the database, otherwise it’s very unclear if you’re accidentally producing an n+1 problem.

Note: This extends to __str__ functions on objects. It shouldn’t reference anything outside of the model’s immediate properties.

Using external APIs

If you import a package for an external API, it often gives you a function you have to call to get a client object. You’ll then often end up using it like:

client = module.Client(auth_settings=settings.CLIENT_KEY)
result = client.thing.get_other_thing(args)
# make result into a useful format for us...

It’s best to encapsulate all of this into a simple function. Place this code into in core.apis.<service_name>.

The same applies if you make a simple in-house API using explicit HTTP requests.

Writing a simple module that encapsulates the client and provides an application-specific layer keeps code readable and prevents the implementation details of the integration from leaking into other bits of the code. It’s also much easier to mock out in tests.

Exceptions

As little code as possible should be within a try: block - it should typically be just one statement. Otherwise it’s unclear to a future programmer what things can fail and in which ways.

Import modules, not objects or functions

e.g.

from . import enums
from core.apis import slack

slack.send_message()
enums.AddressType.HOME

not

from .enums import AddressType
from core.apis.slack import send_message

send_message()
AddressType.HOME

When importing something from one Django app into another, use as to prefix the modules:

from hub.crm import services as crm_services

This rule is not mandatory but it makes code a lot clearer if it’s generally used, especially with cross-app imports.

Naming conventions

Follow standard Python naming conventions.

Capitalisation

If you have an acronym that would normally be all-caps in a class name, keep it in all caps. So DSRApplication not DsrApplication, or ExternalAPI rather than ExternalApi.

For variable names, keep them all-lowercase.

Class suffixes

Given we import modules, not objects, there’s no need to suffix view/form/serializer classes names with View/Form/Serializer.

Within a calling module, it’s nicer to have:

from django.views import generic
from . import forms

class SetPassword(generic.FormView):
    form_class = forms.NewPassword

rather than:

from django.views import generic
from . import forms

class SetPassword(generic.FormView):
    form_class = forms.NewPasswordForm

Timestamp suffixes

Model fields for timestamps should be suffixed _at. e.g. updated_at.

Use enums for enumerated values

Don’t re-use string literals in more than one place. Code will fail loudly instead of silently if you have a typo if you’re using an enum.