Description
Description
Consider the following scenario: we got a multi-language site using the languages English (en) and German (de).
We currently got a page which only exists in the German (de) version. Therefore, we only have a published PageContent
object for this page in German.
If we change the language switch inside the PageContent
admin list view (the one on top, just beside the "Main Navigation" label) to English (en)
, we can see the title of the German Page with a "(de)" behind it. We can then click on the grey circle to create the english version of the page. This all works flawlessly.
However, suppose we have a page which is published in both versions. We now click on the page content settings on the German version and then use the language tab on top of the form to switch the language to English.
This results in an empty form without title, slug or anything that the already existing english version of this page provides. If we continue to fill out the form and save it, we can create an inconsistent state inside the DB because the PageUrl
objects now return 2 results for the english page version.
If we look at the get_urls()
method of this page, we can see that the English version now has 2 PageUrl objects:
<QuerySet [<PageUrl: english-testpage (en)>, <PageUrl: deutsche-testseite-translated (de)>, <PageUrl: adskjfhgdskjfhdsakjh (en)>]>
If we then select the language with 2 PageUrl
objects in the PageContent
list view and try to edit it, we get an error:
Traceback (most recent call last):
File "/usr/local/lib/python3.11/dist-packages/django/core/handlers/exception.py", line 55, in inner
response = get_response(request)
^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/dist-packages/django/core/handlers/base.py", line 197, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/dist-packages/django/contrib/admin/options.py", line 688, in wrapper
return self.admin_site.admin_view(view)(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/dist-packages/django/utils/decorators.py", line 134, in _wrapper_view
response = view_func(request, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/dist-packages/django/views/decorators/cache.py", line 62, in _wrapper_view_func
response = view_func(request, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/dist-packages/django/contrib/admin/sites.py", line 242, in inner
return view(request, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/dist-packages/cms/admin/pageadmin.py", line 981, in change_view
return super().change_view(request, object_id, form_url=form_url, extra_context=context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/dist-packages/django/contrib/admin/options.py", line 1889, in change_view
return self.changeform_view(request, object_id, form_url, extra_context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/dist-packages/django/utils/decorators.py", line 46, in _wrapper
return bound_method(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/dist-packages/django/utils/decorators.py", line 134, in _wrapper_view
response = view_func(request, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/dist-packages/django/contrib/admin/options.py", line 1747, in changeform_view
return self._changeform_view(request, object_id, form_url, extra_context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/dist-packages/django/contrib/admin/options.py", line 1819, in _changeform_view
form = ModelForm(instance=obj)
^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/dist-packages/cms/admin/forms.py", line 578, in __init__
self.url_obj = self.instance.page.get_url(self._language)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/dist-packages/cms/models/pagemodel.py", line 782, in get_url
return self.get_urls().get(language=language)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/dist-packages/django/db/models/query.py", line 640, in get
raise self.model.MultipleObjectsReturned(
^
Exception Type: MultipleObjectsReturned at /de/admin/cms/pagecontent/362/change/
Exception Value: get() returned more than one PageUrl -- it returned 2!
This is really unexpected because we get an explicit option to edit page contents in more than one way and depending on which way you choose you may end up destroying the integrity of your pages.
Steps to reproduce
Follow the steps above.
Expected behaviour
Page Content always get correctly fetched and edited without destroying the consistency of the DB
Actual behaviour
See above.
Screenshots
Not really applicable, the reproduction is too complex to convey through screenshots.
Additional information (CMS/Python/Django versions)
Python 3.11
Django 4.2
djangoCMS 4.1.3
Do you want to help fix this issue?
Imo, this can only be resolved in two ways:
- fix the edit view of page content objects so that using the tab select correctly fetches existing page information and edits it as expected (just the way it did in djangoCMS 3) or
- completely disable the tab selectors inside the page content edit view so that you NEED to use the languag 5CB2 e select inside the list view.
I know this may seem hard to fathom and especially hard to reproduce, so if anyone needs clarification or a short demo I am always available to hop on a quick Discord call.
So yes, I'd like to help but I can't really dig through djangoCMS internals right now and create a PR. Always happy to review them by testing however :)
- Yes, I want to help fix this issue and I will join the channel #pr-reviews on the Discord Server to confirm with the community that a PR is welcome.
- No, I only want to report the issue.