Django calls a lot of your code without your code ever calling it. Views are listed in urlpatterns. Signal receivers are wired by a decorator. Admin methods are named as strings in list_display. Middleware and context processors are dotted paths in settings.py. Management commands are found by file name. Templates call model methods.

A dead-code tool that doesn't know this reports half of a Django app as unused. A tool that trusts it too much hides real dead code. This page shows what Skylos 4.47.1 does on a sample Django and DRF project: what it keeps live, what it finds, which false positives you'll see, and what it misses.


What Skylos treats as used

Django conventionHow Skylos treats it
Function view referenced in any urlpatterns (path, re_path)Used
Class-based view used with .as_view(), and its get/post/... methodsUsed
CBV hooks such as get_queryset, get_context_data, form_valid on a class that directly subclasses a Django viewUsed
@receiver(...) handlers and functions passed to signal.connect(...)Used
ModelAdmin hooks, option attributes (list_display, actions, ...), functions listed in actions=[...], admin.site.register(Model, Admin)Used
Model hooks (save, clean, get_absolute_url, natural_key, ...), Meta, fieldsUsed
Form clean() and clean_<field>()Used
Management command Command, handle, add_argumentsUsed
MiddlewareMixin subclasses and their process_* methodsUsed
Template tags and filters registered with @register.filter, simple_tag, inclusion_tag, tagUsed
Migration RunPython callbacksUsed
Celery @shared_task / @app.taskUsed
DRF viewset actions (list, create, retrieve, ...), serializer validate, validate_<field>, to_representation, get_<field>Used

The base-class checks look at the direct base. If your view, model or admin inherits from your own base class (a project BaseAdminView, an abstract model), some of that handling no longer applies and you'll see more findings on that class.


What it finds

With the config from the next section, a first pass on the sample project at confidence 80, limited to unused functions and classes, lists only code that really is unused:

$ skylos . --confidence 80 --select SKY-U001,SKY-U004 --format concise
shop/admin.py:16  SKY-U001  unused function: unused_admin_action
shop/context_processors.py:5  SKY-U001  unused function: unused_processor
shop/migrations/0001_initial.py:12  SKY-U001  unused function: unused_migration_helper
shop/signals.py:19  SKY-U001  unused function: recalculate_everything
shop/tasks.py:9  SKY-U001  unused function: unused_task_helper
shop/templatetags/shop_tags.py:21  SKY-U001  unused function: unused_tag_helper
shop/views.py:28  SKY-U001  unused function: old_landing
shop/views.py:46  SKY-U001  unused function: unused_helper
shop/middleware.py:25  SKY-U004  unused class: UnusedMiddleware
shop/views.py:91  SKY-U004  unused class: PlainHelperClass

old_landing is a function view that no URLconf references. At the default threshold (60), Skylos also lists unused methods on models, querysets, views, viewsets, serializers, forms, admin classes and commands, such as Product.price_with_tax or ProductViewSet.compute_discount. Those are worth reviewing, but check templates first: Skylos doesn't read them, so a model method used only in a template looks unused.


False positives you will see, and the fix

Skylos doesn't read the strings in settings.py (INSTALLED_APPS, MIDDLEWARE, TEMPLATES), so anything Django loads only from a dotted path looks unused. Without config, the sample project's first scan reported:

  • every variable in settings.py, and application in wsgi.py;
  • a plain middleware class listed only in MIDDLEWARE;
  • a context processor listed only in TEMPLATES;
  • the function named by handler404 = "shop.views.not_found";
  • the TestCase class in the app's tests.py (Skylos treats files under tests/ or test/ and *_test.py as tests, not a file called tests.py);
  • methods named only as strings in list_display, @admin.display methods and DRF @action methods;
  • unused request, modeladmin and queryset parameters (SKY-U006), class attributes Django reads such as paginate_by, and declared form fields (SKY-U003).

This block in pyproject.toml fixed the first five groups and the decorated methods on the sample:

[tool.skylos.whitelist]
names = [
  "application",       # wsgi.py / asgi.py, loaded by the server
  "TimingMiddleware",  # listed in settings.MIDDLEWARE
  "process_view",      # Django middleware hook
  "shop_settings",     # listed in TEMPLATES context_processors
  "not_found",         # handler404 = "shop.views.not_found"
]

[tool.skylos.overrides."settings.py"]
whitelist = ["*"]

[tool.skylos.overrides."tests.py"]
whitelist = ["*"]

[[tool.skylos.dead_code.entrypoints]]
type = "method"
decorators = ["admin.display", "action"]
reason = "Django admin display methods and DRF @action routes"

For the rest:

  • Parameters and class attributes: leave them out of the first pass with --select SKY-U001,SKY-U004, as above.
  • One-off cases: put # skylos: ignore on the def line.
  • Methods named only in list_display strings: add the name to the whitelist.

Keep the TOML valid. In 4.47.1, if pyproject.toml doesn't parse, Skylos ignores the whole [tool.skylos] section without a warning, and every whitelisted name comes back. One way to check: python -c "import tomllib; tomllib.load(open('pyproject.toml', 'rb'))".

Avoid path in entry-point rules unless you need it, and check the output after adding one: on the sample, a path rule for middleware.py also hid a middleware class that really was unused.


What it doesn't find

Be clear about the blind spots before you trust a clean result:

  • Unused class-based views, DRF views and serializers, forms, ModelAdmins and models that directly subclass a Django or DRF base are never reported, even when nothing uses them. Their unused methods are reported; the classes are not.
  • Views behind certain decorators. Unused function views decorated with @login_required, @permission_required, @staff_member_required, @require_POST, @require_GET or @api_view are treated as used. On the sample, two such views were missed even at confidence 0.
  • Dead URLconfs. Skylos doesn't follow include("app.urls") strings. A view listed in a URLconf that nothing includes still counts as used.
  • Receivers that never connect. A @receiver in a module that is never imported (for example, a missing import in AppConfig.ready()) still counts as used.
  • Templates. Template code isn't scanned, in either direction.

So use Skylos to find unused function views, helpers, admin actions, tasks, template-tag helpers and unused methods. For unused routes, use runtime evidence: access logs or request metrics show which URLs nobody calls, which no static tool can tell you.


A workflow that holds up

  1. First pass: skylos . --confidence 80 --select SKY-U001,SKY-U004.
  2. Teach it your string-loaded names with the config above.
  3. Review the 60% method findings, checking templates and getattr-style dynamic use.
  4. Preview removals: skylos clean . --dry-run shows the unused imports and functions it would delete; --apply writes them.
  5. Stop new dead code with a pull-request check: skylos cicd init --no-upload writes a GitHub Actions workflow that needs no Skylos account or API key.

The dead-code docs cover confidence levels and suppressions. For the security side of a Django app, see Django security scanning: what static analysis catches.



Try it

pip install skylos
skylos . --confidence 80 --select SKY-U001,SKY-U004