Aug. 12, 2026

Nifty Django Feature: Admin Form Injection

published by Tim Schilling
in blog Better Simple
published date 2026-08-12
original entry Nifty Django Feature: Admin Form Injection

This feature is about reusing Django’s admin functionality where a pop-up window is opened, an action is taken and a new value is injected into the original window. This is most prominent in the admin when creating a model instance that has a foreign key that doesn’t exist yet. The user clicks the green plus sign which pops open a new window for the foreign key’s model. When the user fills out the form and presses save, the popup window disappears and the newly created instance is selected/populated on the original form view.

A visual walk through of admin form injection

So we’re on the same page, here’s the above workflow when creating a Nickname object that has a ForeignKey to Pet that needs to be created.

A screenshot of the create Nickname admin form with the Pet field missing. A screenshot of the create Nickname admin form with the Pet field missing.

When that green, plus icon is clicked, a new window pops up with the Pet form. A screenshot of the Pet form in a pop-up window. A screenshot of the Pet form in a pop-up window.

This form can be filled out as if we were creating a Pet instance. A screenshot of the Pet form that's populated in a pop-up window. A screenshot of the Pet form that’s populated in a pop-up window.

When it’s saved, we see the <select> element for Pet on the Nickname form has been updated to have “Madame” selected, our newly created Pet. A screenshot of the create Nickname admin form with the Pet field selected. A screenshot of the create Nickname admin form with the Pet field selected.

An example of how to use this

I think it’s best to show you the end product before going into the code. This will show a popup from the Nickname admin form which presents a few nickname choices. When the user selects one, it’ll inject the selected value into the Nickname form.

Here is the Nickname admin form we saw before, but this time we will click that “Generate nicknames” link. This link is something we will have to customize the NicknameModelAdmin to include later. A screenshot of the create Nickname admin form with the name field missing. A screenshot of the create Nickname admin form with the name field missing.

Clicking that link will cause a window to pop up. However, the content of this window will be entirely controlled by us. For this example, it shows three buttons to pick randomized nicknames.

A screenshot of the custom pop up window that has three buttons with different namename choices. A screenshot of the custom pop up window that has three buttons with different namename choices.

Clicking any of those buttons, in this case “Use Fuzzy Biscuit the Wise”, causes the pop up window to close and that value to be injected into the name field for the Nickname form.

A screenshot of the create Nickname admin form with the name field populated with Fuzzy Biscuit the Wise. A screenshot of the create Nickname admin form with the name field populated with Fuzzy Biscuit the Wise.

What I want you to takeaway is that you can create a view that does literally anything to arrive at some value and you can have that value injected into the admin form.

So let’s see what you have to do to wire that up.

Implementing a custom admin form injection

There are several things we need to do to implement this:

  1. Render the link to cause the pop-up window to appear.
  2. Implement the view, template and url routing to render the pop-up window.
  3. Pass the value from the pop-up window back to the original window.

The third point is where we re-use Django’s admin JavaScript functionality from the RelatedLookupObject.js file and where some of the hackiness will appear from.

First we need to render a link on our Nickname detail form view. This can be done with cool_nickname_link method below.

The syntax of the anchor element is important here. The id attribute uses a specific format that’s required by showRelatedObjectLookupPopup which is called in onclick.

The value lookup_id_name is defining what field on the Nickname form that should accept the value returned by the pop-up window. The lookup_ is a prefix for the JavaScript, while the remainder is the input’s id. This means the value will be injected into the Name field for the Nickname model.

from django.urls import reverse
from django.utils.html import format_html

@admin.register(Nickname)
class NicknameAdmin(admin.ModelAdmin):
    readonly_fields = ["cool_nickname_link"]

    @admin.display(description="Nickname tool")
    def cool_nickname_link(self, obj):
        # Add a link to our form that uses the admin's RelatedObjectLookup.js
        # function showRelatedObjectLookupPopup.
        return format_html(
            '<a'
            ' id="lookup_id_name"' # Inject the value into the name field
            ' href="{url}"'
            ' onclick="return showRelatedObjectLookupPopup(this);"'
            '>Generate nicknames</a>',
            url=reverse("admin:cool_nicknames"),
        )

Implement the view, template and urls

Rendering the content of the pop-up window may look familiar. It’s a basic template view. We wire that view into the urls for NicknameAdmin so that we can use reverse("admin:cool_nicknames") in cool_nickname_link.

class NicknameAdmin(admin.ModelAdmin):
    def cool_nicknames_view(self, request):
        """Show multiple nicknames to choose from."""
        return render(
            request, "admin/my_app/nickname/cool_nickname_popup.html",
            # is_popup = True prevents the navbar from rendering
            {"nicknames": random_nicknames(), "is_popup": True},
        )

    def get_urls(self):
        """Define the url patterns for the model admin"""
        return super().get_urls() + [
            path(
                "cool-nicknames/",
                # Protect view with standard admin permission checking
                self.admin_site.admin_view(self.cool_nicknames_view),
                name="cool_nicknames",
            ),
        ]

def random_nicknames() -> str:
    return [
        f"{random.choice(_ADJECTIVES)} " \
        f"{random.choice(_NOUNS)} " \
        f"{random.choice(_TITLES)}"
        for _ in range(3)
    ]

The template is extends the admin’s base_site.html template so it looks fairly integrated. The rest uses pure HTML to make the example easier to read.

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

{% block content %}
<h2>Cool nicknames</h2>
<ul>
  {% for nickname in nicknames %}
  <li>
  <!-- dismissRelatedLookupPopup is from the admin's RelatedObjectLookup.js -->
  <button
    onclick="opener.dismissRelatedLookupPopup(window, '{{ nickname|escapejs }}');"
    class="default"
  >Use {{ nickname }}</button>
  </li>
  {% endfor %}
</ul>
<button onclick="window.close();">Cancel</button>
{% endblock %}

The important piece is the onclick handler for the buttons.

We are calling the dismissRelatedLookupPopup function which Django provides, which will handle injecting the selected nickname into the Name field’s input. We can see that we’re actually calling opener.dismissRelatedLookupPopup which means this function is running on window that opened the pop-up.

And surprisingly, that’s it! If you’ve used Django, you likely agree that the Django admin is awesome. But with a little investigation, you can make that awesomeness work even harder for you. Understanding what RelatedLookupObject.js file expects so that you can use showRelatedObjectLookupPopup and dismissRelatedLookupPopup, you can build your own admin injection feature.


If you have thoughts, comments, or questions, please let me know. You can set up a meeting with me, or find me on the Fediverse, Django Discord server or use email.