Coverage for app/venv/lib/python3.14/site-packages/weblate/fonts/models.py: 69%
75 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 django.conf import settings
6from django.core.exceptions import ValidationError
7from django.core.files.storage import FileSystemStorage
8from django.db import models
9from django.urls import reverse
10from django.utils.translation import gettext_lazy
12from weblate.fonts.utils import get_font_name
13from weblate.fonts.validators import validate_font
14from weblate.lang.models import Language
15from weblate.trans.mixins import UserDisplayMixin
16from weblate.trans.models import Project
17from weblate.utils.data import data_dir
19FONT_STORAGE = FileSystemStorage(location=data_dir("fonts"))
22class Font(models.Model, UserDisplayMixin):
23 family = models.CharField(
24 verbose_name=gettext_lazy("Font family"),
25 max_length=100,
26 blank=True,
27 db_index=False,
28 )
29 style = models.CharField(
30 verbose_name=gettext_lazy("Font style"), max_length=100, blank=True
31 )
32 font = models.FileField(
33 verbose_name=gettext_lazy("Font file"),
34 validators=[validate_font],
35 storage=FONT_STORAGE,
36 help_text=gettext_lazy("OpenType and TrueType fonts are supported."),
37 )
38 project = models.ForeignKey(Project, on_delete=models.deletion.CASCADE)
39 timestamp = models.DateTimeField(auto_now_add=True)
40 user = models.ForeignKey(
41 settings.AUTH_USER_MODEL,
42 null=True,
43 blank=True,
44 on_delete=models.deletion.SET_NULL,
45 )
47 class Meta:
48 unique_together = [ # noqa: RUF012
49 ("family", "style", "project")
50 ]
51 verbose_name = "Font"
52 verbose_name_plural = "Fonts"
54 def __str__(self) -> str:
55 return f"{self.family} {self.style}"
57 def __init__(self, *args, **kwargs) -> None:
58 super().__init__(*args, **kwargs)
59 self.field_errors: dict[str, list[ValidationError]] = {}
61 def save(
62 self, force_insert=False, force_update=False, using=None, update_fields=None
63 ) -> None:
64 from weblate.fonts.tasks import update_fonts_cache
66 self.clean()
67 super().save(
68 force_insert=force_insert,
69 force_update=force_update,
70 using=using,
71 update_fields=update_fields,
72 )
73 update_fonts_cache.delay()
75 def get_absolute_url(self) -> str:
76 return reverse("font", kwargs={"pk": self.pk, "project": self.project.slug})
78 def clean_fields(self, exclude=None) -> None:
79 self.field_errors = {}
80 try:
81 super().clean_fields(exclude)
82 except ValidationError as error:
83 self.field_errors = error.error_dict
84 raise
86 def clean(self) -> None:
87 # Try to parse file only if it passed validation
88 if self.font and "font" not in self.field_errors and not self.family:
89 self.family, self.style = get_font_name(self.font)
91 def get_usage(self):
92 related = FontGroup.objects.filter(
93 models.Q(font=self) | models.Q(fontoverride__font=self)
94 )
95 return related.order().distinct()
98class FontGroupQuerySet(models.QuerySet):
99 def order(self):
100 return self.order_by("name")
103class FontGroup(models.Model):
104 name = models.SlugField(
105 verbose_name=gettext_lazy("Font group name"),
106 max_length=100,
107 help_text=gettext_lazy(
108 "Identifier you will use in checks to select this font group. "
109 "Avoid whitespaces and special characters."
110 ),
111 )
112 font = models.ForeignKey(
113 Font,
114 verbose_name=gettext_lazy("Default font"),
115 on_delete=models.deletion.CASCADE,
116 help_text=gettext_lazy(
117 "Default font is used unless per language override matches."
118 ),
119 )
120 project = models.ForeignKey(
121 Project, on_delete=models.deletion.CASCADE, db_index=False
122 )
124 objects = FontGroupQuerySet.as_manager()
126 class Meta:
127 unique_together = [ # noqa: RUF012
128 ("project", "name")
129 ]
130 verbose_name = "Font group"
131 verbose_name_plural = "Font groups"
133 def __str__(self) -> str:
134 return self.name
136 def get_absolute_url(self) -> str:
137 return reverse(
138 "font_group", kwargs={"pk": self.pk, "project": self.project.slug}
139 )
142class FontOverride(models.Model):
143 group = models.ForeignKey(
144 FontGroup, on_delete=models.deletion.CASCADE, db_index=False
145 )
146 font = models.ForeignKey(
147 Font, on_delete=models.deletion.CASCADE, verbose_name=gettext_lazy("Font")
148 )
149 language = models.ForeignKey(
150 Language,
151 on_delete=models.deletion.CASCADE,
152 verbose_name=gettext_lazy("Language"),
153 )
155 class Meta:
156 unique_together = [ # noqa: RUF012
157 ("group", "language")
158 ]
159 verbose_name = "Font override"
160 verbose_name_plural = "Font overrides"
162 def __str__(self) -> str:
163 return f"{self.group}:{self.font}:{self.language}"