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:
https://code.djangoproject.com/ticket/30577#comment:10

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!

2 Likes

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)
        super().__init__(attrs)

    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.

You would be my hero if this were implemented, it’s really a total nightmare at present. I’ve been trying to do this for django-svelte-jsoneditor and for django-gcp file storage, but it’s basically impossible to do in a way that doesn’t require a huge mess from the user.

1 Like

Hello, we have a use case for this feature in OpenWISP, precisely in openwisp-controller.config.admin.TemplateForm. We use a custom widget to render the field value (JSON) using a JSONSchema form generator.

In the ticket. it is suggested to set the read_only attribute on the widget instance. I want to highlight that this workaround wouldn’t work on the Django’s main branch after this commit which removes the check for read_only widget.

1 Like

@sarahboyce I created a demo project of the difference in using custom widget for readonly field in Django 5.2 and development version of Django.

Django 5.2: custom widget used for readonly field


Development version: default admin rendering used

2 Likes

Having originally created the ticket as nice to have functionality, it’s pretty clear there are some important valid use cases.

I’ve made hacked solutions to the problem in existing projects but it seems like those might not work going forward.

Appreciate the examples of use cases others have put forward! Hopefully this can work its way in to core functionality.

2 Likes

Thank you @pandafy for reopening the ticket in the django bug tracker and providing very clear instructions that are easy to replicate.

I encourage everyone who is affected by this problem to follow the discussion there, I look forward to getting this resolved.