0

I have some Django models with a structure similar to this:

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()

So basically, there are two kind of children which inherit from a practically abstract Child model. (It is not really abstract so I could use a foreign key with it).

The question is - how will I be able to create a link from the admin page of the first model (Grandparent) to the creation page of a new Parent model? That Parent model needs to already have the Grandparent foreign key field populated by the currently viewed grandparent page id.

Inlines are the thing that comes to mind, but I will not be able to use them because inlines cannot be nested and I will be needing them to manipulate the fields inside ChildA and ChildB on the Parent page.

Ohad
  • 1,719
  • 1
  • 16
  • 20

1 Answers1

1

The answer consists of two steps:

# 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)
Ivan Kharlamov
  • 1,889
  • 2
  • 24
  • 33
  • Well, I couldn't manage to get it to work... I'm a beginner in django and couldn't figure out everything in the answer. I'll get back to it further in the project when I'm a bit more experienced. Thanks. – Ohad Mar 18 '12 at 18:37
  • Probably more than one issue, but I think i did something wrong in step 4. I couldn't access get('parent_pk'). The error message said there's no 'get' method in a Nonetype object. Where should the ChildAdmin class code reside? the Child is being edited with inlines anyways. – Ohad Mar 18 '12 at 18:57
  • No, my models are correct. I want the parent to have two kinds of children(`ChildA`, `ChildB`) which inherit some fields from `Child`, Not a three level children tree. – Ohad Mar 19 '12 at 07:25
  • So, @Ohad, were you able to solve the issue with an updated solution above? – Ivan Kharlamov Mar 20 '12 at 17:49
  • I didn't use your solution but it is very similar to what I ended up doing. I really appreciate all the help! Thanks. – Ohad Mar 21 '12 at 08:21