In the Django Admin, when you modify an objects properties, if there is a ForeignKey, it will have a dropdown box of all the choices, plus a "+" button to add more choices. When I click on it, I want to prefill some of the data.
I've noticed I could do it if I could modify the URL (example: http://localhost:8000/admin/app/model/add/?field=value
where field
and value
are programmatically modified.)
I figure I have to override something in the forms.ModelForm
that the admin.ModelAdmin
uses, but I'm not sure what.
Django allows you to replace a request's GET dict (which it uses to pre-populate the admin form).
Django will automatically fill values from URL GET parameters if you are sending field values of model form in the URL.
For example, considering
"http://myhost/admin/app/model/add/?name=testname"
, it will prefill the name
field of the form in the admin add-view template with the value 'testname'
.
But, if you are sending any id in your URL, you need to modify the GET parameters by overriding the add_view
function.
Taken from stackoverflow answer
class ArticleAdmin(admin.ModelAdmin):
# ...
def add_view(self, request, form_url='', extra_context=None):
source_id = request.GET.get('source',None)
if source_id != None:
source = FeedPost.objects.get(id=source_id)
# any extra processing can go here...
g = request.GET.copy()
g.update({
'title':source.title,
'contents':source.description + u"... \n\n[" + source.url + "]",
})
request.GET = g
return super(ArticleAdmin, self).add_view(request, form_url, extra_context)
It just an example.DO it with Your model and fields :)
This is an alternative to my other answer.
This alternative does not use JavaScript, so it works server-side only, which has a number of drawbacks. Nevertheless, some parts of the puzzle may be of use to someone.
The basic idea is this:
The "Add another" (+) button provides a link to the related model admin view.
The URL for this link is constructed in the related_widget_wrapper.html template.
This template uses a url_params
variable, which is created in the RelatedFieldWidgetWrapper.get_context() method.
To pre-fill the related form, we need to append our custom URL parameters to this url_params
variable, in the context of the widget, so we can utilize the default widget template.
The simplest way to achieve this, as far as I can see, is to create a wrapper for the RelatedFieldWidgetWrapper.get_context()
method.
We then extend the ModelAdmin.formfield_for_dbfield()
method, and use our custom wrapper there, so it has access to the current request.
As far as I know, the formfield_for_dbfield
method is not mentioned in the ModelAdmin docs...), but this is where Django applies the RelatedFieldWidgetWrapper.
NOTE: If you are trying to do something similar for a raw-id-field, there is an easier way, viz. by extending ForeignKeyRawIdWidget.url_parameters()
(source).
Here's a minimal example:
from django.db import models
from django.contrib import admin
class Organization(models.Model):
pass
class Participant(models.Model):
organization = models.ForeignKey(to=Organization, on_delete=models.CASCADE)
class Event(models.Model):
organization = models.ForeignKey(to=Organization, on_delete=models.CASCADE)
participants = models.ManyToManyField(to=Participant)
def wrap_extra_url_params(original_get_context, extra_url_params: str):
def get_context_with_extra_url_params(*args, **kwargs):
context = original_get_context(*args, **kwargs)
if 'url_params' in context:
context['url_params'] += '&{}'.format(extra_url_params)
return context
return get_context_with_extra_url_params
class EventAdmin(admin.ModelAdmin):
def formfield_for_dbfield(self, db_field, request, **kwargs):
formfield = super().formfield_for_dbfield(db_field, request, **kwargs)
if db_field.name == 'participants':
# get event organization from current request
event_id = request.resolver_match.kwargs.get('object_id', None)
if event_id:
event = Event.objects.get(id=event_id)
# override the "add another participant" link
formfield.widget.get_context = wrap_extra_url_params(
original_get_context=formfield.widget.get_context,
extra_url_params='organization={}'.format(
event.organization_id))
return formfield
admin.site.register(Organization)
admin.site.register(Participant)
admin.site.register(Event, EventAdmin)
Note that the server-side has no way of knowing whether the user changes the selected organization, so it only works when modifying existing Event
instances.
This works, to a certain degree, but I would go for the JavaScript option.
Aks's answer didn't work for me as I couldn't find the source
field in request.GET
dict. However, I was able to make it work by adding the following function to my TestAdmin
class - This is the admin that opens up on clicking the add button
def get_form(self, request, obj=None, **kwargs):
form = super(TestAdmin, self).get_form(request, obj, **kwargs)
// using URL pattern to get the refrrer object id
admin_referer = re.match(".*(/adminame/)(.+)(/change/)",
request.META.get('HTTP_REFERER'))
if request.GET.get('_popup', None) and admin_referer:
test_model = TestModel.objects.get(id=admin_referer[2])
form.base_fields['prepopulate_field'].initial = test_model
form.base_fields['prepopulate_field'].widget.can_add_related = False
form.base_fields['prepopulate_field'].widget.can_change_related = False
form.base_fields['prepopulate_field'].widget.can_delete_related = False
return form
I would have preferred a better way to fetch the referer's object id, but I could only make this way to work.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With