django-arch-check: a CLI tool that detects architectural problems in Django projects

Hi everyone,

I’ve been working on a static analysis tool for Django projects called
django-arch-check. It analyzes your codebase without importing it,
running Django, or touching your database — just pure AST parsing.

It detects:

  • Fat models (too many methods accumulating on a single model)
  • God apps (one app owning a disproportionate share of the codebase)
  • Circular imports
  • Missing service layer (business logic living in views)
  • Celery tasks without retry configuration
  • Direct SQL usage outside migrations
  • N+1 query risks in views and serializers

It also generates an HTML report with a health score.

I ran it against Saleor as a real-world test and it found:

  • A payment task with no retry configured
  • 72 lines of business logic inside a single view function
  • 5 circular imports in the GraphQL layer
  • The Order model sitting at 19 methods (one away from critical)

Installation:
pip install django-arch-check

Usage:
django-arch-check analyze /path/to/your/project
django-arch-check analyze --format html /path/to/your/project

GitHub: GitHub - RJ-Gamer/django-arch-check · GitHub
PyPI: django-arch-check · PyPI

This is early (v0.3.1) and I’d genuinely appreciate feedback —
false positives you hit, detectors that aren’t useful, things
that are missing. Happy to hear what architectural problems
people run into most on their Django projects.

Welcome @RJ-Gamer !

This is an interesting project, potentially useful.

However, I do hope you realize that a number of things that you consider to be “problems” are actually considered strengths in Django by many-to-a-majority of people using Django.

Specifically:

  • Fat models
  • Single-app structure
  • No service layer

You can find many discussions here and other places identifying these features as desireable.

Note - by “single-app structure”, I’m also assuming that you’re limiting yourself to apps contained within the main directory structure of the project, which ignores the recognition of independently-installed apps. (e.g. See Advanced tutorial: How to write reusable apps | Django documentation | Django)

But I can see where this creates an issue. If you include independently-installed apps, then you would need to evaluate packages like the admin, drf, ddt, etc. But if you don’t include those apps, then you may end up flagging projects that are built around independently-installed modules. (In this context, independently-installed does not necessarily mean “third-party”.)

It’s also not clear what files are being counted

Beyond that, I would consider it more useful to flag apps that are too segregated. I would want to look for “cross-app model references” - those tend to be a bigger danger. (If you’ve got two apps that are making regular refereces to each-other’s models, that’s probably a good sign that they should be one app.)

I did try running this against my current pet-project.

It identified 4 fat models - that’s a good thing, and a desired structure for this project.

It identified 3 god apps - That’s actually a good thing, as the base app is relatively small, and the 3 apps integrated by the base app are all about the same size. But, if I ran your check against the “installable” version, where those three apps are separate modules, I’m sure I would then get the “god app” warning on that main app. (Yes, there are two other tiny apps in the project, so there are always going to be three apps in the project.)

It identified 6 methods in views for “missing service layer” due to the size of the view - in most of those cases, the number of lines in the view is inflated because of how I structure my code when defining a context to be rendered. (Each key in the context is on a separate line, so if my view is pulling together 10 different values for the context, that’s going to be at least 10 lines in the view. That’s above any other work being done by the view.)

Your N+1 test is looking for get, which is a very common function to use when retrieving items from a dict. I got a lot of warnings about that for functions that never use the ORM at all - they’re processing dicts.

Aside from the issues above, the architectural issues I see most frequently are:

  • ModelAdmin classes too big. (This is generally an indicator that the person is trying to do too much in the admin instead of creating their own views.)
  • Models and views broken down into individual files. (Having a model directory with one-model-per-file within it, or a views directory with one view per file.)
  • Django-provided GCBVs being extended beyond their optimal usage. (e.g. Handling multiple different forms by one edit view.)
  • Many small inter-related apps that can’t be used independently.
  • The needless creation of a service layer adding a level of indirection that doesn’t facilitate understanding of the flow.

(Granted, some of these are subjective and may be difficult to define tests.)

Thank you — this was genuinely the most useful feedback I received
on launch day, and I’ve pushed fixes for everything valid you raised.

Released in v0.3.3:

Fat models threshold raised from 10 → 15 methods. You’re right that
“fat models, skinny views” is a legitimate Django philosophy. The
default was too aggressive. On Saleor, this reduced fat model
findings from 9 down to 1 — only the Order model at 19 methods
remains flagged, which is a reasonable signal.

N+1 detector no longer flags dict.get(). The detector now requires
.objects. qualifier before flagging any .get() call. Confirmed
clean on Saleor after the fix.

Missing service layer now counts ORM calls instead of raw line
count. Your context dictionary example was a real false positive —
a view building a 15-key context dict was never business logic.
The detector now flags views with 2+ direct ORM calls (warning)
or 4+ (critical). Saleor’s service layer section is now clean.

Migration task files excluded from Celery detector. Tasks inside
migrations/ paths are skipped — they run once during deployment
and retry logic is irrelevant.

Regarding your suggested detectors — ModelAdmin size and cross-app
model references are both on the roadmap. The cross-app reference
point is particularly interesting, I agree it’s a stronger
architectural signal than app size alone.

The tool is intentionally opinionated but these fixes make it
significantly more accurate. Thanks again for running it against
a real project and taking the time to write this up.