Skip to content

Commit

Permalink
Merge pull request #3137 from digitalfabrik/enhancement/allow-skip-la…
Browse files Browse the repository at this point in the history
…ngtree-duplication

Add option to skip copying language tree and translations when duplicating a region
  • Loading branch information
charludo authored Nov 6, 2024
2 parents b52c745 + 19298b8 commit 1e6dde2
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 9 deletions.
38 changes: 33 additions & 5 deletions integreat_cms/cms/forms/regions/region_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,15 @@ class RegionForm(CustomModelForm):
),
)

duplication_keep_translations = forms.BooleanField(
required=False,
label=_("Copy languages and content translations"),
help_text=_(
"Disable to skip copying of the language tree and all content translations."
),
initial=True,
)

duplication_pbo_behavior = forms.ChoiceField(
choices=duplicate_pbo_behaviors.CHOICES,
initial=duplicate_pbo_behaviors.ACTIVATE,
Expand Down Expand Up @@ -271,6 +280,7 @@ def save(self, commit: bool = True) -> Region:
if duplicate_region:
source_region = self.cleaned_data["duplicated_region"]
keep_status = self.cleaned_data["duplication_keep_status"]
keep_translations = self.cleaned_data["duplication_keep_translations"]

# Determine offers to force activate or to skip when cloning pages
required_offers = OfferTemplate.objects.filter(pages__region=source_region)
Expand All @@ -292,7 +302,9 @@ def save(self, commit: bool = True) -> Region:

# Duplicate language tree
logger.info("Duplicating language tree of %r to %r", source_region, region)
duplicate_language_tree(source_region, region)
duplicate_language_tree(
source_region, region, only_root=not keep_translations
)
# Disable linkcheck listeners to prevent links to be created for outdated versions
with disable_listeners():
# Duplicate pages
Expand All @@ -302,6 +314,7 @@ def save(self, commit: bool = True) -> Region:
region,
keep_status=keep_status,
offers_to_discard=offers_to_discard,
only_root=not keep_translations,
)
# Duplicate Imprint
if source_region.imprint:
Expand Down Expand Up @@ -655,6 +668,7 @@ def duplicate_language_tree(
source_parent: LanguageTreeNode | None = None,
target_parent: LanguageTreeNode | None = None,
logging_prefix: str = "",
only_root: bool = False,
) -> None:
"""
Function to duplicate the language tree of one region to another.
Expand All @@ -669,6 +683,7 @@ def duplicate_language_tree(
:param source_parent: The current parent node id of the recursion
:param target_parent: The node of the target region which is the duplicate of the source parent node
:param logging_prefix: recursion level to get a pretty log output
:param only_root: Set if only the root node should be copied, not its children
"""
logger.debug(
"%s Duplicating child nodes",
Expand Down Expand Up @@ -718,7 +733,7 @@ def duplicate_language_tree(
row_logging_prefix + ("└─" if source_node.is_leaf() else "├─"),
target_node,
)
if not source_node.is_leaf():
if not (source_node.is_leaf() or only_root):
# Call the function recursively for all children of the current node
duplicate_language_tree(
source_region,
Expand All @@ -729,7 +744,7 @@ def duplicate_language_tree(
)


# pylint: disable=too-many-locals, too-many-positional-arguments
# pylint: disable=too-many-locals,too-many-positional-arguments,too-many-arguments
def duplicate_pages(
source_region: Region,
target_region: Region,
Expand All @@ -738,6 +753,7 @@ def duplicate_pages(
logging_prefix: str = "",
keep_status: bool = False,
offers_to_discard: QuerySet[OfferTemplate] | None = None,
only_root: bool = False,
) -> None:
"""
Function to duplicate all non-archived pages from one region to another
Expand All @@ -754,6 +770,7 @@ def duplicate_pages(
:param logging_prefix: Recursion level to get a pretty log output
:param keep_status: Parameter to indicate whether the status of the cloned pages should be kept
:param offers_to_discard: Offers which might be embedded in the source region, but not in the target region
:param only_root: Set if only the root node should be copied, not its children
"""

logger.debug(
Expand Down Expand Up @@ -832,6 +849,7 @@ def duplicate_pages(
row_logging_prefix,
keep_status,
offers_to_discard,
only_root,
)


Expand All @@ -851,7 +869,11 @@ def duplicate_page_translations(
logging_prefix + ("└─" if source_page.is_leaf() else "├─"),
)
# Clone all page translations of the source page
source_page_translations = source_page.translations.all()
source_page_translations = source_page.translations.filter(
language__in=[
node.language for node in target_page.region.language_tree_nodes.all()
]
)
num_translations = len(source_page_translations)
translation_row_logging_prefix = logging_prefix + (
" " if source_page.is_leaf() else "│ "
Expand All @@ -877,12 +899,15 @@ def duplicate_page_translations(
)


def duplicate_imprint(source_region: Region, target_region: Region) -> None:
def duplicate_imprint(
source_region: Region, target_region: Region, only_root: bool = False
) -> None:
"""
Function to duplicate the imprint from one region to another.
:param source_region: the source region from which the imprint should be duplicated
:param target_region: the target region
:param only_root: Set if only the root node should be copied, not its children
"""
source_imprint = source_region.imprint
target_imprint = deepcopy(source_imprint)
Expand All @@ -894,6 +919,9 @@ def duplicate_imprint(source_region: Region, target_region: Region) -> None:

target_imprint.save()

if only_root:
return

# Duplicate imprint translations by iterating to all existing ones
source_page_translations = source_imprint.translations.all()

Expand Down
7 changes: 7 additions & 0 deletions integreat_cms/cms/templates/regions/region_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,13 @@ <h3 class="heading font-bold text-black">
<div class="help-text">
{{ form.duplication_keep_status.help_text }}
</div>
{% render_field form.duplication_keep_translations %}
<label for="{{ form.duplication_keep_translations.id_for_label }}">
{{ form.duplication_keep_translations.label }}
</label>
<div class="help-text">
{{ form.duplication_keep_translations.help_text }}
</div>
<label for="{{ form.duplication_pbo_behavior.id_for_label }}">
{{ form.duplication_pbo_behavior.label }}
</label>
Expand Down
10 changes: 10 additions & 0 deletions integreat_cms/locale/de/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -2251,6 +2251,16 @@ msgstr ""
"Durch das Häkchen wird der Veröffentlichungsstatus der Seiten ebenfalls "
"geklont und nicht auf Entwurf gesetzt."

#: cms/forms/regions/region_form.py
msgid "Copy languages and content translations"
msgstr "Sprachen und Übersetzungen kopieren"

#: cms/forms/regions/region_form.py
msgid ""
"Disable to skip copying of the language tree and all content translations."
msgstr ""
"Abwählen, um das Kopieren des Sprachbaums und aller Inhaltsübersetzungen zu überspringen."

#: cms/forms/regions/region_form.py
msgid "Page based offers cloning behavior"
msgstr "Klonverhalten für seitenbasierte Angebote"
Expand Down
96 changes: 92 additions & 4 deletions tests/cms/test_duplicate_regions.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,13 @@ def test_duplicate_regions(
"longitude": 1,
"latitude": 1,
"duplicated_region": 1,
"duplication_keep_translations": True,
"duplication_pbo_behavior": "activate_missing",
"zammad_url": "https://zammad-test.example.com",
"timezone": "Europe/Berlin",
"mt_renewal_month": 6,
},
)
print(response.headers)
assert response.status_code == 302

target_region = Region.objects.get(slug="cloned")
Expand Down Expand Up @@ -98,12 +98,12 @@ def test_duplicate_regions(
# Check if all cloned page translations exist and are identical
source_page_translations = source_page.translations.all()
# Limit target page translations to all that existed before the links might have been replaced
target_pages_translations = target_page.translations.filter(
target_page_translations = target_page.translations.filter(
last_updated__lt=before_cloning
)
assert len(source_page_translations) == len(target_pages_translations)
assert len(source_page_translations) == len(target_page_translations)
for source_page_translation, target_page_translation in zip(
source_page_translations, target_pages_translations
source_page_translations, target_page_translations
):
source_page_translation_dict = model_to_dict(
source_page_translation,
Expand Down Expand Up @@ -178,3 +178,91 @@ def test_duplicate_regions(
combinations = model.objects.values_list("tree_id", "region")
tree_ids = [tree_id for tree_id, region in set(combinations)]
assert len(tree_ids) == len(set(tree_ids))


# pylint: disable=too-many-locals
@pytest.mark.order("last")
@pytest.mark.django_db(transaction=True, serialized_rollback=True)
def test_duplicate_regions_no_translations(
load_test_data_transactional: None, admin_client: Client
) -> None:
"""
Test whether duplicating regions works as expected when disabling the duplication of translations
:param load_test_data_transactional: The fixture providing the test data (see :meth:`~tests.conftest.load_test_data_transactional`)
:param admin_client: The fixture providing the logged in admin (see :fixture:`admin_client`)
"""
source_region = Region.objects.get(id=1)
target_region_slug = "cloned"
assert not Region.objects.filter(
slug=target_region_slug
).exists(), "The target region should not exist before cloning"

before_cloning = timezone.now()
url = reverse("new_region")
response = admin_client.post(
url,
data={
"administrative_division": "CITY",
"name": "cloned",
"admin_mail": "[email protected]",
"postal_code": "11111",
"status": "ACTIVE",
"longitude": 1,
"latitude": 1,
"duplicated_region": 1,
"duplication_keep_translations": False,
"duplication_pbo_behavior": "activate_missing",
"zammad_url": "https://zammad-test.example.com",
"timezone": "Europe/Berlin",
"mt_renewal_month": 6,
},
)
assert response.status_code == 302

target_region = Region.objects.get(slug="cloned")

source_language_root = source_region.language_tree_root.language
target_languages = target_region.languages
assert len(target_languages) == 1
assert source_language_root in target_languages

# Check if all cloned pages exist and are identical
source_pages = source_region.non_archived_pages
target_pages = target_region.pages.all()
assert len(source_pages) == len(target_pages)
for source_page, target_page in zip(source_pages, target_pages):
source_page_dict = model_to_dict(
source_page,
exclude=[
"id",
"tree_id",
"region",
"parent",
"api_token",
"authors",
"editors",
],
)
target_page_dict = model_to_dict(
target_page,
exclude=[
"id",
"tree_id",
"region",
"parent",
"api_token",
"authors",
"editors",
],
)
assert source_page_dict == target_page_dict

source_page_translations_filtered = source_page.translations.filter(
language=source_language_root
)
target_page_translations = target_page.translations.filter(
last_updated__lt=before_cloning
)
assert len(source_page_translations_filtered) == len(target_page_translations)
assert target_page_translations[0].language == source_language_root

0 comments on commit 1e6dde2

Please sign in to comment.