Как сделать ссылку на страницу создания нового экземпляра модели, связанную с текущим просмотренным экземпляром - PullRequest
0 голосов
/ 28 февраля 2012

У меня есть несколько моделей Django со структурой, подобной этой:

class Grandparent(models.Model):
    name = models.CharField(max_length=80)

class Parent(models.Model):
    name = models.CharField(max_length=80)
    grandparent = models.ForeignKey(Grandparent, related_name='children')

class Child(models.Model):
    parent = models.ForeignKey(Parent, related_name='children')

class ChildA(Child)
    something = models.CharField(max_length=80)
    anotherthing = models.IntegerField()

class ChildB(Child)
    anything = models.CharField(max_length=80)
    hello = models.BooleanField()

Итак, в принципе, есть два вида детей, которые наследуют от практически абстрактной Child модели. (Это не совсем абстрактно, поэтому я мог бы использовать внешний ключ с ним).

Вопрос - как я смогу создать ссылку со страницы администратора первой модели (Grandparent) на страницу создания новой модели Parent? Эта модель Parent должна уже иметь поле внешнего ключа Grandparent, заполненное просматриваемым в настоящее время идентификатором страницы grandparent.

Inline - это то, что приходит на ум, но я не смогу их использовать, потому что inlines не могут быть вложенными, и мне понадобятся они для манипулирования полями внутри ChildA и ChildB на странице Parent. .

1 Ответ

1 голос
/ 29 февраля 2012

Ответ состоит из двух шагов:

# 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)
...