Chapter 1 · What Django is

1.4 The design philosophies, in the code

Django’s design philosophies are a page of its documentation naming the principles it is built by: loose coupling, less code, quick development, don’t repeat yourself, explicit over implicit, consistency, and principles of their own for the models, the database API, URLs, templates, views and the cache (docs/misc/design-philosophies.txt). Most are kept by a mechanism in the source, and in places the code bends one.

Model, template and view

Django’s names for its parts are not those of the pattern called MVC, and its FAQ says why. In Django’s reading a “view” describes “which data you see, not how you see it”, so a view is the Python function called for a URL; how the data looks is the template’s; and the controller is “probably the framework itself: the machinery that sends a request to the appropriate view, according to the Django URL configuration”. So model, template and view: “MTV”. In the code that machinery is the handler, whose centre resolves the path and calls the view (BaseHandler._get_response; Handlers and middleware).

Loose coupling, read in the imports

The design philosophies want the layers not to “know” about each other “unless absolutely necessary”, and give three examples. The imports bear each out, and each has its exception.

“The template system knows nothing about web requests.” The engine, the lexer, the parser and the context import nothing from django/http/; the bridge does, SimpleTemplateResponse, an HttpResponse that renders a template. A request reaches templates all the same, without an import: RequestContext holds one and hands it to every context processor, a callable that returns variables for the template from the request.

“The database layer knows nothing about data display.” django/db/ imports nothing from the template system, django/http/ or the views, but its model fields import django.forms, so that each can make its own form field.

“The view system doesn’t care which template system a programmer uses.” The render shortcut and TemplateResponse ask each engine in TEMPLATES in turn, so a view works the same over a Jinja2 backend. Some of Django’s own pages are the exception, among them the debug pages and the fallback when no "404.html" is found: each is rendered by an Engine made in the code, in the Django template language whatever TEMPLATES names.

Which part imports which is The parts of Django, and what each leans on.

The model as the one place

“Don’t repeat yourself” asks that “every distinct concept and/or piece of data should live in one, and only one, place”, and “less code” that Django use “Python’s dynamic capabilities, such as introspection”. The model is where both are kept. A model is a class statement, and ModelBase.__new__ reads it: it collects the fields, reads the inner Meta, and builds the Options kept as Model._meta. What else needs the model derives from _meta and is not told again. A ModelForm gets its fields by asking each model field for its form field, and the admin does the same through a hook of its own: a registered model has an editing interface without a line of form code, which is “quick development” at its plainest. The schema is written twice, the second time by a program: makemigrations compares the models with the state the existing migration files describe and writes the difference as new files, so the second copy is a history of the models’ changes, file by file.

The design philosophies’ models section adds Active Record: a model “should encapsulate every aspect of an ‘object’”. An instance holds its row’s values as attributes and writes itself with Model.save; its class holds the human-readable name and the default ordering in Meta (Options; Models and fields).

Explicit, and the names Django supplies

“Explicit is better than implicit” is Python’s, and the design philosophies read it as “Django shouldn’t do too much ‘magic.’” For models they say a field should not assume behaviours “based solely on the name of the field”. Nor does one: whether a field is the primary key, may be null or must be unique is given to it as an argument.

Explicitness is held less strictly the other way: a field takes no behaviour from its name, but Django gives a model names it did not declare. A model that declares no primary key, and has no parent to take one from, is given a field called "id", of the class DEFAULT_AUTO_FIELD names unless its application’s AppConfig.default_auto_field names another. One that has no manager, its own or inherited, is given a manager called "objects". A field with choices gives its model a method named after the field, get_status_display for a field called status. A foreign key puts on the model at its other end an accessor named after the first model, with “_set” after it. Each gives way to a declaration: a primary key, a manager, a method of that name, a related_name.

The documentation calls one mechanism “black magic” in so many words: to record which templates a view rendered, the test environment replaces Template._render with a function that sends template_rendered, a patching that “only happens during test running”.

The database API

“SQL efficiency” asks that statements run “as few times as possible”. Setting an attribute changes only the instance; nothing is written until Model.save is called, “rather than the framework saving things behind the scenes silently”. A queryset sends nothing while it is built, runs its query when its results are first needed, and keeps them (QuerySet._fetch_all; QuerySets).

Relations are where queries multiply. Reading a foreign key whose object is not loaded sends a query for that one object, so a loop that reads it from many instances sends a query for each. That is the default fetch mode, the rule a queryset gives its instances for reading a field they have not loaded. Another, FETCH_PEERS, the “optional performance booster” of “SQL efficiency”, fetches the object for every instance the same queryset produced, in one query (django/db/models/fetch_modes.py; Deferred fields and fetch modes in a queryset).

“Terse, powerful syntax”: the double underscores of a keyword argument name a path through relations, and Query.setup_joins turns it into joins. The API should also “not rely on importing other modules or helper objects”, and keyword arguments need none; but Q, for conditions joined by OR in one filter, and F, for one column compared with another, are helper objects imported from django.db.models.

The “option to drop into raw SQL easily” is a call away: QuerySet.raw maps the rows of an SQL query onto instances, RawSQL puts a fragment in an expression, and the connection’s cursor runs anything.

URLs

URLs should not be “coupled to the underlying Python code”. A URLconf maps a route to a callable, and code that needs a URL asks for it by the pattern’s name: reverse walks the resolver back from the name to a path (reverse; URL routing). include mounts an application’s patterns under any prefix, and an instance namespace lets a URL be reversed in either of two mounts of one application. “Definitive URLs” asks Django to “normalize” URLs, since search-engine robots would treat a path with and without a trailing slash as separate pages. Its answer is the APPEND_SLASH setting: when a path without a trailing slash matches no pattern and has produced a 404, and the path with one would resolve to a view not marked no_append_slash, CommonMiddleware redirects to it.

The template system

“Separate logic from presentation”: a template is not Python and cannot run it. The lexer splits it into text, variables, tags and comments (Lexer.tokenize; Templates); a tag is a name looked up among the registered libraries; a variable is a dotted path, and a callable met on the path is never given arguments: a template can call BaseManager.all but cannot pass a method anything.

“Safety and security” asks that a template be unable to run “commands that delete database records”. The guard is in the same method: a callable whose attribute "alters_data" is true is not called, and renders as the engine’s string_if_invalid. The database layer sets the flag on its own writes, Model.save, QuerySet.delete, QuerySet.update and the related managers’, and the mixin AltersData carries it onto a subclass’s override. Reading is allowed: a {% for %} over a queryset takes its length, which runs the query.

Inheritance, against redundancy, is {% extends %}. Custom tags and filters, the “extensibility”, are Python registered in a Library. “Be decoupled from HTML” is half kept: the engine renders any text, but its autoescaping, on by default, is HTML’s, so a "<" in a value comes out as an entity unless the value is marked safe or autoescaping is turned off.

Views and the request

“Writing a view should be as simple as writing a Python function”, and a view is any callable that takes a request and returns a response. A class-based view enters the URLconf as the function View.as_view returns, which makes an instance for each request and calls View.dispatch (View.as_view; Views). The request is an argument, not a global, so a test can build one with RequestFactory and call a view directly. What Django derives from the request is another matter: the active language, the URLconf and the script prefix, the path at which the project is mounted, are kept in state local to the thread or task serving it, set by the handler and the middleware, and read by gettext and reverse without the request.

“GET and POST are distinct.” GET is the parsed query string, whatever the method; POST is the parsed form body of a “POST” request and empty for any other method. View.dispatch sends each method in View.http_method_names to the view’s method of that name in lower case, and require_http_methods answers a method not listed with a 405.

The cache

The cache’s “less code” asks that the framework’s code around a backend be kept “to the absolute minimum, especially for get() operations”. cache is a proxy that hands each attribute to the backend of the default alias, so its get is the backend’s own method: BaseCache.get only raises NotImplementedError, and the local-memory backend’s makes the key and reads its store (LocMemCache.get; Caching, files, mail, signals and tasks). Consistency is that every backend Django ships is a BaseCache; extensibility, that "KEY_FUNCTION" replaces the function that makes a key. The same consistency, a base class and a backend named in a setting, runs through the database, mail, storage and sessions: How Django is extended.