Unchaining Django in Wagtail
Tags: django, wagtail, python
Published: 2023-10-25 20:06:04

## Introduction

This blog post is about a code snippet I'm proud of. I was trying to solve an [issue](https://github.com/wagtail/wagtail/issues/10821) that took me quite some time. The fix took 2 lines of code, but required a full understanding of the Django framework.

## The problem
![computer bug image](https://media.giphy.com/media/Px7FQJqhWTGaA/giphy.gif)
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
![thought process image](https://media.giphy.com/media/4JVTF9zR9BicshFAb7/giphy.gif)

### 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
![looking for](https://media.giphy.com/media/3orieUe6ejxSFxYCXe/giphy.gif)

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
![ASL](https://media.giphy.com/media/TlbyxSGSWD1zO4fpAa/giphy.gif)

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:
```python
#... 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](https://github.com/wagtail/wagtail/pull/11095/files#diff-8d052da6c884d2978f9bd7d649c00006398336bb9c1d2038101a22ecd62236aa) fixed [that](https://github.com/wagtail/wagtail/issues/10821).
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.