pylint-django: teaching Pylint about the Django ORM
Pylint plugin for improving code analysis for when using Django
At a glance
- What is it?
- pylint-django is a Pylint plugin that stops the linter from complaining about things Django generates for you, and that gives the linter a real model of your project when you point it at your settings module. It also ships an opt-in checker for migration patterns that quietly cost you performance on large tables.
- Who is it for?
- pylint-django suits a Django shop that has already adopted Pylint and is tired of suppressing false positives around models, forms and migrations, and it is nearly free to add because it changes nothing about your code. It is a poor fit if your project is not Django based, and a poor fit if you cannot point the plugin at a real settings module, because without one it falls back to defaults that do not know your installed apps and the inference stays shallow.
- Can I use it commercially?
- Yes, with conditions. GPL-2.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A plugin that models the framework rather than the code
pylint-django is a Pylint plugin for improving code analysis when analysing code that uses Django. It is also used by the Prospector tool, which is worth knowing because it means the plugin is already part of the default path for a lot of Python projects.
The problem it solves is that Pylint reasons about plain Python, while a Django project is full of code that is generated or injected at runtime. A `Model.objects` attribute does not exist in the source file. A view's `request` argument is passed in by the framework. A `ForeignKey` attribute is a descriptor whose type Pylint cannot infer from a class body. In every one of those cases Pylint reports a problem that is not a problem, and the usual response is a suppression comment, and the usual consequence is that people stop reading the linter output.
The plugin's value is therefore mostly the absence of noise, which is a harder thing to appreciate from a feature list than a new check would be. A linter that only reports genuine problems is worth more than one with a dozen extra checks buried under hundreds of false positives.
The project is licensed under GPLv2, the repository is not archived, and the last push was on 2026-10-19, so the plugin is being maintained against current Django releases.
The Django dependency is deliberately not installed
Installation is a single pip command:
pip install pylint-djangoThere is a warning attached to it, and the warning is the interesting part of the install instructions. The plugin does not install Django by default, because that causes more trouble than good, and the readme links a pull request where that decision is discussed. If you want the latest Django pulled in automatically, there is an extra:
pip install pylint-django[with-django]Otherwise the instruction is to sort out your testing environment, followed by an emphatic request not to report issues about a missing Django.
That last point is worth repeating as a rule for anyone filing issues against this project. The absence of Django is a configuration state, not a bug, and the maintainers have clearly been asked about it often enough to write it in capital letters. The reason is structural: the plugin's value depends on introspecting a real Django installation, so a working Django environment is a precondition rather than a dependency of the package.
For a team, the sensible arrangement is to have Django present in the environment where the linter runs, and to keep the plugin install itself free of the framework so it can be added to a shared tooling environment without dragging a web framework in with it.
Give it a settings module or the inference stays shallow
To use the plugin you load it into Pylint, and to get the most out of it you tell it where your Django project lives.
The environment variable route is the one you would use in CI:
DJANGO_SETTINGS_MODULE=your.app.settings pylint --load-plugins pylint_django [..other options..] <path_to_your_sources>The command line flag does the same thing:
pylint --load-plugins pylint_django --django-settings-module=your.app.settings [..other options..] <path_to_your_sources>The readme is clear about what happens if you skip this. If you do not configure Django, default settings will be used, but that will not include, for example, which applications to include in `INSTALLED_APPS`, and so the linting and type inference will be less accurate. The recommendation is to specify a settings module.
That sentence is the whole architecture in one line. The plugin does not parse your source to find out what your models look like. It loads Django, calls `django.setup()` with your settings, and then asks the running framework what a given class or attribute actually is. That is why the settings module matters, and why the plugin behaves noticeably better on a project with a conventional app layout than on a project whose settings are assembled in unusual ways.
There is a practical consequence for CI: the lint job now needs the Django settings to be importable, which means environment variables and dependencies have to be present in the linting container, not just in the test job.
What the plugin stops complaining about
The feature list is a list of false positives removed, and reading it tells you what Django's magic looks like from a static analyser's point of view.
Warnings about Django-generated attributes such as `Model.objects` or `Views.request` are prevented. Warnings when using `ForeignKey` attributes are prevented, the classic case being an instance of ForeignKey having no such member. Pylint's knowledge of the types of Model and Form field attributes is fixed, so a form field is no longer an untyped object.
There are validations as well as suppressions. `Model.__unicode__` methods are validated, and `Meta` informational classes on forms and models do not generate errors.
One check is a genuine bug finder rather than a noise remover: it flags dangerous use of the `exclude` attribute in `ModelForm.Meta`. That is the kind of thing that silently drops validation.
And there is a deeper capability. The plugin uses Django's internal machinery to try to resolve models referenced as strings in `ForeignKey` fields, which relies on `django.setup()` and therefore on the appropriate project settings being defined. This is the difference between a plugin that pattern matches on your code and one that asks the framework. It is also the reason the settings module matters, and the reason a misconfigured environment produces a different set of warnings rather than an error.
The migrations checker is off by default on purpose
The additional plugin is `pylint_django.checkers.migrations`, and it looks for two specific migration patterns.
The first is a migration that adds a new model field where the field has a default value. The readme points at the Django documentation, which says this may have performance penalties especially on large tables, and explains the reasoning: adding a column with a default in one step means the database has to write that default to every existing row, whereas adding the column with `null=True` is created instantly and can then be populated separately. The preferred way is to add a new database column with `null=True` and then possibly populate the table with the desired default values. Only the last migration from a sub-directory will be examined.
The second is a `migrations.RunPython()` without a reverse callable, which results in a non reversible data migration. That is a real operational problem, because a data migration you cannot reverse leaves you with no way back.
The checker is disabled by default, and enabling it means loading a second plugin:
pylint --load-plugins pylint_django --load-plugins pylint_django.checkers.migrationsLeaving it off by default is a sensible default for a checker whose findings depend on the shape of your migration history rather than on the code in front of you. Turn it on for a codebase whose migrations you did not write, and read the findings as a review list rather than as errors in your current changes.
Testing the plugin the way Pylint tests itself
The test package follows the structure of Pylint's own tests, and the readme explains how to extend it.
You create a module in a topic directory of `pylint_django/tests/functional/`, named after the topic followed by a test name, for example `functional/views/views_class.py`, and put some code into it. The tests then run Pylint against those modules, and the idea is that no messages occur, though the readme notes it is fine if the idea is that no messages occur now, and that you should check it works by running `scripts/test.sh`.
Arguments passed to `scripts/test.sh` are passed through to the internal pytest invocation, so debugging uses `scripts/test.sh --capture=no`, and a single case is selected by filtering on the file name with `./scripts/test.sh -k 'views_class'`.
Tests with expected error output are possible too, for example when adding a new message or accepting that Pylint is supposed to warn. Each line where a message is expected is marked with a comment such as `# [unused-argument]`, and a `test_file_name.txt` file next to the module holds the expected messages in a fixed format of symbol, line, column, end line, end column, object, message and confidence.
That is a well thought out harness for a plugin whose whole job is producing and suppressing messages, and the guidance to add suppression comments to the sample modules so the samples do not need to be perfect code is a small kindness to contributors.
Version support, packaging and the release line
The package metadata is where the compatibility picture is.
The plugin is built with poetry, the version is 2.8.0, and it is marked as Development Status 5, Production/Stable. The Python requirement is 3.9 and above, below 4.0, and the supported Django versions are listed as classifiers from Django 2.2 through 3.x, 4.x, 5.0 to 5.2 and 6.0, which is a wide range and suggests the plugin is not tightly coupled to one Django generation. The pylint dependency is `>=3.0,<5`, and `pylint-plugin-utils` is required at `>=0.8`.
Django itself is an optional dependency, constrained to `>=2.2`, which matches the warning in the install section: the framework is not pulled in for you.
The release history explains the pace. Version 2.6.1 was tagged on 2024-10-10, 2.7.0 on 2026-01-01 and 2.8.0 on 2026-07-11, with the last push on 2026-10-19. A gap of more than a year followed by two releases in 2026 suggests the project tracks Django's own release cycle rather than shipping continuously, which is a sensible rhythm for a plugin whose behaviour depends on the framework's internals.
Development is managed with a Makefile whose `install` target installs poetry and runs `poetry install`, and whose `make test` target runs tox. The repository also carries a `tox.ini`, a `.coveragerc`, a `custom_dict.txt` for the custom checker messages, a `CHANGELOG.rst`, a `CONTRIBUTORS.md` where you can add your name, a `SECURITY.md`, and a Prospector configuration file from the same era.
Editorial conclusion
pylint-django suits a Django shop that has already adopted Pylint and is tired of suppressing false positives around models, forms and migrations, and it is nearly free to add because it changes nothing about your code. It is a poor fit if your project is not Django based, and a poor fit if you cannot point the plugin at a real settings module, because without one it falls back to defaults that do not know your installed apps and the inference stays shallow. Before you add it, decide whether you will set `DJANGO_SETTINGS_MODULE` in CI rather than relying on defaults, and read the migrations checker documentation before enabling `pylint_django.checkers.migrations`, since it is off by default for a reason.
Frequently asked questions
How do I install pylint-django?
Run `pip install pylint-django`. Django is deliberately not installed by default because that causes more trouble than good, and there is a `pip install pylint-django[with-django]` extra if you want the framework pulled in automatically.
How do I tell pylint-django where my Django settings are?
Either set the `DJANGO_SETTINGS_MODULE` environment variable or pass `--django-settings-module=your.app.settings` on the command line, together with `--load-plugins pylint_django`. Without a settings module the plugin falls back to defaults that do not know your installed apps, so the linting and type inference are less accurate.
What false positives does pylint-django remove?
It prevents warnings about Django-generated attributes such as `Model.objects` and `Views.request`, prevents warnings for `ForeignKey` attributes, fixes Pylint's knowledge of Model and Form field attribute types, and stops `Meta` informational classes on forms and models from generating errors. It also validates `Model.__unicode__` methods and flags dangerous use of `exclude` in `ModelForm.Meta`.
What does the migrations checker look for?
It flags migrations that add a new model field with a default value, which can have performance penalties on large tables, and migrations that use `RunPython()` without a reverse callable, which results in a non reversible data migration. The checker is disabled by default and is enabled by loading `pylint_django.checkers.migrations` as an extra plugin.
Does Prospector use pylint-django?
Yes. The plugin is also used by the Prospector tool, and if you have prospector installed then pylint-django is already installed as a dependency and is activated automatically if Django is detected.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/pylint-dev-pylint-django)Was this useful?