Django / Python Code Standards
Click here for Standards
Click here for Documentation
Click here for HTML Code Standards
Click here for Ember / JavaScript Code Standards
Click here for Security Standards - Development
Click here for Sys-Admin and Configuration Management
Click here for Technology Standards
Click here for UI Standards
These documents are very good starting points:
Code
Important
We are going to start by aiming for 70% test coverage for an app and 50% for a project. Useful to bear these in mind Changing the Metrics Conversation and The many flaws of test coverage
Add the following to requirements/local.txt
:
black
pytest-cov
pytest-flakes
pytest-pep8
white
Add configuration for flakes e.g in setup.cfg
:
[tool:pytest]
addopts= --ds=settings.dev_test --cov-report html --reuse-db --fail-on-template-vars
norecursedirs = .git venv-* src node_modules
# 1. migrations always import models
# 2. custom settings files e.g. 'dev_patrick.py' do 'from .base import *'
# 3. 'test_view_perm.py' py.test fixtures conflict with pyflakes
flakes-ignore =
block/migrations/* UnusedImport
example_block/dev_*.py ImportStarUsed
test_view_perm.py UnusedImport RedefinedWhileUnused
Important
Change block
to the correct path for your app or project.
Warning
DJANGO_SETTINGS_MODULE
was being ignored. To fix, add
addopts= --ds=settings.dev_test --cov-report html...
to
addopts
and remove DJANGO_SETTINGS_MODULE
.
To format the code:
white path/to/the/module.py
Important
Create a separate commit for code formatting.
To check the code:
py.test --flakes
py.test --pep8
Release
(not sure if this project is any good). If it is… check with https://github.com/mgedmin/check-manifest
GIT Commit Messages
From http://chris.beams.io/posts/git-commit/
The seven rules of a great git commit message:
Separate subject from body with a blank line
Limit the subject line to 50 characters
Capitalize the subject line
Do not end the subject line with a period
Use the imperative mood in the subject line
Wrap the body at 72 characters
Use the body to explain what and why vs. how
Tip
And my favourite piece of advice:
A properly formed git commit subject line should always be able to complete the following sentence:
If applied, this commit will your subject line here
Model
The order of model inner classes and standard methods should be as follows (they are not all required):
All database fields
Custom manager attributes
class Meta
def __unicode__()
def __str__()
def save()
def get_absolute_url()
Any custom methods
Delete
Warning
We don’t delete data (unless there is a specific requirement for it).
We have a TimedCreateModifyDeleteModel which sets up the fields.
Tip
To mark an object as deleted in a Django view, see UpdateView not DeleteView
File and Image Field
For a FileField
or ImageField
, then set the upload_to
path to
reflect the app and model name e.g. for the document
field in the
Attachment
model in the mail
app:
document = models.FileField(upload_to='mail/attachment/')
Testing
Continuous Integration
Create a .gitlab-ci.yml
file in the root of the project e.g:
https://gitlab.com/kb/contact/blob/master/.gitlab-ci.yml
Create a requirements/ci.txt
file e.g:
https://gitlab.com/kb/contact/blob/master/requirements/ci.txt
Tip
To make access easier for public repositories, use the GIT https
URL rather than a path to the folder e.g:
-e git+https://gitlab.com/kb/login.git#egg=login
Check the project setup.cfg
file to make sure src
is included in the
norecursedirs
section e.g:
https://gitlab.com/kb/contact/blob/master/setup.cfg:
norecursedirs = .git angular venv-* src
# or
norecursedirs = .git venv-* src node_modules
# or
norecursedirs = .git venv-* src
Commit and push to GitLab.
In GitLab logged in as the project owner:
Select: Settings | General and expand the Permissions section and set: Repository, Pipelines to
Only Project Members
.Select: Settings, CI/CD Pipelines and expand the Runners section and: Disable shared runners for this project.
Still in Settings, CI/CD Piplines expand the “General Pipeline settings and set Test coverage parsing to
\d+\%\s*$
(copy the pytest-cov (Python) example).
For email notifications for the project, Settings, Integrations, Pipelines emails. Tick Active, add Recipients and Add pusher
Note
Test settings on Builds emails threw an error last time I tried, but the email still sends OK.
Pipelines… create…
Tip
To cleanup Docker containers, see Docker.
Factories
Model factories should create the minimum required to construct a valid object e.g. a product will probably need to create a product category, but a contact will not need to fill in the date of birth.
Note
I am not 100% sure about this… but I am sure a factory which does more than it needs to will make it feel like magic is going on and cause confusion.
Mock
We use the unittest.mock
module:
https://docs.python.org/3/library/unittest.mock.html
e.g:
import attr
from unittest.mock import patch
@attr.s
class StripeCustomer:
id = attr.ib()
@patch("stripe.Charge.create")
@patch("stripe.Customer.create", return_value=StripeCustomer(id="xyz"))
@pytest.mark.django_db
def test_payment(mock_charge, mock_customer, client):
_check_success(client)
assert mock_charge.called is True
assert mock_customer.called is True
Tip
We also use @mock.patch.multiple
.
Following Cameron Maske’s tweet ref So many different ways to mock an HTTP request in Python, I am now testing the responses library for mocking HTTP requests…
Note
The following (ref httmock
) is an old note…
When we next do HTTP/API testing, then checkout https://github.com/patrys/httmock
It was recomended in this talk: Django and the testing pyramid - DjangoCon Europe 2017.
Model
Create a DjangoModelFactory
for the model using Factory Boy and test the
following (these are a common source of hard to diagnose issues):
ordering
str
To mock a model manager:
from unittest import mock
with mock.patch('editor.models.CatArticle.objects') as m:
m.return_value = {}
URL
From Coding Conventions:
url(regex=r'^$',
view=views.poll_list,
name='poll_list',
),
… the preferred and wonderfully explicit Jacob Kaplan-Moss / Frank Wiles pattern…
Note
Probably best to use the actual view class rather than just the name,
using view='polls.views.standard.poll_list',
, makes it harder to
debug on errors.