Repository: wagtail/wagtail

Issue:
Revision model rename breaks Page revision foreign key on SQLite
### Issue Summary

#8441 renamed the PageRevision model to Revision, which included a migration with [a `RenameModel` step](https://github.com/wagtail/wagtail/blob/1f43d8ef51e455b92e42447fdc190d5ec83ec53c/wagtail/migrations/0070_rename_pagerevision_revision.py#L15-L18).

On my local machine, running against SQLite in the default configuration, this renamed the table but didn't update the foreign key from the Page model. Looking at the SQL for the migration starts with:

```
% ./manage.py sqlmigrate wagtailcore 0070
BEGIN;
--
-- Rename model PageRevision to Revision
--
ALTER TABLE "wagtailcore_pagerevision" RENAME TO "wagtailcore_revision";
...
```

But if I then check the `live_revision_id` foreign key on the Page model in SQLite, it hasn't been updated, and still points to the now-renamed `wagtailcore_pagerevision` table.

```
% sqlite3 db.sqlite3 
SQLite version 3.32.3 2020-06-18 14:16:19
Enter ".help" for usage hints.
sqlite> PRAGMA foreign_key_list('wagtailcore_page');
...
3|0|wagtailcore_pagerevision|live_revision_id|id|NO ACTION|NO ACTION|NONE
...
```

It looks like I'm getting hit by the `ALTER TABLE RENAME` issue described in the SQLite docs [here](https://www.sqlite.org/draft/lang_altertable.html#alter_table_rename):

> Beginning with version 3.26.0, FOREIGN KEY constraints are always converted when a table is renamed, unless the [PRAGMA legacy_alter_table=ON](https://www.sqlite.org/draft/pragma.html#pragma_legacy_alter_table) setting is engaged.

My `PRAGMA`s are defined thusly (the default values, apparently, for this version of SQLite on MacOS):

```
sqlite> PRAGMA legacy_alter_table;
1
sqlite> PRAGMA foreign_keys;
0
```

I note [this commit](https://github.com/django/django/commit/063cf98d3a6839f40c423cbd845def429c5cf0ce) that just went into Django (the dev version for 4.1) that explicitly disables `legacy_alter_table`; I wonder if I am hitting some edge case that this would fix.

Wagtail seems to have only [one other instance of `RenameModel`](https://github.com/wagtail/wagtail/blob/716bf92c2dc2da2aca5e8f5aa6768b5b087cd4b0/wagtail/contrib/search_promotions/migrations/0001_initial.py#L65) for the SearchPromotion model, but I don't think we have any foreign keys pointing to that. So this might be the first time this combination has hit Wagtail.

### Steps to Reproduce

1. Start a new project with `wagtail start myproject`
2. `cd myproject`
3. `./manage.py migrate`
4. `./manage.py createsuperuser` and create an admin user.
5. `./manage.py runserver`
6. Vist http://localhost:8000/admin/pages/add/home/homepage/3/ to create a new page (you'll be asked to log in first)
7. Fill in the title field with something like "Test". Click "Save Draft". You'll get an error: "OperationalError at /admin/pages/add/home/homepage/3/, no such table: main.wagtailcore_pagerevision"

-   I have confirmed that this issue can be reproduced as described on a fresh Wagtail project: yes

### Technical details

- Mac OS version: Big Sur 11.6.5
- SQLite3 version: 3.32.3 2020-06-18 14:16:19 02c344aceaea0d177dd42e62c8541e3cab4a26c757ba33b3a31a43ccc7d4aapl
-   Python version: 3.10.4
-   Django version: 4.0.5, also happens on 3.2
-   Wagtail version: main (1f43d8ef51e455b92e42447fdc190d5ec83ec53c)



Discussion:
Just to report I think I have the same issue here. Also hit when creating a new page.

I get the same output as @chosak for `PRAGMA foreign_key_list('wagtailcore_page')`, `PRAGMA legacy_alter_table`, `PRAGMA foreign_keys`

This is on macOS 12.3 Monterey, SQLite version 3.37.0 (with the default configuration as far as I’m aware), same Wagtail commit 1f43d8ef51.
Looking into this a bit more, this definitely seems like a Django issue and nothing specific to Wagtail. That being said, we might need to find a workaround if we want to release this table rename given current Django logic.

I dug into the Django history around these SQLite pragmas (which is quite complicated!) and I think this is the same issue as Django [ticket 31765](https://code.djangoproject.com/ticket/31765), which was fixed in https://github.com/django/django/commit/063cf98d3a6839f40c423cbd845def429c5cf0ce (pending Django release 4.1). If I backport this change to Django 4.0, then my local combination of MacOS/SQLite works properly to repoint the foreign key, and Wagtail page creation is fixed.

I commented at https://code.djangoproject.com/ticket/31765#comment:20 to see if it might be possible to get that commit backported to current versions of Django. Otherwise I wonder if we might try adding a similar fix to our problematic migration (something like `PRAGMA legacy_alter_table = OFF`). Obviously it would be nicer not to have to do that.

Thanks for the report!

I think it's worth noting that the SQLite installed on the system is not always the same as the SQLite bundled in Python. In my case:

<img width="744" alt="image" src="https://user-images.githubusercontent.com/6379424/172661854-42e942b3-29ed-4669-b86d-4e1ace58e34d.png">

As the `legacy_alter_table` is `0` on my Python's SQLite, I did not encounter the issue.

My environment:
- macOS Monterey 12.4 on MacBook Pro M1 Pro
- Python 3.10.2 installed from [Python.org](https://python.org)
- SQLite version: 3.35.5 (Python), 3.37.0 (System)
- Django version: 4.0.5
- Wagtail version: `main`
👍 here are the results of the above for me (exact same output):

```console
$ python
Python 3.10.0  […]
$ import sqlite3
$ conn = sqlite3.connect(':memory:')
$ cursor = conn.cursor()
$ cursor.execute('PRAGMA legacy_alter_table').fetchone()
(1,)
$ cursor.execute('PRAGMA foreign_keys').fetchone()
(0,)
$ sqlite3.sqlite_version
'3.37.0'
```

Resolving patch:
diff --git a/wagtail/migrations/0070_rename_pagerevision_revision.py b/wagtail/migrations/0070_rename_pagerevision_revision.py
--- a/wagtail/migrations/0070_rename_pagerevision_revision.py
+++ b/wagtail/migrations/0070_rename_pagerevision_revision.py
@@ -4,6 +4,12 @@
 from django.db import migrations, models
 
 
+def disable_sqlite_legacy_alter_table(apps, schema_editor):
+    # Fix for https://github.com/wagtail/wagtail/issues/8635
+    if schema_editor.connection.vendor == "sqlite":
+        schema_editor.execute("PRAGMA legacy_alter_table = OFF")
+
+
 class Migration(migrations.Migration):
 
     dependencies = [
@@ -12,6 +18,10 @@ class Migration(migrations.Migration):
     ]
 
     operations = [
+        migrations.RunPython(
+            disable_sqlite_legacy_alter_table,
+            migrations.RunPython.noop,
+        ),
         migrations.RenameModel(
             old_name="PageRevision",
             new_name="Revision",


Test patch: