Django comes with a fantastic admin area out of the box. Registering and customizing models is a breath of fresh air with its class based structure.
For the unaware, a change form template is shown when you edit/change an existing model instance, or an object.
Setting up context for this blog post.
First: I have set up a base model with soft deletion capabilities by following the excellent tutorial by Adrienne Domingus titled ‘Soft Deletion in Django‘. Written originally in 2017 and last updated in 2020, this is super useful and a highly recommended read. I got to see a ton of nice, production-quality patterns here.
The field is not editable (editable=False
) as per the model definition. It is not shown in any form. The only way to interact with this field is to use class methods.
class SoftDeletionModel(models.Model):
deleted_at = models.DateTimeField(
blank=False, null=True, default=None, editable=False
)
Second: another tutorial-of-sorts I followed was from the Django Admin Cookbook. It turned up several times in my searches and is generally helpful to point me in the right direction, if nothing else. However, it doesn’t appear to have been updated since Django 2.0.
Its chapter, How to show an uneditable field in admin?, recommends the following solution:
If you have a field with
Django Admin Cookbook by Agiliq, retrieved 2021-05-24.editable=False
in your model, that field, by default, is hidden in the change page. This also happens with any field marked asauto_now
orauto_now_add
, because that sets theeditable=False
on these fields.
If you want these fields to show up on the change page, you can add them toreadonly_fields
.
They share the following snippet next as a demonstration:
@admin.register(Villain)
class VillainAdmin(admin.ModelAdmin, ExportCsvMixin):
# ...
readonly_fields = ["added_on"]
I would highly recommend reading the Django Admin Cookbook article first, before proceeding any further. For the most part, it should take care of your use-case.
The edge-case I encountered, or why the cookbook advice did not work for me.
In my case, I found the snippet to be incomplete. For context, I am running Django 3.2, though that has nothing has to do with the solution. It’s just good to mention for posterity!
As any good Django citizen would, I looked up the API for a ModelAdmin
on the Django documentation site, specifically for the readonly_fields
property, and found the following note:
Note that when specifying
ModelAdmin.fields
orModelAdmin.fieldsets
the read-only fields must be present to be shown (they are ignored otherwise).
This makes sense, given my code (continuing the example offered by Django Admin Cookbook):
@admin.register(Villain)
class VillainAdmin(admin.ModelAdmin, ExportCsvMixin):
# ...
fields = ("name",)
readonly_fields = ("deleted_at",)
Since I had defined a custom fields
property, I needed to have the model field on both my readonly_fields
property as well as the fields
property. Once I put this into action, everything worked as I expected.
Continuing the same example, the solution then is:
@admin.register(Villain)
class VillainAdmin(admin.ModelAdmin, ExportCsvMixin):
# ...
fields = ("name", "deleted_at",)
readonly_fields = ("deleted_at",)
And, that’s it!