I’m trying to replicate the add new model for a foreign key if it’s not already there. For example, in my app I’m trying to track software versions used by people. Most of the time (>75%) the software will already be in the database and be selectable as a foreign key. If not I would like to do what is done on the admin page and add a simple add button beside the ForeignKey selector. Here’s what my simple model looks like in the admin page - I want to recreate the green “add” link.
My software and versions models are defined as these:
class Software(models.Model):
name = models.CharField(max_length=200)
code_developer = models.CharField(max_length=128)
commercial = models.BooleanField()
supported_os = models.PositiveSmallIntegerField(choices=SupportedOS)
url_link = models.URLField()
unique_identifier = NanoidField(unique=True)
def __str__(self):
return f'{self.name} - {self.code_developer}'
def get_absolute_url(self):
"""This is used by the table to generate a link to the detail page."""
return reverse('software_details', args=[str(self.unique_identifier)])
class Meta:
verbose_name_plural = "Software"
class SoftwareVersion(models.Model):
software = models.ForeignKey(Software, on_delete=models.CASCADE)
version = models.CharField(max_length=32)
release_date = models.DateField()
unique_identifier = NanoidField(unique=True)
def __str__(self):
return f'{self.software.name} - {self.version}'
def get_absolute_url(self):
"""This is used by the table to generate a link to the detail page."""
return reverse('software_version_details', args=[str(self.unique_identifier)])
class Meta:
verbose_name_plural = "Software Versions"
I came across an answer on how to achieve this on stackoverflow
and I’ve updated it for python 3 and this get me some of the way there. Here’s what I’ve implemented based on the linked answer.
forms.py
from django.urls import reverse
from django.utils.safestring import mark_safe
from django.forms import widgets
from django.conf import settings
from django.utils.translation import gettext_lazy as _
class RelatedFieldWidgetCanAdd(widgets.Select):
"""
Widget for a foreign key field that allows the user to add a new object.
It uses a link to the add view of the related model.
"""
def __init__(self, related_model, related_url=None, *args, **kwargs):
super().__init__(*args, **kwargs)
if not related_url:
rel_to = related_model
info = (rel_to._meta.app_label, rel_to._meta.object_name.lower())
related_url = 'admin:%s_%s_add' % info
# Note: be careful as reverse not allowed
self.related_url = related_url
def render(self, name, value, *args, **kwargs):
self.related_url = reverse(self.related_url)
output = [super().render(name, value, *args, **kwargs)]
output.append(
f'<a href="{self.related_url}?_to_field=id&_popup=1" class="add-another" id="add_id_{name}" onclick="return showAddAnotherPopup(this);"> '
)
output.append(
f'<img src="{settings.STATIC_URL}admin/img/icon_addlink.gif" width="10" height="10" alt="{_('Add Another')}"/></a>'
)
return mark_safe(''.join(output))
class NewSoftwareVersionForm(forms.ModelForm):
software = forms.ModelChoiceField(
required=False,
queryset=Software.objects.all(),
widget=RelatedFieldWidgetCanAdd(Software, related_url='software_form')
)
class Meta:
model = SoftwareVersion
fields = ("software", "version", "release_date")
labels = {"version": "Software Version"}
widgets = {"release_date": forms.DateInput(attrs={"type": "date"})}
views.py
def submit_software_version(request):
context = {}
if request.method == "POST":
form = NewSoftwareVersionForm(request.POST)
if form.is_valid():
form.save()
else:
form = NewSoftwareVersionForm()
context['form'] = form
return render(request, "database/software_version_form.html", context)
urls.py
# submit pages
urlpatterns += [
path("submit_software_version/", views.submit_software_version, name="software_version_form"),
]
This partially works - I get a link beside the foreign key selector but the + icon is missing, I presume because of the static file serving while in debug mode on my machine, but easy to fix later I think.
The (two) bigger issues are that the link doesn’t open in a new pop-up window but just loads the software form in the same page despite the ?_to_field=id&_popup=1
on the link, which can even be seen in hover on th link on the page.
If the popup works, I also need to refresh the page so that the newly added model appears in the software selector dropdown - how do i do that page refresh?
I’m using python 3.12.9 and django v5.1.5.
Can anyone help on making this open in a new popup without changing the page and refreshing the list. I’m relativley new to django
and I’m a little out of my depth here with something so complicated.