Who is afraid of Django admin?
This post won’t be another one on why you shouldn’t use Django admin.
I would like to talk about some concepts that a web application involves and how the admin interacts with them. Besides that, the internet is full of posts about the admin’s utility and, honestly, I still don’t have a fixed opinion on that subject. The main reason is that, for me, Django’s admin is not the villain, but rather the code that you create can be.
When I think of a web application, I usually think about it in three parts: data, states, and flows. The data is the simplest part to understand, it basically comprehends the information that has been persisted by the application. Using a sales system as example, the data would be a product’s price, quantity and dimensions. States would be meaningful readings and rules for the applications applied to a data set. In our example, we could think that the definition of whether a product is or isn’t available for sale would be two possible states. Finally, flows are the internal interactions of the application which manipulates the data transitioning from a state to a new one.
These web application’s distinct parts are what sometimes make the Django admin our best friend and our worst nightmare at other times. This is because the out of the box admin interacts only with a single part: it directly manipulates data. This feature, that seems harmless at a first glance, is something that can negatively influence the code of your application. This is what makes life easier for system developers and managers when they need to analyze data, but it can also break an application when badly used by an end user who doesn’t know the implementation of the application.
As a quick example, let’s think about a product shipping order. Let’s assume that a shipping order has an attribute that defines if it’s ready or not for shipping and a many to many relationship of many to many product shipments that also has this attribute. When processing the order, the system checks if all shipments are ready and, if so, the order is updated as ready. Now, imagine that a shipping order was processed and updated as ready to be shipped, but one of the products was missing in stock. Well, from the admin point of view, the solution is simple: he’ll just have to update that single product shipment as not ready, right?
If we think about the database, the answer would be yes, but if we think about the application domain, no. Although it has been updated as not ready, it’s order, that had already been processed, will remain flagged as ready. This scenario is just one example of several others that allows the developer to entry an invalid state within the application. These states are not covered by the domain or handled by its flows, so their existence is what often causes bugs and/or customer complaints. Allowing invalid states to be created is always one thing to be aware when using Django admin because, through Django’s ORM, it writes data directly to the database. By default it ignores all the application’s flow that you define and, as a consequence, can violate its state machine.
But, as I said at the beginning of the text, this is not solely admin’s problem, but also your code’s. The same problem of allowing invalid states can be reproduced in views created by the developer. A validation of a field may have been forgotten or an object may be being modified without considering its dependencies. This is especially true for applications that restrict your architecture to Django’s basic Model, View, and Template (or Serializer) structure. In this case, the difference between a custom view that validates a post and directly updates a model and the Django admin is that the admin, at least, already gives it to you free of implementation cost.
After all, the problem is not in using the admin, but in having a code that does not limit the changes of the application’s states as an exclusive responsibility from the application’s flows. In terms of architecture, they can be implemented in many ways, from the Domain Driven Design’s use case to the Facade design pattern. But, regardless of which implementation is being used, they’ll have a common goal: to explicit the application’s execution flows to, through the definition of responsibilities and encapsulation, prevent invalid states from being created through the direct manipulation of data.
In this way, I think of Django’s admin as being just another raw interface that needs to interact with the application’s flows with which I need to be more cautious.
The good thing is that it provides a simple API that allows me to prevent data from being written, to implement my custom admin views and custom admin actions, and to customize the deletion and creation of objects. Each one of them will, instead of their default behavior, integrate with my application’s flow implementation. Used in this way, admin ceases to be invasive and becomes just another, albeit limited, way of serving part of your application.
You could also argue that another possible way to limit admin’s undesired side effects would be to use the Django’s signals, such as post_save, post_delete etc, to control the execution flow. In my opinion this is definitely not an ideal solution. Why? Well, this is subject to an upcoming post…
Another admin’s blind spot shows up when we need to customize its appearance to achieve a better UX. Also because of its database coupling, its interface is very limited and bureaucratic and, although there are several thirdy-party-apps to customize, it is still difficult to extend its page structure, although it is doable. But this is also a chat for a more technical upcoming post about admin.
And what about you? What are your thoughts on Django admin? Have you used it on projects? What issues have you had and how did you solve them?