Skip to main content

Make Your Django Project Newbie Contributor Friendly With pre-commit

·1034 words·5 mins

It’s really worth investing time configuring your project and make it easy for other developers to contribute.

One way of enabling this is a clean, organized, and well-formatted code.

This is really helpful especially for first time developers or contributors as it makes pull request reviews less painful (e.g. trailing white space, unorganized imports, debug statements)

We can easily prevent this by using a tool called pre-commit. Let’s get started!

Installation #

Installing pre-commit #

Just pick one installation method

  • Install using their official installer if you’re on any unix based distribution website

    $ curl https://pre-commit.com/install-local.py | python -
    
  • Install with pip OR just add pre-commit to your requirements.txt or requirements-dev.txt.

    $ pip install pre-commit
    

Installing Django (optional) #

If you already have an existing repository, you can skip this step.

This isn’t gonna be a Django tutorial so we’ll just do the bare minimum setup

$ mkdir mysite
$ python -m venv venv # Create a virtualenv called `venv`
$ source venv/bin/activate
$ pip install Django
$ django-admin startproject .

Let’s initialize it as a git repository since pre-commit only works with git repositories.

$ git init .
$ echo "venv" >> .gitignore
$ git add .
$ git commit -m "Initial setup"

Configuring pre-commit #

Now, on your root directory, let’s create file called .pre-commit-config.yaml. pre-commit looks for this file so it knows what kind of hooks to run when we are committing.

$ touch .pre-commit-config.yaml

Copy and paste this inside the newly created file

repos:
  - repo: 'https://github.com/pre-commit/pre-commit-hooks'
    rev: v3.4.0
    hooks:
      - id: trailing-whitespace
      - id: check-yaml
      - id: check-merge-conflict
      - id: debug-statements
      - id: check-added-large-files
      - id: requirements-txt-fixer
  - repo: local
    hooks:
      - id: django-check
        name: Check django project for potential problems
        entry: sh -c 'python manage.py check'
        types:
          - python
        pass_filenames: false
        language: system
      - id: django-check-migrations
        name: Check django project for missing migrations.
        entry: sh -c 'python manage.py makemigrations --check --dry-run'
        files: models.py
        types:
          - python
        pass_filenames: false
        language: system
  - repo: 'https://gitlab.com/pycqa/flake8'
    rev: 3.9.0
    hooks:
      - id: flake8
  - repo: 'https://github.com/pycqa/isort'
    rev: 5.8.0
    hooks:
      - id: isort
  - repo: 'https://github.com/python/black'
    rev: 20.8b1
    hooks:
      - id: black

It might look overwhelming but you just need to focus on 2 things here: the repo and hooks. If you read it carefully, the hooks are kinda self-explanatory. These hooks gets run in order every time you commit. Let’s see it in action.

Let’s install the hooks and add .pre-commit-config.yaml to git:

$ pre-commit install
pre-commit installed at .git/hooks/pre-commit
$ git add .pre-commit-config.yaml
$ git commit -m "Add pre-commit config"
[INFO] Initializing environment for https://github.com/pre-commit/pre-commit-hooks.
[INFO] Initializing environment for https://gitlab.com/pycqa/flake8.
[INFO] Initializing environment for https://github.com/pycqa/isort.
[INFO] Initializing environment for https://github.com/python/black.
[INFO] Installing environment for https://github.com/pre-commit/pre-commit-hooks.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
[INFO] Installing environment for https://gitlab.com/pycqa/flake8.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
[INFO] Installing environment for https://github.com/pycqa/isort.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
[INFO] Installing environment for https://github.com/python/black.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
Trim Trailing Whitespace.................................................Passed
Check Yaml...............................................................Passed
Check for merge conflicts................................................Passed
Debug Statements (Python)............................(no files to check)Skipped
Check for added large files..............................................Passed
Fix requirements.txt.................................(no files to check)Skipped
Check django project for potential problems..........(no files to check)Skipped
Check django project for missing migrations..........(no files to check)Skipped
flake8...............................................(no files to check)Skipped
isort................................................(no files to check)Skipped
black................................................(no files to check)Skipped

Notice that pre-commit says no files to check. It only checks for files included in our commit and skips the hooks that aren’t applicable.

You might be asking, what if we just wanted to run pre-commit without actually committing. Well, that’s actually possible especially for existing repositories. We can run pre-commit against all files by passing the --all-files argument

$ pre-commit run --all-files
Trim Trailing Whitespace.................................................Passed
Check Yaml...........................................(no files to check)Skipped
Check for merge conflicts................................................Passed
Debug Statements (Python)................................................Passed
Check for added large files..............................................Passed
Fix requirements.txt.................................(no files to check)Skipped
Check django project for potential problems..............................Passed
Check django project for missing migrations..........(no files to check)Skipped
flake8...................................................................Passed
isort....................................................................Passed
black....................................................................Failed
- hook id: black
- files were modified by this hook

reformatted /Users/trafalgar/Sources/github.com/yujinyuz/mysite/mysite/asgi.py
reformatted /Users/trafalgar/Sources/github.com/yujinyuz/mysite/manage.py
reformatted /Users/trafalgar/Sources/github.com/yujinyuz/mysite/mysite/urls.py
reformatted /Users/trafalgar/Sources/github.com/yujinyuz/mysite/mysite/wsgi.py
reformatted /Users/trafalgar/Sources/github.com/yujinyuz/mysite/mysite/settings.py
All done! ✨ 🍰 ✨
5 files reformatted, 1 file left unchanged.

If you notice, our black formatter has failed. This is because it performed changes to our files. And if you run the previous command again, everything should now pass

$ pre-commit run --all-files
Trim Trailing Whitespace.................................................Passed
Check Yaml...........................................(no files to check)Skipped
Check for merge conflicts................................................Passed
Debug Statements (Python)................................................Passed
Check for added large files..............................................Passed
Fix requirements.txt.................................(no files to check)Skipped
Check django project for potential problems..............................Passed
Check django project for missing migrations..........(no files to check)Skipped
flake8...................................................................Passed
isort....................................................................Passed
black....................................................................Passed

Aaaaand that’s it! You can now add the files and commit them to have a better and cleaner repository.

$ git add .
$ git commit -m "Lint files"
Trim Trailing Whitespace.................................................Passed
Check Yaml...........................................(no files to check)Skipped
Check for merge conflicts................................................Passed
Debug Statements (Python)................................................Passed
Check for added large files..............................................Passed
Fix requirements.txt.................................(no files to check)Skipped
Check django project for potential problems..............................Passed
Check django project for missing migrations..........(no files to check)Skipped
flake8...................................................................Passed
isort....................................................................Passed
black....................................................................Passed
[main 725df70] Lint files
 5 files changed, 41 insertions(+), 41 deletions(-)

Configuring your formatters (isort, flake8, and black) #

It’s always common to have an agreed set of rules when working with a team. Since our pre-commit config is using isort, flake8, and black, we might want to add configurations to them such as maximum line length, import ordering, etc.

Let’s create a file called setup.cfg. This gets picked up by our formatters and uses the settings when formatting our code:

$ touch setup.cfg

And paste the following:

[flake8]
ignore = W503,E501,E226,E702,E731
max-line-length = 88
exclude = migrations

[isort]
profile = black
multi_line_output = 3
force_grid_wrap = 0
use_parentheses = True
ensure_newline_before_comments = True
line_length = 88
known_django = django
sections = FUTURE,STDLIB,DJANGO,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
skip = wsgi.py,setup.py

This is the config that I use. You can change it to whatever floats your boat.

Conclusion #

pre-commit has been a great tool and helped me reduce time reviewing and nitpicking unformatted code and focus on the important things.

Hope you find this helpful. Have a great day!