Hello!
My second post here, I am happy that I have found this community
I was told that not many Django developers tweak the admin that much. I myself have not done it in the past for my small personal Django projects. There was simply no need.
It’s quite understandable, usually you don’t send users to the /admin, you build the UI for them to use and that is what Django is all about(at least from my perspective), admin for the admins/developers, UI for the users.
But what if only the basic CRUD operations need to be performed by the user and instead of building a custom UI with custom list/detail views/urls/templates/etc, you have none of that and the users are allowed to use the /admin for CRUD tasks? Users are able to delete/modify/add entries in the DB directly from the /admin with the built in CRUD functionality of the admin. Of course such app is not to be exposed to the external internet, it could used internally in the company, the primary point of entry to the web app is over the /admin.
If there is a need to customize the admin interface a bit, you can pop a jazzmin or unfold admin theme. Other basic tweaks can be done by following solutions described in Django Admin Docs or googling your problem, someone most likely has solved in the past.
In this scenario, users are happy, but developer is even happier, since without much effort at all there is a fully functioning CRUD site.
But DEVELOPER DOES NOT LAUGH THAT MUCH ANYMORE when the users start using the site over the /admin, and requests start coming in… all of a sudden this field has to have this color, that field has to be moved to the left a bit more, this field must be replaced with another field, this input field should have autocomplete functionality, there must be a custom button here or there, and the save button should not only save data when pressed, but also should calculate certain values taken from the currently opened instance and display results over there…
Let’s remember that developer did not built the Django admin by himself, he/she simply stood on the shoulder of giants that have created the Django admin. So now to fulfill users needs, the developer has to modify/override parts of a quite large Django admin codebase by himself and create all these custom solutions.
Instead of giving up on this idea of letting the users use the /admin, developer sees this as a great learning opportunity. Starts researching:
- youtube - no one goes deep in customizing the django admin
- google - small snippets of code for custom solutions of other people, but no general “this is how Django admin works” document/book/podcast, paper or a course
- Django docs - it writes all the attributes/methods, basically source code with nice comments and links, but still, a lot of it is not really understandable for the beginner, no clear picture how everything interconnects
- IDE debuggers - can any information be taken from them about the django admin by setting breakpoints in the admin.py for example?
- django debug toolbar - shows the templates that are being rendered for the django admin, okay, now we know what template has to be overridden to change the layout
Or these days one can ask chatgpt for help… for example the other day, while trying to solve this - Show all the fields in inline of the many-to-many model instead of a simple dropdown I have asked chatgpt for a solution and it gave me something I would not have figured out myself, for example this piece of code below, it helped me to override the built in dropdown field(impossible to use with thousands of entries to choose from) of the inline and implement autocomplete search:
class VesselInline(admin.TabularInline):
model = Deal.vessels.through
extra = 0
def get_formset(self, request, obj=None, **kwargs):
formset = super().get_formset(request, obj, **kwargs)
formset.form.base_fields['vessel'].widget = autocomplete.ModelSelect2(
url='vessel-autocomplete',
attrs={
'data-placeholder': 'Search for a vessel',
'data-minimum-input-length': 2
}
)
return formset
How did it know that get_formset
method has to be modified…?! And not get_form
or get_fieldsets
?
How did it know that formset
, then form
. then vessel field from base_fields
has to be taken and the widget
of it has to be overridden with the autocomplete functionality… Like the relationship between all these? Where is it written? Source code?
But if I look at InlineModelAdmin
class in contrib/admin/options.py and get_formset
method of it, it is not really clear(for me) that it has to be done this way… OOP is my weak spot for now, I should get better with it, perhaps things would get clearer. But do you see this as a solution by simply looking at get_formset
method? If yes, you are the person I must talk to!
Here is the source code of contrib/admin/options.py get_formset
method of InlineModelAdmin
class:
def get_formset(self, request, obj=None, **kwargs):
"""Return a BaseInlineFormSet class for use in admin add/change views."""
if "fields" in kwargs:
fields = kwargs.pop("fields")
else:
fields = flatten_fieldsets(self.get_fieldsets(request, obj))
excluded = self.get_exclude(request, obj)
exclude = [] if excluded is None else list(excluded)
exclude.extend(self.get_readonly_fields(request, obj))
if excluded is None and hasattr(self.form, "_meta") and self.form._meta.exclude:
# Take the custom ModelForm's Meta.exclude into account only if the
# InlineModelAdmin doesn't define its own.
exclude.extend(self.form._meta.exclude)
# If exclude is an empty list we use None, since that's the actual
# default.
exclude = exclude or None
can_delete = self.can_delete and self.has_delete_permission(request, obj)
defaults = {
"form": self.form,
"formset": self.formset,
"fk_name": self.fk_name,
"fields": fields,
"exclude": exclude,
"formfield_callback": partial(self.formfield_for_dbfield, request=request),
"extra": self.get_extra(request, obj, **kwargs),
"min_num": self.get_min_num(request, obj, **kwargs),
"max_num": self.get_max_num(request, obj, **kwargs),
"can_delete": can_delete,
**kwargs,
}
base_model_form = defaults["form"]
can_change = self.has_change_permission(request, obj) if request else True
can_add = self.has_add_permission(request, obj) if request else True
class DeleteProtectedModelForm(base_model_form):
# removed this part
defaults["form"] = DeleteProtectedModelForm
if defaults["fields"] is None and not modelform_defines_fields(
defaults["form"]
):
defaults["fields"] = forms.ALL_FIELDS
return inlineformset_factory(self.parent_model, self.model, **defaults)
def _get_form_for_get_fields(self, request, obj=None):
return self.get_formset(request, obj, fields=None).form
I don’t want to depend on chatgpt spitting out these solutions for me and me now knowing how to get them myself. But I imagine this kind of solution would come to me only after reading Django docs three times, becoming an expert in OOP and building many custom admin projects by myself over many years to start seeing the patterns of how it all interconnects.
- There is such site to understand class based views better https://ccbv.co.uk/, anything similar for django admin?
- there is such attempt to try to understand the admin - Diagram of django view lifecycle - #4 by Eleuthar, printing is a way to go?
Is DOING projects the only way to grasp the Django admin source code? Solving problems one by one with the help of chatgpt until you solve enough to know what is possible and how it is possible to customize the admin? Or there are some resources/tools/strategies you use to get a grasp of it and work on a daily basis?
As you might understood I am really interested in learning everything there is about Django(starting with the code that chatgpt has written above ) and I am really looking forward to hearing your thoughts and suggestions on how I can get better at customizing the Django admin!