Hitul Mistry presented a talk at Europython 2022, Dublin, Ireland on the Walk-through of Django internals.
Summary of talk
Django can be started with a simple command
python manage.py runserver
Django performs the below steps to start the server.
Find management commands
django.core.management.ManagementUtility
has to execute()
() method that gets called.ManagementUtility.execute()
is the front door to execute any management commands.Parse arguments
CommandParser.parse_args()
to parse the command line arguments.ArgumentParser
class and overrides the parse_args
method. The overridden method makes Django error messages more relevant.python manage.py runserver
then django also tries to recommend the best match runserver
.Load settings
DJANGO_SETTINGS_MODULE
environment variable. It gets initialized in the manage.py module.DJANGO_SETTINGS_MODULE
.Load App Configuration and Logging
settings.py.
Django uses Python’s logging module.django.apps.registry.Apps.
In case it finds two classes, it tried to find one which has default marked. If Django finds more than one class marked default, it raises an exception.Start HTTP Server
basehttp
module (django.core.servers.basehttp) run()
method checks for threading and runs the server.socketserver.ThreadingMixIn
is used for threading and
wsgiref.simple_server.WSGIServer
for HTTPServer.Auto reloader
Lets see, simple HTTP request,
curl --location --request POST 'http://localhost:8000/test/' \
--header 'Content-Type: application/json' \
--data-raw '{
"key": "value"
}'
Above request is converted into a raw request as below while traveling on the network. We used wireshark to capture below raw request data.
Request gets converted into raw text.
Frame 1: 372 bytes on wire (2976 bits), 372 bytes captured (2976 bits) on interface lo, id 0
Ethernet II, Src: 00:00:00_00:00:00 (00:00:00:00:00:00), Dst: 00:00:00_00:00:00 (00:00:00:00:00:00)
Internet Protocol Version 4, Src: 127.0.0.1, Dst: 127.0.0.1
Transmission Control Protocol, Src Port: 40806, Dst Port: 8000, Seq: 1, Ack: 1, Len: 306
Hypertext Transfer Protocol
POST /test/ HTTP/1.1\r\n
[Expert Info (Chat/Sequence): POST /test/ HTTP/1.1\r\n]
[POST /test/ HTTP/1.1\r\n]
[Severity level: Chat]
[Group: Sequence]
Request Method: POST
Request URI: /test/
Request Version: HTTP/1.1
Content-Type: application/json\r\n
User-Agent: PostmanRuntime/7.26.5\r\n
Accept: */*\r\n
Cache-Control: no-cache\r\n
Postman-Token: f821ed79-842b-4681-816a-a06f593a4c98\r\n
Host: localhost:8000\r\n
Accept-Encoding: gzip, deflate, br\r\n
Connection: keep-alive\r\n
Content-Length: 22\r\n
[Content length: 22]
\r\n
[Full request URI: http://localhost:8000/test/]
[HTTP request 1/1]
[Response in frame: 50]
File Data: 22 bytes
JavaScript Object Notation: application/json
{"key": "value"}
Let’s see how a simple http request gets served by Django.
HTTP clients sends the HTTP request in HTTP protocol. Request received by webserver such as Gunicorn, uWSGI, Nginx, runserver(django). Webserver and Django communicate with WSGI protocol.
Inside the main root project application, there is a wsgi.py module. Module has method called get_wsgi_application()
which internally calling WSGIHandler().
WSGIHandler():
def __init__(self, *args, **kwargs):
# initialization
def __call__(self, environ, start_response):
# Gets called whenever request comes.
WSGIHandler() has two methods init and call methods. init gets called on HTTP server startup and call gets called whenever any web HTTP request comes.
Wsgiref parses the raw request we show in the request and converts them into the parsed dictionary.
Dictionary contains all the request parameters.
{ 'HTTPACCEPT': '/_', 'HTTP_ACCEPT_ENCODING': 'gzip, deflate, br', 'HTTP_CACHE_CONTROL': 'no-cache', 'HTTP_CONNECTION': 'keep-alive', 'HTTP_HOST': 'localhost:8000', ... 'wsgi.errors': <_io.TextIOWrapper name='
call method calls get_response method which is passed as a function argument. Internally it matches route, execute middlewares, executes view. On each view and middleware calling Django has exception handling implemented.
ORM has components,
class College(models.Model):
name = models.CharField(max_length=100)
address = models.CharField(max_length=400)
class Student(models.Model):
Name = models.CharField(max_length=200)
enr_no = models.IntegerField()
college = models.ForeignKey(College)
If we import models and try to print attributes in the model then we get below result.
models.College.**dict**
mappingproxy({'__module__': 'debug.models',
'__doc__': 'College(id, name, address)',
'_meta': <Options for College>,
'DoesNotExist': debug.models.College.DoesNotExist,
'MultipleObjectsReturned': debug.models.College.MultipleObjectsReturned,
'name': <django.db.models.query_utils.DeferredAttribute at 0x7fdad9b6ed30>,
'address': <django.db.models.query_utils.DeferredAttribute at 0x7fdad9b6ed68>,
'id': <django.db.models.query_utils.DeferredAttribute at 0x7fdad9b6ee80>,
'objects': <django.db.models.manager.ManagerDescriptor at 0x7fdad9b6eef0>,
'student_set': <django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor at 0x7fdad9b78588>})
class ModelState:
db = None
adding = True
fields_cache = ModelStateFieldsCacheDescriptor()
student_instance = models.Student.objects.filter().last()
student_instance.name = “Hitul Mistry”
student_instance.save()
fields_cache
stores the foreign key objects on calling prefetch_related()
.from_db
method gets called on django.db.models.base.Model
before passing the results from the database results to instance. It initializes model with values received from database.Fields(models.IntegerField, models.CharField etc.)
which inherited from django.db.models.Field
.get_internal_type
will check into the database’s internal type to DB type mappings. data_types
mapping will be found in the DatabaseWrapper. It is generally used in migrations for building the query. In case it could not find the value from the mapping then db_type method will be called.data_types = {
‘Autofield’: ‘serial’,
‘CharField’: ‘varchar(%(max_length)s)’,
…
}
from_db_value()
converts value to python type from database type. Example, timezone conversion to given timezone).get_db_prep_save()
called before saving into database.debug_models.College.objects.create(
name="ABC College",
address="Abc college campus, rolland street road."
)
from_queryset
method dynamically builds the inheritance class.django.db.models.sql.Query
is inherited by django.db.models.sql.subqueries
, it has different query classes such as InsertQuery, AggregateQuery, UpdateQuery etc.insert_values
, add_update_fields
, add_related_update
, add_filter
etc.(django.db.models.sql.compiler)
as_sql
method, which prepares the query and later executed by execute_query
method.queryset.iterator(chunksize=100)
should be used whenever required. Prefetch_related is not supported.django.db.models.sql.Query
class has different methods add_filter
, add_q
, add_select_related
, add_annotation
, add_extra
, add_ordering
etc to hold the data.How do Django does query in query chaining?
debug_models.College.objects.filter(
name="ABC College"
).filter(address__contains=”abc”)
as_sql
method in SQLCompiler forms the SQL query based on the parameters passed.debug_models.College.objects.filter(
name="ABC College"
).update(name=”BBC College”)
django.db.models.sql.subqueries.UpdateQuery
has add_update_values, add_related_update
etc which stores the data in Query for the compiler.SQLUpdateCompile
r will form the query and DatabaseWrapper will execute the query on database.debug_models.College.objects.filter(
name="ABC College"
).delete()
DELETE FROM "debug_college" WHERE "debug_college"."id" IN (36, 35, 34, 33, 32, 31, 30);
django.db.models.deletion.Collector
class collects the objects to be deleted(collect()
), deleting the objects(delete()
) and sending the pre and post delete signals.SQLDeleteCompiler
forms the SQL query for the actual delete.CASCADE, PROTECT, RESTRICT
.django.db.backends.<database>.base.py
operators = {
'exact': '= %s',
'iexact': '= UPPER(%s)',
'contains': 'LIKE %s',
…
}
pattern_ops = {
'contains': "LIKE '%%' || {} || '%%'",
'icontains': "LIKE '%%' || UPPER({}) ||
'%%'",
'startswith': "LIKE {} || '%%'"
…
}
data_types = {
'AutoField': 'serial',
'BigAutoField': 'bigserial',
'BinaryField': 'bytea',
...
}
django.db.backends.<database>.features.py
class DatabaseFeatures(BaseDatabaseFeatures):
allows_group_by_selected_pks = True
can_return_columns_from_insert = True
can_return_rows_from_bulk_insert = True
has_real_datatype = True
….
django.db.backends.<database>.operations.py
Ahmedabad
K P Epitome, Block B, Office No: 714, Near DAV International School, Makarba, Ahmedabad-380051, Gujarat.
+91 99747 29554
Mumbai
WeWork, Enam Sambhav C-20, G Block,Bandra- Kurla Complex, MUMBAI-400051, Maharashtra.
+91 99747 29554
Stockholm
Bäverbäcksgränd 10 12462 Bandhagen, Stockholm, Sweden.
+46 72789 9039