In the Django admin interface there is the nice ability to dynamically add new items to foreign key fields and it'd be nice to provide that same functionality in our own interfaces. Luckily since the admin interface already has all the functionality implemented we can be good little software engineers and reuse their code instead of reinventing the wheel.
The first thing we need to do is get access to ADMIN_MEDIA_PREFIX out of the settings so that we can include some of the admin javascript in our own interface, so we'll create a template context processor to handle that.
# context_processors.py from django.conf import settings def admin_media_prefix(request): return {'ADMIN_MEDIA_PREFIX': settings.ADMIN_MEDIA_PREFIX }
Make sure to include this new template context processor in your settings in the TEMPLATE_CONTEXT_PROCESSORS tuple.
For information on writing your own context processors: here
Then we just include the admin interface's RelatedObjectLookups.js into our template:
<script src="{{ ADMIN_MEDIA_PREFIX }}js/admin/RelatedObjectLookups.js"></script>
Now we create a custom widget that calls the javascript we just included and attach it to the foreignkey fields:
#widgets.py from django import forms from django.template.loader import render_to_string class SelectWithPopUp(forms.Select): model = None def __init__(self, model=None): self.model = model super(SelectWithPopUp, self).__init__() def render(self, name, *args, **kwargs): html = super(SelectWithPopUp, self).render(name, *args, **kwargs) if not self.model: self.model = name popupplus = render_to_string("formpopup.html", {'field': name, 'model': self.model}) return html+popupplus
The template code for your widget:
<a href="/add/{{ model }}/" class="add-another" id="add_id_{{ field }}" onclick="return showAddAnotherPopup(this);"> + </a>
Now you can attach your widget to any foreignkey select field on your form:
#forms.py from app.widgets import SelectWithPopUp class CustomForm(forms.Form): selectfield = forms.ModelChoiceField(Model.objects, widget=SelectWithPopUp)
The final piece of the puzzle is to create a view that can handle the saving and creation of the dynamic form:
# views.py from django.shortcuts import get_object_or_404, render_to_response from django.template import RequestContext from django.contrib.auth.decorators import login_required from django.http import HttpResponseRedirect, HttpResponseNotFound, HttpResponse from django.utils.html import escape from django.forms.models import modelform_factory from django.db.models.loading import get_models, get_app, get_apps @login_required def add_new_model(request, model_name): if (model_name.lower() == model_name): normal_model_name = model_name.capitalize() else: normal_model_name = model_name app_list = get_apps() for app in app_list: for model in get_models(app): if model.__name__ == normal_model_name: form = modelform_factory(model) if request.method == 'POST': form = form(request.POST) if form.is_valid(): try: new_obj = form.save() except forms.ValidationError, error: new_obj = None if new_obj: return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script>' % \ (escape(new_obj._get_pk_val()), escape(new_obj))) else: form = form() page_context = {'form': form, 'field': normal_model_name} return render_to_response('popup.html', page_context, context_instance=RequestContext(request))
The template code for your view:
<html> <head> <title>Add {{ field }}</title> <script src="{{ ADMIN_MEDIA_PREFIX }}js/admin/RelatedObjectLookups.js"></script> </head> <body> <h1>Add {{ field }}</h1> <form method="POST" action="/add/{{ field }}"> {% csrf_token %} <table> {{ form }} </table> <p><input type="submit" /> | <a href="javascript:window.close()">Cancel</a></p> </form> </body> </html>
I've implemented all this code into a re-usable django application that you can just drop into your own project here.
Thank you Mimsy for the original tips on how to implement this.

