Feature request (discussion): custom rendering for readonly fields in admin

Hi everyone, first time posting in the forum.

I’m here as suggested by @sarahboyce, looking forward to hearing your feedback on a feature suggestion for the contrib.admin module. In particular, as the title reads, regarding the ability to customize the read_only representation of model admin fields based on their type, without relying on the manually specified, explicit read_only parameter of widgets which only works if explicitly set in the admin form. Instead, I’d like to set a read_only representation only based on the type of the field, which in my example is DateTimeField.

Here is the link of the comment that got me posting here:

You can read through the thread to better understand the issue, and you will find a link to my PoC of a possible solution.

Here is the link to an actual workaround/monkeypatch ugly and full of problems that Django forced me to write to overcome the issue in my open source project:

The same code is present in my PoC from the aforementioned thread: GitHub - ldeluigi/django-poc-20240610

Let me know what you think and if there is a better way to achieve what I want, without explicitly writing custom forms with custom widgets for every single ocurrence of that field type in any admin form. Thanks for the attention!

Hi @deloo
@sarahboyce suggested to discuss the topic here. I encoutered the same issue as you and it’s a common topic on StackOverflow.

I noted that Django Admin for some reason handles the readonly in a special way. I guess that one reason is that Django itself is not really providing a generic way to render a readonly version of a widget. It seems like it’s common enough use-case to solve generically (IMO).

There are some special Readonly*Widget but custom logic is required to swap it out in the field or somewhere else.

Django already allows to customize widgets by overriding a template and/or context data. I would like to extend that idea and introduce a readonly_template_name.

What do you think?

I think it’s not 100% concruent of what you requested (no custom widgets) but I get the feeling that the topics are intertwined. You need a quick hook for small changes, I need a more sophisticated widget. In both cases, having a readonly field/obj, Django Admin is simply hardwired to type-matching known types only.

Example (it would be required to do that for all default widgets of Django and add a fallback for backwards compatibility):

class ReadonlyAwareInput(Input):
    template_name = "django/forms/widgets/input.html"
    readonly_template_name = "django/forms/widgets/readonly_input.html"  # new

    def __init__(self, attrs=None, is_readonly=False):
        self.is_readonly= is_readonly
        if attrs is not None:
            attrs = attrs.copy()
            self.input_type = attrs.pop("type", self.input_type)

    def get_context(self, name, value, attrs):
        context = super().get_context(name, value, attrs)
        context["widget"]["type"] = self.input_type
        context["is_readonly"] = self.is_readonly  # new for usage in template as well
        return context

    def render(self, name, value, attrs=None, renderer=None):
        """Render the widget as an HTML string."""
        context = self.get_context(name, value, attrs)
        if self.is_readonly:
            return self._render(self.readonly_template_name, context, renderer)
        return self._render(self.template_name, context, renderer)

class PreviewFileInput(ReadonlyAwareInput):
    template_name = "django/forms/widgets/preview_file.html"
    readonly_template_name = "django/forms/widgets/readonly_preview_file.html"  # override new readonly_template_name

    def get_context(self, name, value, attrs):
        .... # as usual

Firstly, thanks for replying and for sharing your case.
As I wrote in my comment under the original ticket post, I’m suggesting a very general approach that could allow overriding the readonly representation of any field of type T using a readonly_formfield_override kind of new setting for model admin.
This approach could be extended to support your usecase by allowing to map field classes not only to callables (which would be best for my case) but to more complex classes capable of exploiting the context:

    readonly_formfield_overrides = {
        FileField: PreviewFileInput,
        DateTimeField: date_time_field_readonly_formatter

where PreviewFileInput can be some “form field” used the way you suggest while date_time_field_readonly_formatter would be the simple callable corresponding to my needs.

This way, you can decouple your readonly logic and code from the read/write counterpart.
Let me know if you would like an API of this kind.