Repository: wagtail/wagtail

Issue:
Edit page will get error after changing LANGUAGE_CODE in settings
Found a bug? Please fill out the sections below. 👍

### Issue Summary
When I set LANGUAGE_CODE = 'zh-hans', then edit page will ocurred error like

`can't currently handle on_delete types other than CASCADE, SET_NULL and DO_NOTHING`


### Steps to Reproduce
1.set LANGUAGE_CODE = 'zh-hans' in settings/base.py
2. runserver anyway
3. Try to edit page in nemu

Any other relevant information. For example, why do you consider this a bug and what did you expect to happen instead?

* I have confirmed that this issue can be reproduced as described on a fresh Wagtail project: (yes)


### Technical details

* Python version: 3.9.1
* Django version: 3.1.7
* Wagtail version: 2.12.2.
* Browser version: Edge 88.



Discussion:
Hi @Sccotliu - thanks for the report.
I'm not able to reproduce this error on a fresh Wagtail project as described. From the error message, it sounds like you've set the ParentalKey on an Orderable child model to `on_delete=models.PROTECT` or similar - not related to the language setting.

If this isn't the case, please include your models.py file and the full traceback of the error.
Actually, I've just been able to replicate it - it looks like it only happens if the page has been previously saved under the original language setting. Investigating now...
cc @kaedroho

Looks like there are two variants of this issue: if LANGUAGE_CODE is changed immediately after running initial migrations - but with Wagtail i18n still disabled - then a "Locale object with ID 1 does not exist" error occurs on save:

```
Traceback (most recent call last):
  File "/Users/matthew/.virtualenvs/zhhans/lib/python3.9/site-packages/django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "/Users/matthew/.virtualenvs/zhhans/lib/python3.9/site-packages/django/core/handlers/base.py", line 181, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/Users/matthew/.virtualenvs/zhhans/lib/python3.9/site-packages/django/views/decorators/cache.py", line 44, in _wrapped_view_func
    response = view_func(request, *args, **kwargs)
  File "/Users/matthew/.virtualenvs/zhhans/lib/python3.9/site-packages/wagtail/admin/urls/__init__.py", line 127, in wrapper
    return view_func(request, *args, **kwargs)
  File "/Users/matthew/.virtualenvs/zhhans/lib/python3.9/site-packages/wagtail/admin/auth.py", line 193, in decorated_view
    return view_func(request, *args, **kwargs)
  File "/Users/matthew/.virtualenvs/zhhans/lib/python3.9/site-packages/django/views/generic/base.py", line 70, in view
    return self.dispatch(request, *args, **kwargs)
  File "/Users/matthew/.virtualenvs/zhhans/lib/python3.9/site-packages/wagtail/admin/views/pages/edit.py", line 131, in dispatch
    return super().dispatch(request)
  File "/Users/matthew/.virtualenvs/zhhans/lib/python3.9/site-packages/django/views/generic/base.py", line 98, in dispatch
    return handler(request, *args, **kwargs)
  File "/Users/matthew/.virtualenvs/zhhans/lib/python3.9/site-packages/wagtail/admin/views/pages/edit.py", line 218, in post
    return self.form_valid(self.form)
  File "/Users/matthew/.virtualenvs/zhhans/lib/python3.9/site-packages/wagtail/admin/views/pages/edit.py", line 248, in form_valid
    return self.save_action()
  File "/Users/matthew/.virtualenvs/zhhans/lib/python3.9/site-packages/wagtail/admin/views/pages/edit.py", line 254, in save_action
    self.page.save_revision(
  File "/Users/matthew/.virtualenvs/zhhans/lib/python3.9/site-packages/wagtail/core/models.py", line 1287, in save_revision
    self.full_clean()
  File "/Users/matthew/.virtualenvs/zhhans/lib/python3.9/site-packages/wagtail/core/models.py", line 948, in full_clean
    super().full_clean(*args, **kwargs)
  File "/Users/matthew/.virtualenvs/zhhans/lib/python3.9/site-packages/django/db/models/base.py", line 1231, in full_clean
    raise ValidationError(errors)

Exception Type: ValidationError at /admin/pages/3/edit/
Exception Value: {'locale': ['Objekt vom Typ locale mit id 1 existiert nicht.']}
```

On the other hand, if you run initial migrations, make a draft edit to the homepage and then change LANGUAGE_CODE, then on visiting the Edit view again I get the on_delete error as described here, presumably an instance of https://github.com/wagtail/django-modelcluster/issues/127 at the point where it's unpacking the previous revision's JSON:

```
Traceback (most recent call last):
  File "/Users/matthew/.virtualenvs/zhhans/lib/python3.9/site-packages/modelcluster/models.py", line 86, in model_from_serializable_data
    field.remote_field.model._default_manager.get(**{field.remote_field.field_name: clean_value})
  File "/Users/matthew/.virtualenvs/zhhans/lib/python3.9/site-packages/django/db/models/manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/Users/matthew/.virtualenvs/zhhans/lib/python3.9/site-packages/django/db/models/query.py", line 429, in get
    raise self.model.DoesNotExist(

During handling of the above exception (Locale matching query does not exist.), another exception occurred:
  File "/Users/matthew/.virtualenvs/zhhans/lib/python3.9/site-packages/django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "/Users/matthew/.virtualenvs/zhhans/lib/python3.9/site-packages/django/core/handlers/base.py", line 181, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/Users/matthew/.virtualenvs/zhhans/lib/python3.9/site-packages/django/views/decorators/cache.py", line 44, in _wrapped_view_func
    response = view_func(request, *args, **kwargs)
  File "/Users/matthew/.virtualenvs/zhhans/lib/python3.9/site-packages/wagtail/admin/urls/__init__.py", line 127, in wrapper
    return view_func(request, *args, **kwargs)
  File "/Users/matthew/.virtualenvs/zhhans/lib/python3.9/site-packages/wagtail/admin/auth.py", line 193, in decorated_view
    return view_func(request, *args, **kwargs)
  File "/Users/matthew/.virtualenvs/zhhans/lib/python3.9/site-packages/django/views/generic/base.py", line 70, in view
    return self.dispatch(request, *args, **kwargs)
  File "/Users/matthew/.virtualenvs/zhhans/lib/python3.9/site-packages/wagtail/admin/views/pages/edit.py", line 104, in dispatch
    self.page = self.real_page_record.get_latest_revision_as_page()
  File "/Users/matthew/.virtualenvs/zhhans/lib/python3.9/site-packages/wagtail/core/models.py", line 1360, in get_latest_revision_as_page
    return latest_revision.as_page_object()
  File "/Users/matthew/.virtualenvs/zhhans/lib/python3.9/site-packages/wagtail/core/models.py", line 2781, in as_page_object
    return self.page.specific.with_content_json(self.content_json)
  File "/Users/matthew/.virtualenvs/zhhans/lib/python3.9/site-packages/wagtail/core/models.py", line 2629, in with_content_json
    obj = self.specific_class.from_json(content_json)
  File "/Users/matthew/.virtualenvs/zhhans/lib/python3.9/site-packages/modelcluster/models.py", line 272, in from_json
    return cls.from_serializable_data(json.loads(json_data), check_fks=check_fks, strict_fks=strict_fks)
  File "/Users/matthew/.virtualenvs/zhhans/lib/python3.9/site-packages/modelcluster/models.py", line 239, in from_serializable_data
    obj = model_from_serializable_data(cls, data, check_fks=check_fks, strict_fks=strict_fks)
  File "/Users/matthew/.virtualenvs/zhhans/lib/python3.9/site-packages/modelcluster/models.py", line 100, in model_from_serializable_data
    raise Exception("can't currently handle on_delete types other than CASCADE, SET_NULL and DO_NOTHING")

Exception Type: Exception at /admin/pages/3/edit/
Exception Value: can't currently handle on_delete types other than CASCADE, SET_NULL and DO_NOTHING
```

Have also replicated this on 2.11.1.
This is happening due to the custom manager on the Locale class, which filters out any locales that are not in the current set of recognised language codes:

https://github.com/wagtail/wagtail/blob/4ae37572d467554a9483f96813a995ef23437d90/wagtail/core/models.py#L333-L335

If LANGUAGE_CODE is changed from the one that the page tree was created under, then the locale of the existing pages will no longer be considered valid, even on a non-i18n-enabled install - and so pages will fail validation checks on saving.

@kaedroho Commenting out this `get_queryset` method fixes both variants of this bug, and doesn't break any of Wagtail or wagtail-localize's tests. Any reason not to remove it? (Or, alternatively, only enforce it when WAGTAIL_I18N_ENABLED is true - that does mean that people are likely to see this breaking again as soon as they enable i18N, but at least that way we aren't breaking something that works on existing setups, and it's [sort of covered by the documentation(https://docs.wagtail.io/en/stable/advanced_topics/i18n.html#locales).)

Resolving patch:
diff --git a/wagtail/core/models.py b/wagtail/core/models.py
--- a/wagtail/core/models.py
+++ b/wagtail/core/models.py
@@ -330,10 +330,6 @@ def pk(obj):
 
 
 class LocaleManager(models.Manager):
-    def get_queryset(self):
-        # Exclude any locales that have an invalid language code
-        return super().get_queryset().filter(language_code__in=get_content_languages().keys())
-
     def get_for_language(self, language_code):
         """
         Gets a Locale from a language code.


Test patch:
diff --git a/wagtail/admin/tests/pages/test_edit_page.py b/wagtail/admin/tests/pages/test_edit_page.py
--- a/wagtail/admin/tests/pages/test_edit_page.py
+++ b/wagtail/admin/tests/pages/test_edit_page.py
@@ -1000,6 +1000,58 @@ def test_post_edit_alias_page(self):
 
         self.assertEqual(response.status_code, 405)
 
+    def test_edit_after_change_language_code(self):
+        """
+        Verify that changing LANGUAGE_CODE with no corresponding database change does not break editing
+        """
+        # Add a draft revision
+        self.child_page.title = "Hello world updated"
+        self.child_page.save_revision()
+
+        # Hack the Locale model to simulate a page tree that was created with LANGUAGE_CODE = 'de'
+        # (which is not a valid content language under the current configuration)
+        Locale.objects.update(language_code='de')
+
+        # Tests that the edit page loads
+        response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )))
+        self.assertEqual(response.status_code, 200)
+
+        # Tests simple editing
+        post_data = {
+            'title': "I've been edited!",
+            'content': "Some content",
+            'slug': 'hello-world',
+        }
+        response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data)
+
+        # Should be redirected to edit page
+        self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )))
+
+    def test_edit_after_change_language_code_without_revisions(self):
+        """
+        Verify that changing LANGUAGE_CODE with no corresponding database change does not break editing
+        """
+        # Hack the Locale model to simulate a page tree that was created with LANGUAGE_CODE = 'de'
+        # (which is not a valid content language under the current configuration)
+        Locale.objects.update(language_code='de')
+
+        PageRevision.objects.filter(page_id=self.child_page.id).delete()
+
+        # Tests that the edit page loads
+        response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )))
+        self.assertEqual(response.status_code, 200)
+
+        # Tests simple editing
+        post_data = {
+            'title': "I've been edited!",
+            'content': "Some content",
+            'slug': 'hello-world',
+        }
+        response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data)
+
+        # Should be redirected to edit page
+        self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )))
+
 
 class TestPageEditReordering(TestCase, WagtailTestUtils):
     def setUp(self):