Custom form in the Admin: how to make it look the same as Django's Admin forms?

I have a Django application which handles license codes, so there’s a model and a modeladmin, and that all works. But I want to add a custom page to the admin interface to generate a whole bunch of license codes and while everything is working, it looks really bad.

So first of all to get a new button in the top right, I have a custom change_list.html template for this specific model:

{% extends "admin/change_list.html" %}

{% block object-tools-items %}
  {{ block.super }}
  <li><a href="generate/" class="addlink">Generate Codes</a></li>
{% endblock %}

This makes the extra button show up in the top right of the list of items. Clicking on that button opens a new page, which I created with this code:

@admin.register(RedeemCode)
class RedeemCodeAdmin(admin.ModelAdmin):
    # [...the usual admin config...]

    def get_urls(self):
        return [
            path("generate/", self.admin_site.admin_view(self.generate_codes), name="generate-codes"),
        ] + super().get_urls()

    def generate_codes(self, request):
        class GenerateCodeForm(forms.Form):
            product = forms.ModelChoiceField(queryset=ProductVariant.objects.all())
            partner = forms.ModelChoiceField(queryset=Partner.objects.all())
            comment = forms.CharField()
            count = forms.IntegerField(min_value=1, initial=1)
            for_email = forms.CharField()
            export_csv = forms.BooleanField(required=False, label="Export generated codes to CSV")

        if request.method == "POST":
            form = GenerateCodeForm(request.POST)
            if form.is_valid():
                print(form.cleaned_data)
                return HttpResponseRedirect("/admin/shop/redeemcode/")

        context = dict(
            # Include common variables for rendering the admin template.
            self.admin_site.each_context(request),
            opts=RedeemCode._meta,
            title="Generate Codes",
            form=GenerateCodeForm(),
        )

        return TemplateResponse(request, "admin/shop/redeemcode/generate_codes.html", context)

And my generate_codes.html template:

{% extends "admin/base_site.html" %}
{% load i18n admin_urls %}

{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">{% translate 'Home' %}</a>
&rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a>
&rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
&rsaquo; {% translate 'Generate Codes' %}
</div>
{% endblock %}

{% block content %}
<form action="." method="post">
    {% csrf_token %}
    {{ form }}
    <input type="submit" value="Generate codes">
</form>
{% endblock %}

The result looks like this:

enter image description here

It all works perfectly fine but that’s not very good looking. Compared to normal add/edit admin forms the labels don’t line up, text input fields are a lot smaller, the checkbox label is shown in front instead of after the checkbox, there’s no padding between the form and the submit button, etc.

So the question is how I can reuse the admin look and feel for a custom form on a custom page.

That’s up to you to manage. You could look at how the various admin widgets are rendered and reuse those templates. You can also fully customize your form rendering

You could also take a look at Crispy Forms. That’s what we use and we’re really satisfied with it.

I know it has been a couple of months, but i encourage you to try django-admin-action-forms. I think it does exactly what you are looking for. The form page matches the Django admin style, without the need to manually extending templates etc.

Hope you find it useful! :slight_smile:

Sounds interesting, but isn’t this specifically (and only) for building admin actions? As in, actions that work on a selection of rows? I actually needed to create a form that’s not an admin action, not related to anything you first select, if that makes sense.

I will check out the project’s code to see how it matches the Django Admin styling, as that was the part I had problems with. Thanks!

I see what you mean. There is also a django-no-queryset-admin-actions library, which provides a way to create actions without selecting any objects. From the code you provided, I believe you could add a button that would submit an empty form with action and csrf set, which would direct you to a action form page, the example is provided in README.

Both libs work together and are fully compatible with one another.