Analyzer::Python::Django
Inherits Analyzer::Python::PythonEngine < Analyzer < FileHelper < Reference < Object
path("tenants/<int:pk>/", include(get_model_urls("tenancy", "tenant"))):
a urlconf entry mounting the url list a project function builds out of a
view registry, which decorators on the view classes fill at import time
(@register_model_view(Tenant, "edit")). Neither name means anything to
this code: the builder is recognised by its body (it subscripts a
module-level registry with its own parameters, filters the entries on
one, and makes a path() from another), the writer by appending a dict
to that same registry slot, and each decorator application is evaluated
against the writer's signature.
Constants
HTTP_METHODS is a fixed table (from PythonEngine), so the
decorator-stack scan and the request.method == "..." scan below
(each an .each loop run per decorator line / per function-body
line) can look up a precompiled regex per method name instead of
interpolating and recompiling one on every iteration. Purely a
lookup-table hoist — the matched text/capture groups are unchanged,
so it doesn't affect the line/offset values computed elsewhere in
this file.
DECORATOR_METHOD_NAME_REGEXES also uses HTTP_METHODS_EXCLUDING_QUERY,
for the OTHER reason that constant documents: it bare-word-matches a
method name anywhere in a decorator's text (this scan is not CBV-
specific — it runs over the decorator stack above a plain function
too), and real decorators (e.g. DRF-spectacular's
OpenApiParameter("query", ...)) commonly carry query as a
parameter name rather than a verb restriction.
REQUEST_METHOD_NAME_REGEXES keeps the full list: it only fires on
an explicit, quoted request.method == "QUERY" comparison, which is
unambiguous developer-written evidence, not a name collision.
The input fields of the forms django.contrib.auth.forms ships, which
the scan never holds.
Verbs Django's own class-based views accept, for a urlconf that mounts
one directly (auth_views.PasswordResetView.as_view()): the class lives
in site-packages, outside the scan, so its body cannot be read.
LogoutView answered GET too below Django 5.
@action(...), and the @list_route(...) / @detail_route(...) it
replaced in DRF 3.8 (still the API of projects that vendor DRF, like
Taiga): a list route is detail=False, a detail route detail=True.
Verbs a project router class adds to DRF's own route table, as
{detail?, verb, viewset method}: NetBox-style bulk operations,
self.routes[0].mapping.update({"put": "bulk_update"}). Index 0 of
SimpleRouter.routes is the list route, 2 the detail route.
router = routers.DefaultRouter(trailing_slash=False)
http_method_names = ["get", "post"] on a view class.
WebhookForm(request.POST) / SearchForm(data=request.GET): a form
bound to the request reads every field its class declares, from the
body or the query string. Only a class the scan holds counts.
ponytail: declared fields only; a ModelForm's Meta.fields columns
are not resolved.
Earlier arguments may come first: AuthenticationForm(request, data=request.POST), PasswordChangeForm(user=request.user, data=...).
The local form (what form.cleaned_data reads) is bound only to
classes whose field set is closed, at least once to the request.
path(f"{prefix}admin/login/", view): an f-string route interpolating
a module-level string reads that string, its first top-level binding
(the default deployment, as path(prefix, include(...)) mounts at "").
A literal with any other interpolation is left as written.
data = json.loads(request.body) (or request.body.decode()): the
local's top-level keys are JSON body fields.
bool_from_request(request.GET, "include_predictions", False): a
project function handed a request mapping and a key reads that key
when its body looks the key up in the mapping (params.get(key, ...),
params[key]), judged by its definition.
Spec.model_validate(request.json) / Model.model_validate_json( request.body): a Pydantic model the view validates the request body
with names the JSON fields it reads, its annotated attributes.
ponytail: field names as declared; Field(alias=...) and alias
generators are not applied.
The input fields class target and its project bases declare as
name = <...>Field( / <...>Serializer( attributes, the read-only
ones left out. Serializers and Django forms declare them alike.
Field classes DRF never reads from input: HyperlinkedIdentityField
(and project subclasses keeping the name) forces read_only.
Verb checks and request reads in a function view's (or a helper's) body lines. A view that reads the raw body takes one: unrestricted, it answers POST.
def get(...) / async def post(...) method heads in class-based
views. Precompiled — an interpolated literal would be recompiled on
every line of every CBV class body. Uses HTTP_METHODS_EXCLUDING_QUERY
(see PythonEngine): Django's CBV dispatch (View.dispatch) looks up
request.method.lower() against the class's http_method_names
allowlist, which does not include query upstream — unlike Flask's
duck-typed getattr(self, request.method.lower()) dispatch, a
def query(self): method is genuinely inert unless a view manually
widens that allowlist, so matching it here would report a phantom
route.
The optional ( admits the tuple form include(("app.urls", "app")).
Regular expressions for extracting Django URL configurations
headers = request.META then headers.get("HTTP_USER_AGENT"): reads
through a local bound to a request mapping, spelled as reads of the
mapping itself.
return webhook_form(request, channel): a function view that hands its
request to a project function runs it to serve the request, so the
verbs it tests and the data it reads are the view's. Each comes back
as {verbs of the request.method branch calling it (nil outside one),
whether the call follows such a branch (it serves the other verbs),
its file, its body lines}, two calls deep.
Map request parameters to their respective fields
Precompiled per-field access patterns so extract_params_from_line
never rebuilds a PCRE2 regex per request field. Compiled once from
REQUEST_PARAM_FIELD_MAP. {field_name, field_methods, param_type,
bracket_re, get_re}
Map request parameter types to HTTP methods
data = request.POST if request.method == "POST" else request.GET:
which mapping the local names depends on the verb served.
Argument lists of each top-level url|path|re_path|register(...) call
in text. A call nested in another's arguments (an inline
include([...]) body) is skipped here and read when that include is
expanded. Scans bytes: every delimiter is ASCII, so UTF-8 is safe and
each call costs one forward pass.
url|path|re_path|register(, or a project wrapper named *_path(
(Zulip's rest_path); group 1 is set for the wrapper.
What a class-based view method body reads besides its own request.X
lines: forms it binds to the request, and the self methods it hands
request to (self.get_return_url(request, obj)), two calls deep.
Zero-width, so scan also sees a call nested in another's arguments
(self.paginate_queryset(self.get_queryset())).
Body fields a handler reads through the DRF serializer it hands
request.data: the fields that serializer class (or a project base)
declares. get_serializer means the view's serializer_class.
ponytail: declared fields only; a ModelSerializer's Meta.fields
model columns are not resolved.
XSerializer(data=request.data) / self.get_serializer(data=...).
A declared serializer field: name = serializers.CharField(.
login_form = import_string(settings.USER_LOGIN_FORM): a local bound
to the class a setting names by dotted path, through Django's
import_string or a project function that ends in it.
name = list(other), name: T = other, name += other.
Joins the verbatim pieces of an extended helper list; a comment line, so
route scanning reads through it, and split back so each piece keeps
its source line.
name = [, name: list[...] = [, name += [ and name.extend([.
An auth decorator applied in the urlconf itself:
path("saved/", login_required(SavedView.as_view())),
permission_required("x")(view). The view file never shows it, so the
auth tagger (which reads decorators at the handler's line) cannot see
it; the analyzer tags the endpoint it builds from that pattern instead.
rest_path("users/me", GET=get_me, PATCH=update_me): the keyword
names the verb, its value the view serving it.
A module-level X = get_class("search.views", "X") or
A, X = get_classes("m", ["A", "X"]) (Oscar's dynamic loader).
Class methods
Instance methods
Extract endpoints from a Django URL configuration file
Extract endpoints from a given file
Extract parameters from a line of code
Instance-side view of the same declaration. The per-file rescues live on
this base class, which has no way to name the analyzer that is running
inside them, so a skipped file could not be attributed to a tech.
Deriving it from analyzer_for keeps the name written exactly once.