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.