可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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.
回答1:
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 :)
回答2:
This is an alternative to my other answer.
This alternative does not use JavaScript, so it works server-side only, which has a lot 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.
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.
回答3:
This is old, but it can be quite a puzzle, and the accepted answer did not address the true issue for me.
The question, as I understand it, is how to pre-fill data on the related model's admin form, which pops up after you click the green +
("Add another") next to a widget for a ForeignKey
or ManyToManyField
. This is illustrated below.
As suggested in the (currently) accepted answer, as well as here, this can be achieved by adding URL parameters for each of the fields that you want to pre-fill. This is also illustrated in the image.
The question that remains unanswered, however, is:
How, exactly, can we add these URL parameters to the URL used by the +
("Add another") link?
Assuming the pre-filled values depend on a the current state of the form on the client side, e.g. the selected organization
in the image above, I suppose it makes sense to use JavaScript.
Note: If the pre-filled values do not depend on the form state, nor on the current request, other options might be more appropriate.
Here's a minimal example that was used to create the image above:
models.py: (we put the admin stuff in here as well for convenience)
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)
admin.site.register(Organization)
admin.site.register(Participant)
admin.site.register(Event)
Now we extend the Django admin change_form
template (based on the docs) for our Event
model with a little bit of JavaScript.
Follow these instructions to set-up your templates folder, and don't forget to add you templates folder to DIRS
in settings.TEMPLATES
.
templates/admin/my_app/event/change_form.html:
{% extends 'admin/change_form.html' %}
{% load static %}
{% block admin_change_form_document_ready %}
{{ block.super }}
<script type="text/javascript">
var add_participant_url = document.getElementById("add_id_participants").href;
var organization = document.getElementById("id_organization")
organization.onchange = function() {
document.getElementById("add_id_participants").href =
add_participant_url + "&organization=" + organization.value;
};
</script>
{% endblock %}
DISCLAIMER: I have very limited experience with JavaScript, so any suggestions for improvement are welcome.
Basically, whenever the user changes the selected organization on the "Change event" form, the URL for the "Add another participant" (+) link is updated with the selected value, so the organization field on the "Add participant" form will be pre-filled.