1

Say I have the following model in a Wagtail app:

# models.py
from django.db import models
from django.db.models.deletion import CASCADE

from wagtail.admin.edit_handlers import InlinePanel, FieldPanel

from modelcluster.models import ParentalKey, ClusterableModel


class Person(ClusterableModel):
    name = models.CharField(max_length=300)
    contact_for = ParentalKey(
        'self', on_delete=CASCADE, null=True, related_name='contacts'
    )

    panels = [
        FieldPanel('name'),
        FieldPanel('contact_for'),
        InlinePanel('contacts')
    ]

And the following hooks:

# wagtail_hooks.py
from wagtail.contrib.modeladmin.options import ModelAdmin, modeladmin_register
from home.models import Person


class PersonAdmin(ModelAdmin):
    model = Person
    menu_label = 'People'
    menu_icon = 'list-ul'
    menu_order = 200
    add_to_settings_menu = False
    exclude_from_explorer = False
    list_display = ('name',)
    search_fields = ('name',)
    list_filter = ('contact_for',)


modeladmin_register(PersonAdmin)

When running the above and navigating to Admin > People > Add Person I will get a RecursionError exception (maximum recursion depth exceeded).

I'm guessing this is a Wagtail related issue, because I can use the class as intended in IPython:

./manage.py shell -i ipython

In [1]: from home.models import Person

In [2]: mike = Person(name='Mike')

In [3]: mike.contacts = [Person(name='Sam'), Person(name='John')]

In [4]: mike.contacts
Out[4]: <modelcluster.fields.create_deferring_foreign_related_manager.<locals>.DeferringRelatedManager at 0x7f36e9548af0>

In [5]: mike.save()

In [6]: [person.name for person in Person.objects.all()]
Out[6]: ['Mike', 'Sam', 'John']

In [7]: Person.objects.all()[1].contact_for.name
Out[7]: 'Mike'

So, is there a way I can make use of a recursive ParentalKey in Wagtail? What am I doing wrong/missing?

Edit: I just found this answer. So I'm wondering if I should even be trying to use ParentalKey (and ParentalManyToManyField) for non Page models.

Edit 2: For anyone interested, I ended up splitting my Model to avoid a recursive key. InlinePanels do work with ModelAdmin classes, it's just Wagtail doesn't seem to support recursive keys. I have also opted to use Snippets and SnippetChooserPanel where it felt appropriate.

Samuel
  • 157
  • 1
  • 10

1 Answers1

2

A ParentalKey means that the child model is notionally treated as 'part of' the parent model and doesn't exist as an independent entity - for example, an image gallery being part of a page - for purposes such as versioning, and moderation workflow. (In Wagtail, non-page models handled through snippets or ModelAdmin don't have these features, but in order for them to share the same InlinePanel mechanism as pages, they also use ParentalKey.)

In this case, a Person is not part of another Person, and needs to be editable independently of the 'parent', so a ParentalKey isn't appropriate here. Instead, you should use a ForeignKey, which just indicates some relation between the models. This does mean that you can't use an InlinePanel to manage multiple Person records within the same view - you'll have to edit them separately, and use something like SnippetChooserPanel or a simple dropdown to set up the relations between them.

gasman
  • 23,691
  • 1
  • 38
  • 56