Code Quality & Linting
Learn tools and practices for maintaining high code quality in Django projects. This is a foundational concept in Python web development that professional developers rely on daily. The explanations below are written to be beginner-friendly while covering the depth and nuance that comes from real-world Python/Django experience. Take your time with each section and practice the examples
Code Quality Tools
Maintaining high code quality is essential for long-term project success. Various tools help enforce coding standards and identify potential issues.. This is an essential concept that every Python/Django developer must understand thoroughly. In professional development environments, getting this right can mean the difference between code that works reliably and code that breaks in production. The following sections break this down into clear, digestible pieces with practical examples you can try immediately
Linting & Code Analysis
# Code Quality Tools Setup
# requirements-dev.txt (Development dependencies)
flake8==6.0.0
black==23.3.0
isort==5.12.0
pylint==2.17.4
mypy==1.3.0
bandit==1.7.5
pre-commit==3.3.2
# .flake8 configuration
[flake8]
max-line-length = 88
extend-ignore = E203, W503
exclude =
.git,
__pycache__,
.venv,
venv,
env,
migrations,
staticfiles,
media
# Black configuration (pyproject.toml)
[tool.black]
line-length = 88
target-version = ['py39']
include = '\.pyi?$'
extend-exclude = '''
/(
# directories
\.eggs
| \.git
| \.hg
| \.mypy_cache
| \.tox
| \.venv
| _build
| buck-out
| build
| dist
)/
'''
# isort configuration (pyproject.toml)
[tool.isort]
profile = "black"
multi_line_output = 3
line_length = 88
known_django = "django"
known_first_party = "myapp"
sections = ["FUTURE", "STDLIB", "DJANGO", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"]
# Pylint configuration (.pylintrc)
[MASTER]
disable=
C0114, # missing-module-docstring
C0115, # missing-class-docstring
C0116, # missing-function-docstring
[FORMAT]
max-line-length=88
[MESSAGES CONTROL]
disable=
C0103, # invalid-name
C0301, # line-too-long
R0903, # too-few-public-methods
R0913, # too-many-arguments
# MyPy configuration (pyproject.toml)
[tool.mypy]
python_version = "3.9"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
check_untyped_defs = true
disallow_untyped_decorators = true
no_implicit_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_no_return = true
warn_unreachable = true
strict_equality = true
[[tool.mypy.overrides]]
module = [
"django.*",
"django_extensions.*",
]
ignore_missing_imports = true
# Pre-commit hooks (.pre-commit-config.yaml)
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- id: check-merge-conflict
- repo: https://github.com/psf/black
rev: 23.3.0
hooks:
- id: black
language_version: python3
- repo: https://github.com/pycqa/isort
rev: 5.12.0
hooks:
- id: isort
- repo: https://github.com/pycqa/flake8
rev: 6.0.0
hooks:
- id: flake8
- repo: https://github.com/pycqa/bandit
rev: 1.7.5
hooks:
- id: bandit
args: [-r, ., -f, json, -o, bandit-report.json]
# Custom Django management command for code quality
# management/commands/check_code_quality.py
from django.core.management.base import BaseCommand
import subprocess
import sys
class Command(BaseCommand):
help = 'Check code quality using various tools'
def add_arguments(self, parser):
parser.add_argument(
'--fix',
action='store_true',
help='Automatically fix formatting issues'
)
def handle(self, *args, **options):
tools = [
('Black', ['black', '--check', '.']),
('isort', ['isort', '--check-only', '.']),
('Flake8', ['flake8', '.']),
('Pylint', ['pylint', 'myapp/']),
('MyPy', ['mypy', '.']),
('Bandit', ['bandit', '-r', '.']),
]
failed_tools = []
for tool_name, command in tools:
self.stdout.write(f'Running {tool_name}...')
try:
result = subprocess.run(
command,
capture_output=True,
text=True
)
if result.returncode == 0:
self.stdout.write(
self.style.SUCCESS(f'{tool_name}: PASSED')
)
else:
self.stdout.write(
self.style.ERROR(f'{tool_name}: FAILED')
)
self.stdout.write(result.stdout)
self.stdout.write(result.stderr)
failed_tools.append(tool_name)
except FileNotFoundError:
self.stdout.write(
self.style.WARNING(f'{tool_name}: Not installed')
)
if failed_tools:
self.stdout.write(
self.style.ERROR(
f'Code quality checks failed for: {", ".join(failed_tools)}'
)
)
sys.exit(1)
else:
self.stdout.write(
self.style.SUCCESS('All code quality checks passed!')
)
# Usage: python manage.py check_code_quality
# GitHub Actions workflow for code quality
# .github/workflows/code-quality.yml
name: Code Quality
on: [push, pull_request]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.9'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements-dev.txt
- name: Run Black
run: black --check .
- name: Run isort
run: isort --check-only .
- name: Run Flake8
run: flake8 .
- name: Run Pylint
run: pylint myapp/
- name: Run MyPy
run: mypy .
- name: Run Bandit
run: bandit -r . -f json -o bandit-report.json
# IDE Configuration (VS Code)
# .vscode/settings.json
{
"python.linting.enabled": true,
"python.linting.flake8Enabled": true,
"python.linting.pylintEnabled": true,
"python.linting.mypyEnabled": true,
"python.formatting.provider": "black",
"python.sortImports.args": ["--profile", "black"],
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true
},
"python.linting.flake8Args": ["--max-line-length=88"],
"python.linting.pylintArgs": ["--max-line-length=88"]
}