Coverage for app/venv/lib/python3.14/site-packages/weblate/glossary/tasks.py: 64%
48 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 07:15 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 07:15 +0000
1# Copyright © Michal Čihař <michal@weblate.org>
2#
3# SPDX-License-Identifier: GPL-3.0-or-later
5from __future__ import annotations
7from django.db import transaction
8from django.db.models import F
10from weblate.auth.models import get_anonymous
11from weblate.lang.models import Language
12from weblate.trans.models import Component, Project, Translation
13from weblate.utils.celery import app
14from weblate.utils.lock import WeblateLockTimeoutError
15from weblate.utils.stats import prefetch_stats
18@app.task(
19 trail=False,
20 autoretry_for=(Component.DoesNotExist, WeblateLockTimeoutError),
21 retry_backoff=60,
22)
23def sync_glossary_languages(pk: int, component: Component | None = None) -> None:
24 """Add missing glossary languages."""
25 if component is None:
26 component = Component.objects.get(pk=pk)
28 language_ids = set(component.translation_set.values_list("language_id", flat=True))
29 missing = (
30 Language.objects.filter(translation__component__project=component.project)
31 .exclude(pk__in=language_ids)
32 .distinct()
33 )
34 if not missing:
35 return
36 with component.repository.lock:
37 component.log_info("Adding glossary languages: %s", missing)
38 # Make sure changes are committed, create_translations might discard pending ones
39 component.commit_pending("glossary languages", None)
40 needs_create = False
41 for language in missing:
42 added = component.add_new_language(
43 language, None, create_translations=False
44 )
45 if added is not None: 45 ↛ 41line 45 didn't jump to line 41 because the condition on line 45 was always true
46 needs_create = True
48 if needs_create: 48 ↛ exitline 48 didn't jump to the function exit
49 # force_scan needed, see add_new_language
50 component.create_translations_immediate(force_scan=True)
53@app.task(trail=False, autoretry_for=(Project.DoesNotExist, WeblateLockTimeoutError))
54def cleanup_stale_glossaries(project: int | Project) -> None:
55 """
56 Delete stale glossaries.
58 A glossary translation is considered stale when it meets the following conditions:
59 - glossary.language is not used in any other non-glossary components
60 - glossary.language is different from glossary.component.source_language
61 - It has no translation
63 Stale glossary is not removed if:
64 - the component only has one glossary component
65 - if is managed outside weblate (i.e repo != 'local:')
66 """
67 if isinstance(project, int):
68 project = Project.objects.get(pk=project)
70 languages_in_non_glossary_components: set[int] = set(
71 Translation.objects.filter(
72 component__project=project, component__is_glossary=False
73 ).values_list("language_id", flat=True)
74 )
76 glossary_translations = prefetch_stats(
77 Translation.objects.filter(
78 component__project=project, component__is_glossary=True
79 )
80 .prefetch()
81 .exclude(language__id__in=languages_in_non_glossary_components)
82 .exclude(language=F("component__source_language"))
83 )
85 component_to_check = []
87 for glossary in glossary_translations:
88 if glossary.can_be_deleted():
89 glossary.remove(get_anonymous())
90 if glossary.component not in component_to_check:
91 component_to_check.append(glossary.component)
93 for component in component_to_check:
94 transaction.on_commit(component.schedule_update_checks)
97@app.task(
98 trail=False,
99 autoretry_for=(Component.DoesNotExist, WeblateLockTimeoutError),
100 retry_backoff=60,
101)
102def sync_terminology(pk: int, component: Component | None = None):
103 """Sync terminology and add missing glossary languages."""
104 if component is None: 104 ↛ 107line 104 didn't jump to line 107 because the condition on line 104 was always true
105 component = Component.objects.get(pk=pk)
107 sync_glossary_languages(pk, component)
109 component.source_translation.sync_terminology()
111 return {"component": pk}