Ответ состоит из двух шагов:
# your_app_name/admin.py
from django import forms
from django.utils.safestring import mark_safe
from django.contrib import admin
from django.core.exceptions import ObjectDoesNotExist
# 1. Override `Grandparent`'s and `Parent`'s `ModelAdmin` forms:
#
# Create a widget with a hyperlink to `Parent` admin form
# with `Grandparent`'s primary key as `GET` parameter.
# Place it in `Grandparent`'s `ModelAdmin`:
class AddParentAdminWidget(forms.Widget):
def __init__(self, obj, attrs=None):
self.object = obj
super(AddParentAdminWidget, self).__init__(attrs)
def render(self, name, value, attrs=None):
if self.object.pk:
return mark_safe(
u"""<a class="button" href="../../../%(app_label)s/%(object_name)s/add/?grandparent_pk=%(object_pk)s">%(linktitle)s</a>
""" %\
{
'app_label': Parent._meta.app_label,
'object_name': Parent._meta.object_name.lower(),
'object_pk': self.object.pk,
'linktitle': 'Add new Parent instance with prepopulated Grandparent field.',
}
)
else:
return mark_safe(u'')
# Add a dummy `add_new_parent_link` field to `Grandparent's` form
# just to be able to replace it with your previously created widget.
class GrandparentAdminForm(forms.ModelForm):
add_new_parent_link = forms.CharField(label = 'Add new parent instance', required = False)
def __init__(self, *args, **kwargs):
super(GrandparentAdminForm, self).__init__(*args, **kwargs)
# instance is always available, it just does or doesn't have pk.
self.fields['add_new_parent_link'].widget = AddParentAdminWidget(self.instance)
class Meta:
model = Grandparent
class GrandparentAdmin(admin.ModelAdmin):
form = GrandparentAdminForm
# 2. Set initial data in `Parent`'s `ModelForm`
#
# (do this by accessing the `request` object,
# which is set in `ParentAdmin` code below):
class ParentAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(ParentAdminForm, self).__init__(*args, **kwargs)
if self.request.GET.get('grandparent_pk', False):
try:
grandparent_pk = int(self.request.GET.get('grandparent_pk', ''),)
except ValueError:
pass
else:
try:
Grandparent.objects.get(pk = grandparent_pk)
except ObjectDoesNotExist:
pass
else:
self.initial['grandparent'] = grandparent_pk
class Meta:
model = Parent
# Add your children as regular `StackedInline`'s
class ChildInline(admin.StackedInline):
model = Child
class ChildAInline(admin.StackedInline):
model = ChildA
class ChildBInline(admin.StackedInline):
model = ChildB
class ParentAdmin(admin.ModelAdmin):
form = ParentAdminForm
def get_form(self, request, obj=None, **kwargs):
"""
Use a trick to be able to access `request` object in `ParentAdminForm`.
Yes, unfortunately we need it [http://stackoverflow.com/a/6062628/497056]
to access `request` object in an admin `ModelForm` (as of `Django` 1.4).
Hopefully, this will be fixed in newer versions.
"""
AdminForm = super(ParentAdmin, self).get_form(request, obj, **kwargs)
class ModelFormMetaClass(AdminForm):
"""
We need this metaclass
to be able to access request in a form
"""
def __new__(cls, *args, **kwargs):
kwargs['request'] = request
return AdminForm(*args, **kwargs)
return ModelFormMetaClass
inlines = [
ChildInline,
ChildAInline,
ChildBInline,
]
admin.site.register(Grandparent, GrandparentAdmin)
admin.site.register(Parent, ParentAdmin)