djangodjango-formsdjango-admin

Custom form in the Django Admin: how to make it look the same as the 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:

enter image description here

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

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.


Solution

  • One year later, but what about this (tested with Django 5.1.1):

    {% extends "admin/base_site.html" %}
    
    {# added static to load #}
    {% load i18n admin_urls static %}
    
    {# add admin forms css #}
    {% block extrastyle %}{{ block.super }}<link rel="stylesheet" href="{% static "admin/css/forms.css" %}">{% endblock %}
    
    
    {% block content %}
        <form action="." method="post">
            {% csrf_token %}
    
            {# generate form #}
            <div>
                <fieldset class="module aligned">
                    {% for field in form %}
                        <div class="form-row">
                            <div>
                                <div class="flex-container">
                                    {{ field.label_tag  }}
                                    {{ field }}
                                </div>
                            </div>
                        </div>
                    {% endfor %}
                </fieldset>
            </div>
            <input type="submit" value="Generate codes">
        </form>
    {% endblock %}