Introduction
This blog post is about a code snippet I'm proud of. I was trying to solve an issue that took me quite some time. The fix took 2 lines of code, but required a full understanding of the Django framework.
The problem
In Wagtail, creating pages from the dashboard works well, until a certain point. If you were migrating content from an old Blog, with hundreds of pages, the strain of recreating all the pages and then copy-pasting would outweigh the benefits of migrating. The better way to do this would be with a script. You export content from the old system into a CSV file and then read it into the Page
model and save each row as an instance. All from the Django shell.
The particular problem: After migration, downloading the XLSX file from the dashboard would crash with an AttributeError
, despite your changes being reflected.
My thought process
Reproducing the error
To find out why it crashed, I had to first reproduce the error. I mimicked a script loading pages directly into the database from the shell. So, using the Django shell, I imported Wagtail's Page
model, and the User
model, so I could attach an Author to the page. Afterward, I looked through wagtail/models/__init__.py
to know the required kwargs for a Page
instance. I created one, saved it with save()
, and published it with publish()
, passing in the revision arg. I refreshed, and the change was reflected in the dashboard. On the Aging pages
interface, I saw pages created via the dashboard, and the page created via the shell. On clicking the button for downloading the XLSX report, the dashboard crashed. I successfully reproduced the error.
Finding out the error trigger
I checked to make sure it was the same error, then I looked for the trigger. It pointed towards an attribute not being found from the Page
instance. An attribute that didn't exist in the schema.
Interpreting the error
The AttributeError
happening only after shell manipulation meant the views
file was responsible for that 'extra' attribute because shell commands run independently of views.
Finding the culprit block of code
Reading the AgingPagesView
class, I noticed get_queryset()
annotated a last_published_by
that was assigned a value of a query from a PageLogEntry
model. This model created an instance every time a Page was 'published' from the dashboard. I also noticed the decorate_paginated_queryset
called a method add_last_publisher_name_to_page
for each page in the queryset
returned.
Making the fix
All the add_last_publisher_name_to_page()
did was check if last_published_by
existed, give a user_id
value, and then create a last_published_by_user
field for the page (which wouldn't exist if the page was created from the shell). So, my mind-blowingly fantastic fix:
#... The code already there
else:
page.last_published_by_user = ""
Genius, I know. 😁
Assigning an empty string value would stop the crash, without breaking existing logic.
TL;DR
This fixed that.
It was a big problem that was fixed by a seemingly small change, but that small change was hidden behind a lot of complexity.
Thanks for reading.
Cheers.
Top comments (0)