50 tips to maintain a Django reusable app

50 tips to maintain a Django reusable app

Published Jan. 2, 2018 in Deployment, Development, Tests and quality, Documentation, Web - Last update on Jan. 2, 2018.

I love Django, not just because it on top of Python, but more because it keeps its philosophy: All Zen of Python is respected with batteries included. The Ponies' framework has basically most tools and mechanisms a WebDev would want to have as such authentication, ORM multi-DB or template engine. But where Django becomes definitively great is integration, it has a large ecosystem of reusable apps which are quite easy to install.

My contribution to this ecosystem is wide, I maintain and use a lot of them so I decided to write all the things I think useful to create a Django reusable app:

1. Do not re-invent the wheel

Before create a new app there are 3 mandatory steps: Google, Pypi, GitHub and look at Django-Packages is also a great idea. You may find the project that mades exactly what you want without touch your IDE. In an ideal world, but in fact you'll find:

  • The great project with last updated in 2009
  • The great project without tests
  • The great project without your Django/Python compatibility
  • The great project not registered on Pypi

All these cases could be a great 1st new: someone already thought about your problem. But maybe the project isn't maintened anymore, no probem it is less work than create from scratch.

2. Use Django mechanisms

As a framework, Django has a lot of built-in features: Database ORM, cache, storage, i18n, for don't give too much. All these parts are greatly made and improved since more than 5 years. They are designed so as to be modular, e.g django.contrib.auth lets you choose what is your User model. Like the previous tip, there are often great code already written in Django's source, Do not create yourself a slugify function or a message system, they are already here, just customize. That's how you'll respect your deadlines.

3. Create a Python-only layer

Your application may have a first goal which doesn't need Django. For example, I created a package named django-web-rich-object, it acts a bit like Facebook Debbuger. This app parses URLs and stores their data in database using Django ORM, but the parsing layer is part of another package: web-rich-object. It's better to code free softwares like this because the bundle will be only use by Django users and the parsing part could be used by any Pythoners, using "Vanilla", Flask or GTK. And I don't think a Plone developer would want to hack django-x-project...

4. Write in Python 3

Since 2017 I started to code only with Python 3, it is obviously the future (and the present). If you write only for Python 2 you are already creating a technical debt, so the best reflex is code for both. To help everyone, Django has a built-in six module: django.utils.six, with that, forget questions like: Should I import BytesIO or StringIO, from io or from StringIO, just import from one of both from Django. And it is just one example, like the original six module, this one can save your day in many cases.

5. Make it compatible with last Django LTS

Each 4 minor version of Django is a Long-Time Support: 1.4, 1.8 and I hope 2.0. To actually support 1.4 is crazy and I suggest from 1.8 to Django's master. By copy/paste lines from others project you can easily support a lot of versions or if you're lazy there is django-compatForward and backwards compatibility layer for Django 1.4, 1.7, 1.8, 1.9, and 1.10

6. Do not write to much in __init__.py files

For me, to write project's logic in __init__.py is in domain of satanism or dark path, as you want. It should only have meta informations (module's doc, version, author, etc) and why not imports to ease developer work.

7. Create your own settings file

All parameters related to your application should be refecenred in the app's settings.py (or whatever.py). This file could look like this:

from django.conf import settings

#: This is a doc about the setting below.
PARAM1 = getattr(settings, 'APP_PARAM1', None)

#: These comments really act as docstring.
PARAM2 = getattr(settings, 'APP_PARAM2', None)

It has several advantages:

  • You can set default values without pollute the code using this one
  • All constants are stored in one module (DRY)
  • You can document all your constant with comments

8. Let the possibility to by-pass behavior

Open source projects have to be though as agnostic as possible. An application has a primary task but its author should let users all (best) possibility to integrate it. It is one keystone of most opensource projects:

  • OpenStack lets you choose the backends you want for compute, volume, network, etc...
  • Django lets you choose your database, storage, email server, etc...

Simple users can have a variety of drivers/backends and developers could create their own. Or in a more explcit case, Django has its authentication mechanism and let you choose the model defined as the User one. 

9. Write class-based views

I am one of those who ever used function views until I understood Django's ModelAdmin or REST Framework's ViewSets are Class-Based Views. In terms of reusability, classes are without doubt the best choice.

The problem of functions views, you can't easily make them reusable: it's just a function. With a class you can simply use heritage: You write a class-based view and another developer can re-use it.

Django has a lot of abstract and mixin' view classes, this allows, if you know them, to create easily complex behavior with pagination, form handling, etc.

10. Don't customize to much your web UI

Your UI design is bad

If your application needs a web interface, don't customize it too much. Personally I apply the following method:

  • Administration tasks are made in Django's Admin Interface. Yes this brillant CRUD is already here
  • If my app could need some basic page, then I create a simple templates/myapp/ folder and a base.html file inside. This file will be parent of the other ones and contains inline CSS with the basic Django's "It's worked" theme.

I know that many professional of frontend won't like my WebUI, so I just let them a functional ugly one and they'll be able to imagine a new one. I think my UI as a an example.

11. Provide a REST API

For me, it is more important than WebUI. With Tastypie or REST Framework but not with hands. A REST API is the first step to a modern JS framework, an external programming, a command-line tool, or microservices infrastructures.

Create only tools useful to your app or for a basic usage. For Django REST, developers may want to customize authentication with its parameters. Like class-based views, everything are classes so there are only advantages.

12. Store static files and templates in sub-directories named with app name

Do not store your template files directly in the templates folder, always create subdirectory. Why ? because if someone has the same idea than you, it could have some files conflicts, and the template from the first declared app (in INSTALLED_APPS) will be chosen. The same for static files. Your app tree should look like:

$ tree myapp/
myapp/
├── static
│   └── myapp
└── templates
    └── myapp

13. Write great template files

If you have many templates files, factorize as much as possible your code. Firstly you'll apply DRY method and won't keep useless code. Secondly you'll allow user to override a part easily.

As I wrote before, if they want a clean integration, users will never use entirely your UI. But with some tricks, you'll avoid them to re-write every parts of your templates. The most important things to understand to greatly decouple templates are:

extends tag

I generaly use it like this {% extends "myapp/base.html" %} and think base.html will never be used by a developer. He/She'll want to customize everything, so I just put a simple theme inspired by the "Welcome to Django" page.

Just think to make your base.html as smart as possible, you must allow to include extra statics, headers or else.

include tag

The easiest way to make an inclusion with a little context control.

Custom tags

The best way to handle code blocks. It has the same usage than include but as tags are Python functions you can do anything you want. To make a difference with templates used directly, I prefix templates used by tags by a _, for example _posts.html.

14. makemigration only for new releases

Do not create too much migrations files, only one per release is my rule. 1st thing, I never make (also temporary) migration for non-database data, e.g. I changed the verbose_name of help_text of a field. 2nd, While I'm not commiting to master I avoid to create migrations, If I must I'll do as following:

  1. I code and create my migration with makemigrations
  2. Code again and create 2nd migration and migrate
  3. I migrate backward with --fake option
  4. I delete my new migrations files in myapp/migrations/
  5. Launch a new makemigrations
  6. And migrate again with --fake

With that method, I could make 100x migrations, when I'll commit, I keep only the last one containing all changes since the last commit.

15. Write a primary API

API everywhere

If your app has a primary goal, give it a primary API simply usable by devs. I made a project name django-super-favicon, it aims to generate a set of favicon from a source image and upload to static storage. The package was a mainly Command and usable with shell access, but I realized it is not enough. The next step was to allow developers to import my tools and use it directly in Python. Now I could imagine an online tool to create favicons and send to user.

To resume this topic in few words: Let users create different ways to use your package.

16. Command to help sys admins

Obviously Django command system is really helpful. If you want to write some, do not forget to give them a --noinput mode. They are command-line tool, so they could be used by bash, Puppet, Ansible or whatever and these last tools won't dialog with your Command.

17. Celery tasks to help devs

Create a tasks.py in your application and your asynchronous methods will be automaticaly registered by celeryd. In the same idea than "Write a primary API", a developer will love to have tasks already available. 

18. Model optimisation

It is one thing to don't to do too fast until you don't have a clear though of your database schema and usage. Django documentation has a great topic about database usage optimisation and I just want to add some tips about models:

  • Use index=True on every field field which is used as filter, DateField & DateTimeField firsts
  • Use index_together in the same idea

19. Model QuerySet and Manager

Managers and Querysets are the main way to launch batch operations on models. Create all methods that you think useful: Filters, database modifications, external actions like mails.

To imagine the usefulness, just think a view for list every viewable posts of this blog. With a custom filter, I have the same behavior on website, RSS or else. It's a DRY logic.

Django doc has a great page about that.

20. Put meta data about the app inside the app

setup.py's values should be located in app/__init__.py. That's one main usage of __init__.py files, to store metadata. Let's take the example of an application's version: if you don't follow this tips version number will be available only in setup.py, good for setuptools, but as setup.py isn't importable, it is not available for other piece of code like Sphinx, dependencies or custom code. My app/__init__.py looks like this:

"""My app description"""
VERSION = (0, 3, 0) __version__ = '.'.join([str(i) for i in VERSION]) __author__ = 'Anthony Monthe (ZuluPro)' __email__ = 'anthony@gmail.com' __url__ = 'https://github.com/ZuluPro/web-rich-object' __license__ = 'BSD' __keywords__ = ['web rich object', 'opengraph', 'facebook', 'web', 'twitter']

I can access to these data from setup.py:

"""Setup script of myapp"""
from setuptools import setup
import myapp

setup(
    name='myapp',
    version=myapp.__version__,
    description=myapp.__doc__,
    keywords=myapp.__keywords__,
    author=myapp.__author__,
    author_email=myapp.__email__,
    url=myapp.__url__,
    license=myapp.__license__,
    ...
)

 Or Sphinx conf.py:

import myapp

version = ".".join([str(i) for i in myapp.VERSION[:-1]])
release = myapp.__version__ 

21. Create great ModelAdmin for a great Admin interface

If you want to customize a maximum your WebUI, you should do it in Admin interface. Create great ModelAdmin and register it, users have it automaticaly in their admin. A good ModelAdmin is:

  • Good columns with list_display, they show useful things and/or useful links
  • Filters with list_filters, you can easily create your custom ones
  • Usage of date_hierarchy
  • Intelligent form with fieldsets
  • Simple add form: Admins sometime don't need all fields to only create an instance
  • Useful admin actions: Change booleans quickly is often very useful

To make easily batch action, do not hesitate to do create QuerySet custom methods.

22 Create custom exceptions

Provide custom exceptions to all error that could happen. It supposes to have a mature idea of possible issues in your code.

It could sound superfluous, but if Django hadn't a Model.DoesNotExist error, maybe it had raised an IndexError in some case and a MySQL exception. A dev who want to use a Models.objects.get() should have to deal with Python, MySQL, PostgreSQL or else errors. And he/she also has to guess what is the exact problem. If you want your app to be magic a user should not see IndexError: list index out of range but ElementNotFound.

23. Use Django Check system

There's not a lot of re-usable app using this feature but it is incredibly useful. I saw some projects like Askbot which make a startup script to ensure Django's settings and personally I did that in my settings.py file. But in fact the best is to use the built-in check system. You just have to create a checks.py and it will be integrated to Django core's mechanisms.

Just a small tip about: Instead of write like in Django documentation, create constant for each check. It will let you reuse them in unit tests for example. Mines are like this:

from django.core.checks import Warning, register, Tags
from django.utils.six import string_types
from dbbackup import settings

W001 = Warning('Invalid HOSTNAME parameter',
               hint='Set a non empty string to this settings.DBBACKUP_HOSTNAME',
               id='dbbackup.W001')
W002 = Warning('Invalid STORAGE parameter',
               hint='Set a valid path to a storage in settings.DBBACKUP_STORAGE',
               id='dbbackup.W002')


@register(Tags.compatibility)
def check_settings(app_configs, **kwargs):
    errors = []
    if not settings.HOSTNAME:
        errors.append(W001)

    if not settings.STORAGE or not isinstance(settings.STORAGE, string_types):
        errors.append(W002)

    return errors

24. Define loggers

Developers debug their app with pdb (or print statements for others), but the same app in production won't be troubleshooted in this way and the best friends of a sys admin are logs. Take care of:

  • Log catched exception, e.g.: Your backend doesn't work but you plan this with a try/except to not raise a 500. Even you return a HTTP 200, it is a critical error that should be logged.
  • Let a way to customize which logger users want. Do not log everything in the same namespace
  • Declare them in documentation
  • Test that your app won't break other loggers

25. Py.test and django-nose are useless

In case of re-usable apps' unit tests, test micro-framework are not really useful, why:

  1. Django has already a huge test framework creating test DB, having custom assert methods, a test client etc...
  2. Instead of choose between py.test and nose, we'll satisfy everyone without both

26. Add unit tests inside app

A complex app has generally its small test framework. If you plan to put unit tests outside of your Python package, developers using your app and who want to create tests about in their project won't be able to enjoy your test utilities.

27. Prefer FactoryBoy to fixtures

Yes fixtures system is greatly implemented in Django, but no you shouldn't use it because:

  • As more as your tests are complex, as more as you'll have to include fixtures. And you'll have to choose between huge file with all requirements and many small files matching with tests
  • Fixtures set up is really expensive: Firstly, there are read I/O from file, deserialization and write I/O to disk
  • initial_fixtures are often imcompatible with Django projects and or tests

That's why FactoryBoy, you provide a class to populate your database or create a set of any objects. Again it is highly re-usable by developers and without deserialization global test time will be really save. And in the same idea of the previous subject, a dev will be happy to have factories ready to help contributions.

28. Create a test project

An app is often untestable without a Django project, so you should include one inside your tests directory. Instead of just do a django-admin.py startproject test_project, just create the required files in my app's tests/:

$ tree dj_web_rich_object/tests/
├── factories.py
├── __init__.py
├── settings.py
├── test_models.py
└── urls.py

I clean settings.py to keep only real requirements, INSTALLED_APPS could have only your app. urls.py is useless if you don't test views. These files will just help you to keep compatibitly between Django's versions.

29. Create a simple test launcher

Your application is just a app, so there's no manage.py. I generaly create a similar file name runtests.py and by default it runs test myapp, but with args it acts as manage.py and let users use all commands (shell, dbshell, makemigrations, migrate). Here's mine:

#!/usr/bin/env python
import os
import sys

import django
from django.conf import settings
from django.core.management import execute_from_command_line


def main(argv=None):
    os.environ['DJANGO_SETTINGS_MODULE'] = 'dj_web_rich_object.tests.settings'
    argv = argv or []
    if len(argv) <= 1:
        from django.test.utils import get_runner
        if django.VERSION >= (1, 7):
            django.setup()
        TestRunner = get_runner(settings)
        test_runner = TestRunner()
        result = test_runner.run_tests(["dj_web_rich_object.tests"])
        return result
    execute_from_command_line(argv)

if __name__ == '__main__':
    sys.exit(bool(main(sys.argv)))

Without argument, it launch my app tests else it acts like manage.py. And please note with Django older than 1.8 you must setup configuration.

30. Coverage at ±95%

Test coverage is one of the most appreciated indicator of quality. it is a bad one, but let guess if authors think about tests or not. To greatly measure it, help everyone and put a .coveragerc file:

$ cat django-web-rich-object/.coveragerc
# .coveragerc to control coverage.py
[run]
branch = True
source = dj_web_rich_object
omit =
    dj_web_rich_object/tests/*
    dj_web_rich_object/migrations/*
    dj_web_rich_object/functional_tests*

[report]
# Regexes for lines to exclude from consideration
exclude_lines =
    # Have to re-enable the standard pragma
    pragma: no cover
    noqa:

    # Don't complain about missing debug-only code:
    def __repr__
    if self\.debug

    # Don't complain if tests don't hit defensive assertion code:
    raise AssertionError
    raise NotImplementedError

    # Don't complain if non-runnable code isn't run:
    if 0:
    if __name__ == .__main__.:
    __all__
    import
    deprecated_warning
    in_development_warning

ignore_errors = True

Use it to ignore directories like tests, useless migrations and make results more accurate. 

31. Do a great README

This is one of the most important part of a free repository. It is like humans.txt but used. Mines include:

  • Short description
  • Badges to indicate the seriousness
  • Simple usage if possible
  • How to contribute

32. Add a simple setup.py file

In the same idea of Do no write too much in __init__.py file, let setup.py file as simple as possible. In my opinion, all the data you need are in the app's __init__.py, requirements.txt files and README. Other data are setuptools specific and should be just in this file.

from setuptools import setup, find_packages
import curriculum


def read_file(path):
    with open(path, 'r') as fd:
        return fd.read()

setup(
    name='django-cv',
    version=curriculum.__version__,
    url=curriculum.__url__,
    description=curriculum.__doc__,
    long_description=read_file('README.rst'),
    author=curriculum.__author__,
    author_email=curriculum.__email__,
    license=curriculum.__license__,
    platforms='any',
    zip_safe=False,
    classifiers=[
        'Development Status :: 4 - Beta',
        'Environment :: Web Environment',
        'Framework :: Django',
        'Intended Audience :: Developers',
        'Intended Audience :: Customer Service',
        'Intended Audience :: System Administrators',
        'Intended Audience :: Information Technology',
        'Natural Language :: English',
        'License :: OSI Approved :: BSD License',
        'Operating System :: OS Independent',
        'Programming Language :: Python',
        'Programming Language :: Python :: 2',
        'Programming Language :: Python :: 3',
        'Framework :: Django',
    ],
    packages=find_packages(exclude=['tests.runtests.main']),
    include_package_data=True,
    test_suite='tests.runtests.main',
    install_requires=read_file('requirements.txt').splitlines(),
)

33. Put files other than *.py in your MANIFEST.in

The issue met by every developer: You put in application in pre-production and has no CSS in your website. After some search, you forgot to include static files in your MANIFEST.in, so they are not included in your Python package and your have to do a new release.

Never forget this file, include inside CSS, JS and TXT from your templates and static directories. And if you make your setup.py like in the topic above, do not forget to put README, requirements and else in the MANIFEST.in.

34. Check MANIFEST.in doesn't include craps

Forget to include or exclude file from MANIFEST.in is one of the most vicious error that can happen. If you don't exclude, you risk to publish private data (in .pyc files) or omit to include and simply break your app. The solution: check-manifest.

This lib helps to list differences between sdist and your repository, and suggests what you should add. Just add what you are sure to ignore, like .travis.yml:

[check-manifest]
ignore =
    .travis.yml

35. Pin dependencies' versions in requirements.txt

It's more or less mandatory to have a requirements.txt in your repository. It is the primary source of dependencies and could be use by .travis.yml, tox.ini, or online CI tool to ensure your requirements are updated. But please, pin you dependencies to a version or mininum to a minor stable version.

Example of why: A package linked to Django risks to break on each update of minor versions, an automation tools won't know if your package is compatible with a new Django version, it could update the framework and break user's project.

36. Create a requirements-tests.txt

Don't mix your app requirements and testing ones. Tests requirements are often the same than the development ones, so this file will help developers to setup their environment, just:

pip install -r requirements-tests.txt

37. Ensure code quality with prospector

Lint code is too time consuming, and in an open-source project code quality is very very important. Generaly contributors are curious guys who want to bring an improvement after read source. Maybe like me, it doesn't disturb him/her to PEP8-ize an ugly code, but best is to maintain it clean in a CI process. Don't make a choice between pep8, pylint or else, prospector is all in one.

38. Ensure your documentation doctest

To include code snippets in your doc is a great thing, to have these wrong is an enormeous error. So ensure you code snippet with doctest.

39. Ensure many things with tox

In Python this tool shoud be the base of every CI tools, it does all the basics jobs needed to ensure a project:

  • Install your package and its dependencies
  • Tests in all Python versions
  • Create multiple environments

It's made on top of virtualenv, so you can easily re-use the test environments. For me Tox is a little bit like a Makefile but dedicated to Python, it helps me to gather a set of command I frequently launch and I use it for many purpose: Unit tests, functional tests, Lint, documentation validation.

Here's an example for tox.ini from Django-DBBackup, this app must support as much as Django and Python versions to help devs to update:

[tox]
envlist = py{2.7,3.2,3.3,3.4,3.5,pypy,pypy3}-django{1.6,1.7,1.8,1.9,1.10},lint,docs,functional

[testenv]
passenv = *
basepython =
    py2.7: python2.7
    py3.2: python3.2
    py3.3: python3.3
    py3.4: python3.4
    py3.5: python3.5
    pypypy: pypy
    pypypy3: pypy3
deps =
    -rrequirements-tests.txt
    django1.6: Django>=1.6,<1.7
    django1.7: Django>=1.7,<1.8
    django1.8: Django>=1.8,<1.9
    django1.9: Django>=1.9,<1.10
    djangomaster: Django>=1.9,<1.10
commands = {posargs:coverage run runtests.py}

[testenv:lint]
basepython = python
deps =
    prospector
commands = prospector dbbackup -0

[testenv:docs]
basepython = python
whitelist_externals=make
deps = -rrequirements-docs.txt
commands = make docs

[testenv:functional]
passenv = *
whitelist_externals = bash
deps =
    -rrequirements-tests.txt
    Django
    mysqlclient
    psycopg2
basepython = python
commands = {posargs:bash -x functional.sh}

[testenv:upgrade]
passenv = *
whitelist_externals = bash
deps =
    -rrequirements-tests.txt
    Django<1.10
    mysqlclient
    psycopg2
basepython = python
commands = {posargs:bash -x test_upgrade.sh}

This file is a bit complex, but it allows me to create a test matrix with the following dimensions:

  • Python version
  • Django version
  • Database backend

I also have test environments for other things:

  • I launch prospector to lint code
  • I launch functional tests
  • I test upgrading
  • I test documentation

And could do many other actions.

40. Add CI (Travis or else)

Absolutely mandatory, most of CI tools are free for open-source project, so never hesitate to add one. Do not think too much about how to launch your tests, just install tox and run your test environments. Here's an example of .travis.yml file that I use:

language: python

python:
  - "2.7"
  - "3.2"
  - "3.3"
  - "3.4"
  - "3.5"
  - "pypy"
  - "pypy3"

services:
  - mysql
  - postgresql
  - mongodb
addons:
  postgresql: "9.4"

env:
  matrix:
    - DJANGO=1.6
    - DJANGO=1.7
    - DJANGO=1.8
    - DJANGO=1.9
    - DJANGO=1.10

install:
  - TOX_ENV=py${TRAVIS_PYTHON_VERSION}-django${DJANGO}
  - pip install 'virtualenv<14.0.0' tox
  - tox -e $TOX_ENV --notest

script:
  - tox -e $TOX_ENV

after_success:
  - tox -e $TOX_ENV -- pip install coveralls 
  - tox -e $TOX_ENV -- coveralls $COVERALLS_OPTION

matrix:
  fast_finish: true
  include:
    - python: "3.4"
      env: TOX_ENV=lint
      install: pip install tox
      script: tox -e $TOX_ENV
    - python: "3.4"
      env: TOX_ENV=docs
      install: pip install tox
      script: tox -e $TOX_ENV
    - python: "3.5"
      env: ENV=functional
      install:
        - pip install tox
        - export PYTHON='coverage run -a'
      before_install: 
        - mysql -e 'CREATE DATABASE test;'
        - psql -c 'CREATE DATABASE test;' -U postgres
      script:
        - DATABASE_URL=sqlite:////tmp/db.sqlite tox -e functional
        - DATABASE_URL=mysql://travis:@localhost/test tox -e functional
        - DATABASE_URL=postgres://postgres:@localhost/test tox -e functional
    - python: "3.4"
      env: TOX_ENV=upgrade
      install:
        - pip install tox
        - export PYTHON='coverage run -a'
      before_install: 
        - mysql -e 'CREATE DATABASE test;'
        - psql -c 'CREATE DATABASE test;' -U postgres
      script:
        - DATABASE_URL=sqlite:////tmp/db.sqlite tox -e upgrade
        - DATABASE_URL=mysql://travis:@localhost/test tox -e upgrade
        - DATABASE_URL=postgres://postgres:@localhost/test tox -e upgrade
  exclude:
    - python: "3.5"
      env: DJANGO=1.6
    - python: "3.5"
      env: DJANGO=1.7
    - python: "3.2"
      env: DJANGO=1.9
    - python: "3.3"
      env: DJANGO=1.9
    - python: "3.2"
      env: DJANGO=1.9
    - python: "3.3"
      env: DJANGO=1.9
    - python: "pypy3"
      env: DJANGO=1.9
    - python: "3.3"
      env: DJANGO=1.10
    - python: "3.2"
      env: DJANGO=1.10
    - python: "3.3"
      env: DJANGO=1.10
    - python: "pypy3"
      env: DJANGO=1.10
  allow_failures:
    - python: pypy3

Like my tox.ini, this file create a matrix of Django and Python versions. I just added the following things:

  • Added tasks for custom tasks: Documentation, lint, etc
  • Exclusion of impossible environment

41. Add documentation or not

Until your application stay simple a README.rst could entirely does the job. But there is one moment when even a minimal doc is really helpful for devs and users.

Sphinx is here for you. If you don't know it, it is the standard tools for create Python (or else) documentation from RST.

42. Document your commands with sphinx-django-command

It is a little ad for my package and it is really useful. Instead of update your Django commands' documentation just add its Sphinx tags and get an updated doc.

I also encourage everyone to create project to document easily Django projects/apps.

43. Tag all versions

Git has the super tag feature allowing to add a simple name to a commit. The best thing with that is to keep all published version as a Git tag, e.g. "1.1", "1.2", etc. It really eases repository handling and debugging for you, your peers and automation tools. For example, you'll be able to install any version without publish on Pypi:

pip install https://github.com/ZuluPro/sphinx-django-command/archive/mybranch.zip 

44. Create a Makefile

It could sounds redundant with tox.ini, but Makefile can be considered as the first entry point of any user/developer. I explain, Tox is a Python tool used to deal with virtual Python environment. If you are a C/C++, Ruby, JS developer, it doesn't concern you. And more, some tasks could require simple shell commands or NodeJS one, so Makefile is the place where to store this kind of procedure.

45. Create a sample/demo project

Maybe even with great README and documentation a user would like to see how your app works, so a demo project will helps. Create a ready-to-use Django project with your app installed. It would be great if it has its README to indicate that user only has to ./manage.py runserver (or else).

It will help users in different ways:

  1. He/she will have a skeleton of how the app works
  2. He/she can have the look and feel

46. Add Heroku deployment link

Heroku offers a super "Deploy to Heroku" button providing an easy way to deploy and configure a project on their platform. Great thing to test quickly your app. The following button in your the README can help to launch quickly your demo:

Deploy 

47. Add a Dockerfile

Docker can help you in two ways:

  • Test image: Define a container that will launch tests, creates an environment more isolated than Tox.
  • Demo image: Define a container to test your app in real environment, in same idea of Heroku deployment.
  • User image: Define the base image usable by other. It isn't compatible with almost Django re-usable app because they are re-usable by Django itself.

48. Add docker-compose.yml file

I don't think it will be re-used directly, but include a docker-compose.yml helps to understand the minimal service requirements of your app. Your app could need a celery daemon, a celeryd or some trick in environment variables. So for let a skeleton to users a docker-compose.yml is useful.

49. Use transifex for i18n

I don't think you speak 33 languages but you are open-minded and don't put border to your app. Do not let you overflood by translations PRs, use Transifex, it is free for open-source project.

Why ? Because it will let you organize translator teams, receive translation, approve them and merge.

50. I forgot .gitignore

A big and efficient .gitignore file at the beginning of the project could avoid many errors of a lot people. Some will say "It's just one more topic, on his post!". Everyone look at its git status before commit but we'll take a simple example: If you don't ignore *.pyc files and a contributor decide to add a new folder (git add folder/), it will add *.pyc files too.

Voilà.

Comments

No comments yet.

Post your comment

Comment as . Log out.