Django, DjangoREST and their components

I was chatting to a few friends recently about my enjoyment of using Django and DjangoREST. I have been using both for a few years in both an industrial setting and on a side-project, the same side-project that led me to write my blog post about containerisation.

I was praising Django, yet none of those that I was speaking to had any experience with Django. I wanted to send them a small primer on Django and DjangoREST, yet I did not know of one and so I have decided that I should publish one myself.

I want to be clear that this blog is just an overview and a whistle-stop tour of Django. I will be simplifying some concepts; I won't be going down into the nitty-gritty.

The purpose of this blog is for someone to be able to scan through it and gain insight into Django, DjangoREST and their components.

I am splitting it roughly into two sections: Django and DjangoREST.

Onwards!

Django

Django is an opinionated "batteries-included" web framework for Python. A newspaper organisation created Django to serve news articles as web-pages, and it really feels like it would be a great fit in that setting[1].

The core concepts to understand with Django is the model-view-template (MVT) architecture and its components, which I will now run through.

Models and QuerySets

Models are one's data layer, as in MVC architectures. Django features object-relation mapping (ORM) tools which allows one to map Python classes - called models - to tables within one of the supported databases. Django builds database migration files from these models. Django then exports these to SQL migration Python files; the files apply queries against one's databases.

I have found using Django models and migrations quite easy, compared to using a lighter-weight framework where one must manage their own migrations or use a more complicated separate ORM tool.

Defined models expose functions, such as object.all(), which return QuerySets. QuerySets are collections of objects - of the same type as defined in one's models - that retrieve data from a database. Each model will have a set of automatically generated functions that return QuerySets. The data from these QuerySets can then be utilised in your application.

Views and Mixins

Views are functions that take in HTTP requests and return an HTTP response. They could return HTML, errors, or anything else that you might return in an HTTP response. Each view serves a specific purpose, usually serving a specific web-page of one's website.

Views are associated with specific URLs and HTTP endpoints and are displayed when a user hits those specific endpoints. Query parameters, such as primary keys or database IDs, can be included within the defined URLs to render specific entries from a database when combining views with templates.

Views in Django can be written as individual functions or be class based. When writing class-based views "mixins" can be used. Mixins are small reusable classes with common behaviour that can be used across views.

Templates

Despite coming last in the MVT acronym, Templates are, in a sense, the glue that binds the Models and Views together in the Django framework.

Templates allow for dynamic generation of HTML on the server. Template engines allow for programming constructs, such as loops and if statements, to be added to HTML and thus allow for more sophisticated rendering. Templates are not unique to Django, but Django only officially supports Django Template Language (DTL) and Jinga2. Other third-party template engines are unofficially supported.

Templates receive data from QuerySets when rendering. Templates are only served to a user from a view when a user hits the endpoint URL that is associated with that view. The view requests the data from the database using models and returns QuerySets. The QuerySets are then passed to a template which is returned to the user.

In this sense, you can see that a request in Django's MVT architecture has the following flow: Request → View → Models their QuerySets → View → Template → Response

A visualisation of the flow of requests through Django.
A visualisation of the flow of requests through Django.

DjangoREST

Creating a classic HTTP REST API with vanilla Django would be difficult. A developer would need to handle a great deal of complex systems, such as authentication and serialization; it would rapidly become a nightmare to maintain. As you can likely guess, DjangoREST solves this. I will provide an overview of DjangoREST's core concepts.

Serializers

View utilise Serializers to define and validate the properties of a request's body. Serializers define fields, their data types and whether they are mandatory. Serializers validate the format of in-bound data and convert it to Python, allowing Python to interact with the request body data. Likewise, Serializers transform outbound Python objects into the format that the HTTP response is expecting.

Generic views

Generic Views are pre-built views that save a developer from having to build multiple end-points for common patterns, which developers would be required to do in simpler frameworks. For example, rather than having to write individual end-points for "GET", "PUT", "PATCH" and "DELETE" for a single model a developer may use the "RetrieveUpdateDestroyAPIView" generic view, which provides method handlers for each of these functions.

Developers still have to write a view when using generic views. It is often just a case of plugging in the serializer for the appropriate model, and determining which QuerySet and look-up field the view will used for the interactions on that query set, rather than writing the request handler logic for each HTTP method.

Generic Views are a feature of DjangoREST that I have found to be an incredible productivity booster.

ViewSets and Routers

ViewSets abstract away HTTP method handlers and URL assignment. ViewSets provide operation-based functions such as "retrieve" or "update", rather than functions named like HTTP methods.

ViewSets also focus on a whole model rather than individual end-points. While Generic views simplify creating end-points for a single model, such as create end-points for a list view or operations on a single instance of a model, ViewSets abstract away operations for the whole of the model - both individual instances and collections - within a single class.

Routers are used to automatically generate URLs for ViewSets which provide a standardised end-point structure for an API built using DjangoREST.

I will add a disclaimer here: I have rarely used ViewSets, preferring the explicitness of generic views. In my side-project in particular - due to spending infrequent time on it - I felt that ViewSets and Routers abstracted too much away. Indeed, the DjangoREST documentation also raises this as a trade-off with ViewSets[2].

ViewSets abstract away code, minimise repetition and provide a standardised structure to API end-points whilst being less explicit in what they do.

Closing Thoughts

I have thoroughly enjoyed my time working with Django and DjangoREST. The learning curve of a batteries included framework was worth it for the productivity boost.

I hope that this post serves well as a rapid overview of the Django and DjangoREST frameworks. Much of it was written and edited from the Django[3] and DjangoREST[4] documentation; if you were looking for a deeper diver then the next destinations on your list should be those.


Sources:

[1] https://www.quora.com/What-is-the-history-of-the-Django-web-framework-Why-has-it-been-described-as-developed-in-a-newsroom/answer/Simon-Willison

[2] https://www.django-rest-framework.org/tutorial/6-viewsets-and-routers/#trade-offs-between-views-vs-viewsets

[3] https://www.djangoproject.com/

[4] https://www.django-rest-framework.org/