Django / Python Code Standards ****************************** .. highlight:: python - Click here for :doc:`dev-standards` - Click here for :doc:`dev-standards-doc` - Click here for :doc:`dev-standards-html` - Click here for :doc:`dev-standards-js` - Click here for :doc:`dev-standards-security` - Click here for :doc:`dev-standards-sys` - Click here for :doc:`dev-standards-tech` - Click here for :doc:`dev-standards-ui` These documents are **very** good starting points: - `Django models, encapsulation and data integrity`_ - `Open Sourcing a Python Project the Right Way`_ 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: 1. Separate subject from body with a blank line 2. Limit the subject line to 50 characters 3. Capitalize the subject line 4. Do not end the subject line with a period 5. Use the imperative mood in the subject line 6. Wrap the body at 72 characters 7. 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 .. _model_set_deleted: Delete ------ .. warning:: We don't delete data (unless there is a specific requirement for it). We have a :ref:`timed_create_modify_delete_model` which sets up the fields. .. tip:: To mark an object as deleted in a Django view, see :ref:`update_view_not_delete_view` 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: 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 :ref:`docker-system-prune`. 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. .. _`Cameron Maske's`: https://twitter.com/cameronmaske .. _`Changing the Metrics Conversation`: https://www.thoughtworks.com/insights/blog/changing-metrics-conversation .. _`Coding Conventions`: https://django-party-pack.readthedocs.org/en/latest/conventions.html#using-the-url-function .. _`Django and the testing pyramid - DjangoCon Europe 2017`: https://speakerdeck.com/aaronbassett/django-and-the-testing-pyramid-djangocon-europe-2017 .. _`Django models, encapsulation and data integrity`: http://www.dabapps.com/blog/django-models-and-encapsulation/ .. _`Factory Boy`: https://github.com/rbarrois/factory_boy .. _`Open Sourcing a Python Project the Right Way`: http://www.jeffknupp.com/blog/2013/08/16/open-sourcing-a-python-project-the-right-way/ .. _`So many different ways to mock an HTTP request in Python`: https://twitter.com/cameronmaske/status/1034355679274909696 .. _`The many flaws of test coverage`: http://greglturnquist.com/2016/12/the-many-flaws-of-test-coverage.html .. _responses: https://github.com/getsentry/responses