Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

10896 Duplicate component shortcut #12320

Merged
merged 25 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
cb93a79
additional attributes in component duplication
gersona Aug 22, 2024
f08e572
additional "duplicate component" added to manage menu
gersona Aug 22, 2024
8efa10f
update tests
gersona Aug 23, 2024
70a7abc
update changelog
gersona Aug 23, 2024
ad3a863
migration file generated for component model
gersona Aug 23, 2024
5600f75
fix test coverage
gersona Aug 26, 2024
40e65ba
Merge remote-tracking branch 'origin/main' into 10896_duplicate_compo…
gersona Aug 26, 2024
ae3db88
Merge remote-tracking branch 'upstream/main' into 10896_duplicate_com…
gersona Sep 30, 2024
2888e82
copy duplicate fields from source component
gersona Oct 8, 2024
d6d899a
Merge remote-tracking branch 'upstream/main' into 10896_duplicate_com…
gersona Oct 8, 2024
66a9fa6
fix migration file conflict
gersona Oct 8, 2024
2e87e5c
Merge branch 'WeblateOrg:main' into 10896_duplicate_component
gersona Oct 8, 2024
fae4983
Merge remote-tracking branch 'upstream/main' into 10896_duplicate_com…
gersona Oct 14, 2024
3c097fd
Merge remote-tracking branch 'upstream/main' into 10896_duplicate_com…
gersona Oct 15, 2024
bfd05be
Revert component fields to required
gersona Oct 15, 2024
15f42a3
Merge branch 'main' into 10896_duplicate_component
gersona Oct 17, 2024
eca49dd
Fix component filtering for an user
nijel Nov 6, 2024
5bc1132
Merge branch 'main' into 10896_duplicate_component
nijel Nov 6, 2024
9bf3c2c
Update weblate/trans/views/create.py
nijel Nov 6, 2024
7bf7152
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 6, 2024
0ac335a
Update weblate/templates/component.html
nijel Nov 6, 2024
f87fe7f
Merge branch 'main' into 10896_duplicate_component
nijel Nov 6, 2024
27ca649
Merge branch 'main' into 10896_duplicate_component
nijel Nov 6, 2024
8ea6e9b
Merge branch 'main' into 10896_duplicate_component
nijel Nov 6, 2024
08998b7
Merge branch 'main' into 10896_duplicate_component
nijel Nov 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Not yet released.
* :ref:`vcs-bitbucket-cloud`.

**Improvements**
* A shortcut to duplicate a component is now available directly in the menu (:guilabel:`Manage` → :guilabel:`Duplicate Component`)

* :ref:`mt-modernmt` supports :ref:`glossary-mt`.
* :ref:`mt-deepl` now supports specifying translation context.
Expand Down
1 change: 1 addition & 0 deletions weblate/templates/component.html
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
<li><a href="{% url 'manage-access' project=object.project.slug %}">{% trans "Users" %}</a></li>
{% endif %}
{% if user_can_edit_component %}
<li><a href="{% url 'create-component' %}?component={{ object.id }}#existing">{% trans "Duplicate Component" %}</li>
nijel marked this conversation as resolved.
Show resolved Hide resolved
<li><a href="{% url 'guide' path=object.get_url_path %}">{% trans "Community localization checklist" %}</a></li>
<li><a href="{% url 'addons' path=object.get_url_path %}">{% trans "Add-ons" %}</a></li>
{% endif %}
Expand Down
6 changes: 6 additions & 0 deletions weblate/trans/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -1657,6 +1657,12 @@ def clean(self) -> None:
class ComponentCreateForm(SettingsBaseForm, ComponentDocsMixin, ComponentAntispamMixin):
"""Component creation form."""

source_component = forms.ModelChoiceField(
queryset=Component.objects.none(),
required=False,
widget=forms.HiddenInput(),
)

class Meta:
model = Component
fields = [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Copyright © Michal Čihař <michal@weblate.org>
#
# SPDX-License-Identifier: GPL-3.0-or-later

# Generated by Django 5.0.6 on 2024-09-30 10:38

from django.db import migrations, models

import weblate.utils.render


class Migration(migrations.Migration):
dependencies = [
("trans", "0024_component_key_filter"),
]

operations = [
migrations.AlterField(
model_name="component",
name="add_message",
field=models.TextField(
blank=True,
default="Added translation using Weblate ({{ language_name }})\n\n",
help_text="You can use template language for various info, please consult the documentation for more details.",
validators=[weblate.utils.render.validate_render_commit],
verbose_name="Commit message when adding translation",
),
),
migrations.AlterField(
model_name="component",
name="addon_message",
field=models.TextField(
blank=True,
default='Update translation files\n\nUpdated by "{{ addon_name }}" add-on in Weblate.\n\nTranslation: {{ project_name }}/{{ component_name }}\nTranslate-URL: {{ url }}',
help_text="You can use template language for various info, please consult the documentation for more details.",
validators=[weblate.utils.render.validate_render_addon],
verbose_name="Commit message when add-on makes a change",
),
),
migrations.AlterField(
model_name="component",
name="delete_message",
field=models.TextField(
blank=True,
default="Deleted translation using Weblate ({{ language_name }})\n\n",
help_text="You can use template language for various info, please consult the documentation for more details.",
validators=[weblate.utils.render.validate_render_commit],
verbose_name="Commit message when removing translation",
),
),
migrations.AlterField(
model_name="component",
name="merge_message",
field=models.TextField(
blank=True,
default="Merge branch '{{ component_remote_branch }}' into Weblate.\n\n",
help_text="You can use template language for various info, please consult the documentation for more details.",
validators=[weblate.utils.render.validate_render_component],
verbose_name="Commit message when merging translation",
),
),
migrations.AlterField(
model_name="component",
name="merge_style",
field=models.CharField(
blank=True,
choices=[
("merge", "Merge"),
("rebase", "Rebase"),
("merge_noff", "Merge without fast-forward"),
],
default="rebase",
help_text="Define whether Weblate should merge the upstream repository or rebase changes onto it.",
max_length=10,
verbose_name="Merge style",
),
),
migrations.AlterField(
model_name="component",
name="pull_message",
field=models.TextField(
blank=True,
default="Translations update from {{ site_title }}\n\nTranslations update from [{{ site_title }}]({{ site_url }}) for [{{ project_name }}/{{ component_name }}]({{url}}).\n\n{% if component_linked_childs %}\nIt also includes following components:\n{% for linked in component_linked_childs %}\n* [{{ linked.project_name }}/{{ linked.name }}]({{ linked.url }})\n{% endfor %}\n{% endif %}\n\nCurrent translation status:\n\n![Weblate translation status]({{widget_url}})\n",
help_text="You can use template language for various info, please consult the documentation for more details.",
validators=[weblate.utils.render.validate_render_addon],
verbose_name="Merge request message",
),
),
]
6 changes: 6 additions & 0 deletions weblate/trans/models/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,7 @@
verbose_name=gettext_lazy("Merge style"),
max_length=10,
choices=MERGE_CHOICES,
blank=True,
default=settings.DEFAULT_MERGE_STYLE,
help_text=gettext_lazy(
"Define whether Weblate should merge the upstream repository "
Expand All @@ -636,6 +637,7 @@
"please consult the documentation for more details."
),
validators=[validate_render_commit],
blank=True,
default=settings.DEFAULT_ADD_MESSAGE,
)
delete_message = models.TextField(
Expand All @@ -645,6 +647,7 @@
"please consult the documentation for more details."
),
validators=[validate_render_commit],
blank=True,
default=settings.DEFAULT_DELETE_MESSAGE,
)
merge_message = models.TextField(
Expand All @@ -654,6 +657,7 @@
"please consult the documentation for more details."
),
validators=[validate_render_component],
blank=True,
default=settings.DEFAULT_MERGE_MESSAGE,
)
addon_message = models.TextField(
Expand All @@ -663,6 +667,7 @@
"please consult the documentation for more details."
),
validators=[validate_render_addon],
blank=True,
default=settings.DEFAULT_ADDON_MESSAGE,
)
pull_message = models.TextField(
Expand All @@ -672,6 +677,7 @@
"please consult the documentation for more details."
),
validators=[validate_render_addon],
blank=True,
gersona marked this conversation as resolved.
Show resolved Hide resolved
default=settings.DEFAULT_PULL_MESSAGE,
)
push_on_commit = models.BooleanField(
Expand Down Expand Up @@ -1104,7 +1110,7 @@
# Calculate progress for translations
if progress is None:
self.translations_progress += 1
progress = 100 * self.translations_progress // self.translations_count

Check failure on line 1113 in weblate/trans/models/component.py

View workflow job for this annotation

GitHub Actions / mypy

Unsupported operand types for // ("int" and "None")
# Store task state
current_task.update_state(
state="PROGRESS", meta={"progress": progress, "component": self.pk}
Expand Down Expand Up @@ -1753,7 +1759,7 @@
from weblate.trans.tasks import perform_push

self.log_info("scheduling push")
perform_push.delay_on_commit(

Check failure on line 1762 in weblate/trans/models/component.py

View workflow job for this annotation

GitHub Actions / mypy

"Task[[Any, VarArg(Any), KwArg(Any)], None]" has no attribute "delay_on_commit"
self.pk, None, force_commit=False, do_update=do_update
)

Expand Down Expand Up @@ -2340,7 +2346,7 @@
.order_by("-id")[0]
.auto_status
):
self.do_lock(user=None, lock=False, auto=True)

Check failure on line 2349 in weblate/trans/models/component.py

View workflow job for this annotation

GitHub Actions / mypy

Argument "user" to "do_lock" of "Component" has incompatible type "None"; expected "User"

if ALERTS[alert].link_wide:
for component in self.linked_childs:
Expand All @@ -2358,7 +2364,7 @@

# Automatically lock on error
if created and self.auto_lock_error and alert in LOCKING_ALERTS:
self.do_lock(user=None, lock=True, auto=True)

Check failure on line 2367 in weblate/trans/models/component.py

View workflow job for this annotation

GitHub Actions / mypy

Argument "user" to "do_lock" of "Component" has incompatible type "None"; expected "User"

# Update details with exception of component removal
if not created and not noupdate:
Expand Down Expand Up @@ -2563,7 +2569,7 @@
)
self.handle_parse_error(error.__cause__, filename=self.template)
self.update_import_alerts()
raise error.__cause__ from error # pylint: disable=E0710

Check failure on line 2572 in weblate/trans/models/component.py

View workflow job for this annotation

GitHub Actions / mypy

Exception must be derived from BaseException
was_change |= bool(translation.reason)
translations[translation.id] = translation
languages[lang.code] = translation
Expand Down Expand Up @@ -2627,7 +2633,7 @@
if self.needs_cleanup and not self.template:
from weblate.trans.tasks import cleanup_component

cleanup_component.delay_on_commit(self.id)

Check failure on line 2636 in weblate/trans/models/component.py

View workflow job for this annotation

GitHub Actions / mypy

"Task[[Any], None]" has no attribute "delay_on_commit"

if was_change:
if self.needs_variants_update:
Expand Down Expand Up @@ -2659,7 +2665,7 @@
if settings.CELERY_TASK_ALWAYS_EAGER:
batch_update_checks(self.id, batched_checks, component=self)
else:
batch_update_checks.delay_on_commit(self.id, batched_checks)

Check failure on line 2668 in weblate/trans/models/component.py

View workflow job for this annotation

GitHub Actions / mypy

"Task[[Any, Any, Component | None], None]" has no attribute "delay_on_commit"
self.batch_checks = False
self.batched_checks = set()

Expand Down Expand Up @@ -2810,7 +2816,7 @@
raise ValidationError(
{"filemask": gettext("The file mask did not match any files.")}
)
langs = {}

Check failure on line 2819 in weblate/trans/models/component.py

View workflow job for this annotation

GitHub Actions / mypy

Need type annotation for "langs" (hint: "langs: dict[<type>, <type>] = ...")
existing_langs = set()

for match in matches:
Expand Down Expand Up @@ -2878,7 +2884,7 @@
if (not self.new_base and self.new_lang != "add") or not self.file_format:
return
# File is valid or no file is needed
errors = []

Check failure on line 2887 in weblate/trans/models/component.py

View workflow job for this annotation

GitHub Actions / mypy

Need type annotation for "errors" (hint: "errors: list[<type>] = ...")
if self.is_valid_base_for_new(errors):
return
# File is needed, but not present
Expand Down Expand Up @@ -3249,7 +3255,7 @@
if self.variant_regex:
variant_re = re.compile(self.variant_regex)
units = process_units.filter(context__regex=self.variant_regex)
variant_updates = {}

Check failure on line 3258 in weblate/trans/models/component.py

View workflow job for this annotation

GitHub Actions / mypy

Need type annotation for "variant_updates" (hint: "variant_updates: dict[<type>, <type>] = ...")
for unit in units.iterator():
if variant_re.findall(unit.context):
key = variant_re.sub("", unit.context)
Expand Down
104 changes: 102 additions & 2 deletions weblate/trans/tests/test_create.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

"""Test for creating projects and models."""

import urllib.parse

from django.core.files.uploadedfile import SimpleUploadedFile
from django.test.utils import modify_settings, override_settings
from django.urls import reverse
Expand Down Expand Up @@ -179,22 +181,120 @@ def test_create_component_wizard(self) -> None:

@modify_settings(INSTALLED_APPS={"remove": "weblate.billing"})
def test_create_component_existing(self) -> None:
from weblate.trans.models import Component

# Make superuser
self.user.is_superuser = True
self.user.save()

self.component.agreement = "test agreement"
self.component.merge_style = "merge"
self.component.commit_message = "test commit_message"
self.component.add_message = "test add_message"
self.component.delete_message = "test delete_message"
self.component.merge_message = "test merge_message"
self.component.addon_message = "test addon_message"
self.component.pull_message = "test pull_message"
self.component.save()

response = self.client.get(
reverse("create-component") + f"?component={self.component.pk}#existing",
follow=True,
)
# init step
self.assertContains(response, "Create component")

with override_settings(CREATE_GLOSSARIES=self.CREATE_GLOSSARIES):
response = self.client.post(
reverse("create-component"),
{
"origin": "existing",
"name": "Create Component",
"slug": "create-component",
"name": "Create Component From Existing",
"slug": "create-component-from-existing",
"component": self.component.pk,
"is_glossary": self.component.is_glossary,
},
follow=True,
)

self.assertContains(response, self.component.get_repo_link_url())
parsed_query = urllib.parse.parse_qs(response.request["QUERY_STRING"])
expected_query_strings = [
"vcs",
"source_language",
"license",
]
for field in expected_query_strings:
if component_value := getattr(self.component, field):
if field == "source_language":
component_value = str(component_value.id)
self.assertEqual(parsed_query[field][0], component_value)

self.assertEqual(parsed_query["source_component"][0], str(self.component.pk))

# discovery step
self.assertContains(response, "Choose translation files to import")

with override_settings(CREATE_GLOSSARIES=self.CREATE_GLOSSARIES):
response = self.client.post(
reverse("create-component-vcs")
+ f"?source_component={self.component.pk}#existing,",
{
"name": "Create Component From Existing",
"slug": "create-component-from-existing",
"is_glossary": self.component.is_glossary,
"project": self.component.project_id,
"vcs": self.component.vcs,
"repo": self.component.repo,
"discovery": 28, # deep/*/locales/*/LC_MESSAGES/messages.po
"source_language": self.component.source_language_id,
},
follow=True,
)
self.assertContains(
response,
"You will be able to edit more options in the component settings after creating it.",
)

with override_settings(CREATE_GLOSSARIES=self.CREATE_GLOSSARIES):
response = self.client.post(
reverse("create-component-vcs")
+ f"?source_component={self.component.pk}#existing,",
{
"name": "Create Component From Existing",
"slug": "create-component-from-existing",
"is_glossary": self.component.is_glossary,
"project": self.component.project_id,
"vcs": self.component.vcs,
"repo": self.component.repo,
"source_language": self.component.source_language_id,
"file_format": "po",
"filemask": "deep/*/locales/*/LC_MESSAGES/messages.po",
"new_lang": "add",
"new_base": "deep/cs/locales/cs/LC_MESSAGES/messages.po",
"language_regex": "^[^.]+$",
"source_component": self.component.pk,
},
follow=True,
)
self.assertContains(response, "Community localization checklist")
self.assertContains(response, "Test/Create Component From Existing @ Weblate")

new_component = Component.objects.get(name="Create Component From Existing")
cloned_fields = [
"agreement",
"merge_style",
"commit_message",
"add_message",
"delete_message",
"merge_message",
"addon_message",
"pull_message",
]
for field in cloned_fields:
self.assertEqual(
getattr(new_component, field), getattr(self.component, field)
)

@modify_settings(INSTALLED_APPS={"remove": "weblate.billing"})
def test_create_component_branch_fail(self) -> None:
Expand Down
12 changes: 4 additions & 8 deletions weblate/trans/tests/test_reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from django.urls import reverse
from django.utils import timezone

from weblate.trans.models.category import Category
from weblate.trans.tests.test_views import ViewTestCase
from weblate.trans.views.reports import generate_counts, generate_credits

Expand Down Expand Up @@ -397,15 +396,12 @@ def get_kwargs(self):
class ReportsCategoryTest(ReportsComponentTest):
def setUp(self) -> None:
super().setUp()
self.category = self.create_category()
self.setup_category()

def create_category(self) -> None:
category = Category.objects.create(
name="test category", slug="test-category", project=self.project
)
self.component.category = category
def setup_category(self) -> None:
self.component.category = self.create_category(project=self.project)
self.component.save()
return category
self.category = self.component.category

def get_kwargs(self) -> dict[str, tuple]:
return {"path": self.category.get_url_path()}
8 changes: 7 additions & 1 deletion weblate/trans/tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from weblate.auth.models import User
from weblate.configuration.models import Setting, SettingCategory
from weblate.formats.models import FILE_FORMATS
from weblate.trans.models import Component, Project
from weblate.trans.models import Category, Component, Project
from weblate.utils.files import remove_tree
from weblate.vcs.models import VCS_REGISTRY

Expand Down Expand Up @@ -166,6 +166,12 @@ def create_project(self, **kwargs):
self.addCleanup(remove_tree, project.full_path, True)
return project

def create_category(self, project, **kwargs):
"""Create test category."""
return Category.objects.create(
name="Test category", slug="test-category", project=project, **kwargs
)

def format_local_path(self, path):
"""Format path for local access to the repository."""
if sys.platform != "win32":
Expand Down
Loading
Loading