Admin/htmx: Render single modelform field

Hello, I am trying to make the ModelForm rendered via the ModelAdmin show or hide one field based on the status of another field’s checkbox. I am also trying to learn and apply htmx rather than using javascript.

I have figured out how to do this for the add view. However, I am having trouble obtaining the form and having it render the one field I need in a change view.

I have added the URL and view that htmx uses to get the field html.

Here is my ModelAdmin. It will include/exclude the database field depending on the status of the db_required field, which is provided by htmx. In the db_required_view, I return an empty response if db_required is not in the htmx request, or I render the database form field. For the add view, self.model is None and the form is correctly returned. However, the form is not being built correctly when using this from the change view’s form.

class ApplicationAdmin(admin.ModelAdmin):
    model = Application
    list_display = ("name", "business_unit", "application_link", "notes")
    inlines = [ApplicationComponentInline]
    autocomplete_fields = ["business_unit"]
    form = ApplicationModelForm

    def get_urls(self):
        urls = super().get_urls()
        my_urls = [
            path(
                "add/db_required/",
                self.admin_site.admin_view(self.db_required_view),
                name="application-add-db_required",
            ),
            path(
                "<uuid:application_id>/change/db_required/",
                self.admin_site.admin_view(self.db_required_view),
                name="application-change-db_required",
            ),
        ]
        return my_urls + urls

    def get_exclude(self, request, obj=None):
        if (
            not request.GET.get("db_required")
            and not request.POST.get("db_required")
            and not obj.db_required
        ):
            print("Excluding 'database' field.")
            self.exclude = ("database",)
        else:
            self.exclude = tuple()
        return super().get_exclude(request, obj)

    def db_required_view(self, request, application_id=None):
        if request.GET.get("db_required"):
            context = {
                **self.admin_site.each_context(request),
                "form": self.get_form(request, self.model),
            }
            return TemplateResponse(
                request,
                "admin/power_application_usage_api/application/db_required_field.html",
                context,
            )
        else:
            return reswap(retarget(HttpResponse(""), "div.field-database"), "outerHTML")

And here is my template that renders the database field.

<div class="form-row field-database">
    <div>
        <label for="id_database">Database:</label>
        {{ form.database }}
        <div class="help" id="id_database_helptext">
            If your application or tool does require a database,
            please select which one.
        </div>
    </div>
</div>

Any pointers would be greatly appreciated. Also, if there is a better way to accomplish rendering a single ModelForm field, I would appreciate seeing that as well.

Thanks in advance.

Personally, I wouldn’t even think of issuing a request to render a single field for a situation like this.

If I had to do something like this, I’d create a small JavaScript function that can be added to the admin page as a media asset. This function would toggle that element between “enabled & visible” and “disabled & hidden” based upon the value of the other field.

I know that you want to use HTMX, but if you’re open to another solution, and you don’t want to actually write the javascript, Alpine.js can be handy in this situation.

Hi @KenWhitesell , I realize I am probably making this harder than necessary and I may eventually go to using javascript. I was trying to use this as a learning exercise for htmx and I do realize I am making it harder than it needs to be. Thanks for the response.

Hi @leandrodesouzadev, thanks for the response. I have heard of Alpine.js and will probably look into it once my stubbornness runs out and I abadon htmx in this use-case.

I understand - I’m a big fan of HTMX myself.

However, when it comes to the addition or removal of fields in forms, it potentially adds another layer of complexity, particularly when form validation is involved on the server side. It’s one thing to have HTMX add a field to a form. It’s something else for that form to be submitted and to have the view be able to validate that modified form.

Just in case someone else is silly enough to try this…I made some progress today. I stepped through the _changeform_view method of ModelAdmin in django.contrib.admin.options.py and had a key realization. ModelAdmin.get_form returns a form class, not the instance. I needed to create the instance before I could render it. I changed my db_required_view to the following and was able to successfully render the database field.

    def db_required_view(self, request, application_id=None):
        if request.GET.get("db_required"):
            ModelForm = self.get_form(request, self.model, change=True)
            obj = self.get_object(request, application_id)
            context = {
                **self.admin_site.each_context(request),
                "form": ModelForm(
                    {"database": obj.database if obj else None},
                    None,
                    obj,
                ),
            }
            return TemplateResponse(
                request,
                "admin/power_application_usage_api/application/db_required_field.html",
                context,
            )
        else:
            return reswap(retarget(HttpResponse(""), "div.field-database"), "outerHTML")