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

1# Copyright © Michal Čihař <michal@weblate.org> 

2# 

3# SPDX-License-Identifier: GPL-3.0-or-later 

4 

5from __future__ import annotations 

6 

7from django.db import transaction 

8from django.db.models import F 

9 

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 

16 

17 

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) 

27 

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 

47 

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) 

51 

52 

53@app.task(trail=False, autoretry_for=(Project.DoesNotExist, WeblateLockTimeoutError)) 

54def cleanup_stale_glossaries(project: int | Project) -> None: 

55 """ 

56 Delete stale glossaries. 

57 

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 

62 

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) 

69 

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 ) 

75 

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 ) 

84 

85 component_to_check = [] 

86 

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) 

92 

93 for component in component_to_check: 

94 transaction.on_commit(component.schedule_update_checks) 

95 

96 

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) 

106 

107 sync_glossary_languages(pk, component) 

108 

109 component.source_translation.sync_terminology() 

110 

111 return {"component": pk}