Scriptim
Scriptim

Reputation: 1832

How to programmatically choose a parent on page creation using Wagtail admin?

I have two models, one of which is an index page for the other:

class FooIndexPage(Page):
    category = models.IntegerField(unique=True)
    subpage_types = ['foo.fooPage']

class FooPage(Page):
    category = models.IntegerField()

I obviously left out most of the fields because they are irrelevant to my question. The category field of FooPage is actually derived from other fields; I just put it that way for the sake of simplicity.

What I want to achieve: Each FooPage should eventually be a child of a certain FooIndexPage in the tree structure. However, before the page is created, it is not yet clear which page is exactly the appropriate FooIndexPage (since the category is derived from the other fields, as mentioned above). There is also the possibility that it does not yet exist and must be created first. The whole thing should happen automatically when a user creates a FooPage (the user creates the page in the admin interface as a child of the home page). As soon as the user saves the page, it should be moved to the corresponding FooIndexPage, which may have to be created first.

I have attempted to solve it by overriding the save method of FooPage and moving the page accordingly after creation:

def save(self, *args, **kwargs):
    super().save(*args, **kwargs)
    category = self.category
    if not FooIndexPage.objects.filter(category=category).exists():
        self.get_parent().add_child(instance=FooIndexPage(category=category))
    index_page = FooIndexPage.objects.get(category=category)
    self.move(index_page, 'last-child')

The correct FooIndexPage is created if it does not exist. However, the code raises an exception on self.move:

Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/home/.../lib/python3.8/site-packages/wagtail/core/models.py", line 1381, in move
    super().move(target, pos=pos)
  File "/home/.../lib/python3.8/site-packages/treebeard/mp_tree.py", line 1095, in move
    return MP_MoveHandler(self, target, pos).process()
  File "/home/.../lib/python3.8/site-packages/treebeard/mp_tree.py", line 472, in process
    if self.target.is_descendant_of(self.node):
AttributeError: 'NoneType' object has no attribute 'is_descendant_of'

This seems to be a known issue of treebeard, which occurs when the parent page has no children yet. I was not able to find a solution, though.

Is there a way to overwrite the parent page when creating a page?

Upvotes: 1

Views: 887

Answers (1)

gasman
gasman

Reputation: 25227

As per the Treebeard docs:

django-treebeard uses Django raw SQL queries for some write operations, and raw queries don’t update the objects in the ORM since it’s being bypassed.

Because of this, if you have a node in memory and plan to use it after a tree modification (adding/removing/moving nodes), you need to reload it.

By creating index_page, you're making a modification to the tree while the node self is still in memory. It's hard to know what kind of issues this might cause, but if for example creating the index page caused self's path value to change, then the move operation might perform updates based on the stale path value. It's probably worth running ./manage.py fixtree at this point to check whether any corruption to the tree structure has occurred.

Running self.refresh_from_db() before the move should avoid that, but I'm not 100% confident that's safe to do inside a save method, so I'd be inclined to avoid that by fetching a new copy of the self node and running move on that instead:

new_self = Page.objects.get(pk=self.pk)
new_self.move(index_page, 'last-child')

Upvotes: 1

Related Questions