• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rdmorganiser / rdmo / 20428555173

22 Dec 2025 10:02AM UTC coverage: 94.693% (-0.1%) from 94.814%
20428555173

Pull #1436

github

web-flow
Merge 0ffb48a5d into 57a75b09e
Pull Request #1436: Draft: add `rdmo.config` app for `Plugin` model (plugin managament)

2191 of 2304 branches covered (95.1%)

23411 of 24723 relevant lines covered (94.69%)

3.79 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.18
rdmo/options/models.py
1
from __future__ import annotations
4✔
2

3
from django.conf import settings
4✔
4
from django.contrib.sites.models import Site
4✔
5
from django.db import models
4✔
6
from django.utils.functional import cached_property
4✔
7
from django.utils.translation import gettext_lazy as _
4✔
8

9
from rdmo.conditions.models import Condition
4✔
10
from rdmo.core.models import TranslationMixin
4✔
11
from rdmo.core.utils import join_url
4✔
12

13

14
class OptionSet(models.Model):
4✔
15

16
    uri = models.URLField(
4✔
17
        max_length=800, blank=True,
18
        verbose_name=_('URI'),
19
        help_text=_('The Uniform Resource Identifier of this option set (auto-generated).')
20
    )
21
    uri_prefix = models.URLField(
4✔
22
        max_length=256,
23
        verbose_name=_('URI Prefix'),
24
        help_text=_('The prefix for the URI of this option set.')
25
    )
26
    uri_path = models.CharField(
4✔
27
        max_length=512, blank=True,
28
        verbose_name=_('URI Path'),
29
        help_text=_('The path for the URI of this option set.')
30
    )
31
    comment = models.TextField(
4✔
32
        blank=True,
33
        verbose_name=_('Comment'),
34
        help_text=_('Additional internal information about this option set.')
35
    )
36
    locked = models.BooleanField(
4✔
37
        default=False,
38
        verbose_name=_('Locked'),
39
        help_text=_('Designates whether this option set (and its options) can be changed.')
40
    )
41
    order = models.IntegerField(
4✔
42
        default=0,
43
        verbose_name=_('Order'),
44
        help_text=_('The position of this option set in lists.')
45
    )
46
    editors = models.ManyToManyField(
4✔
47
        Site, related_name='optionsets_as_editor', blank=True,
48
        verbose_name=_('Editors'),
49
        help_text=_('The sites that can edit this option set (in a multi site setup).')
50
    )
51
    provider_key = models.SlugField(
4✔
52
        max_length=128, blank=True,
53
        verbose_name=_('Provider'),
54
        help_text=_('The provider for this optionset. If set, it will create dynamic options for this optionset.')
55
    )
56
    options = models.ManyToManyField(
4✔
57
        'Option', through='OptionSetOption', blank=True, related_name='optionsets',
58
        verbose_name=_('Options'),
59
        help_text=_('The list of options for this option set.')
60
    )
61
    conditions = models.ManyToManyField(
4✔
62
        Condition, blank=True, related_name='optionsets',
63
        verbose_name=_('Conditions'),
64
        help_text=_('The list of conditions evaluated for this option set.')
65
    )
66
    plugins = models.ManyToManyField(  # can not import Plugin due to circular import
4✔
67
        'config.Plugin', blank=True, related_name='optionsets',
68
        verbose_name=_('Plugins'),
69
        help_text=_('The list of plugins evaluated for this option set.')
70
    )
71

72
    class Meta:
4✔
73
        ordering = ('uri', )
4✔
74
        verbose_name = _('Option set')
4✔
75
        verbose_name_plural = _('Option sets')
4✔
76

77
    def __str__(self):
4✔
78
        return self.uri
4✔
79

80
    def save(self, *args, **kwargs):
4✔
81
        self.uri = self.build_uri(self.uri_prefix, self.uri_path)
4✔
82
        super().save(*args, **kwargs)
4✔
83

84
    @property
4✔
85
    def label(self) -> str:
4✔
86
        return self.uri
×
87

88
    @property
4✔
89
    def has_plugins(self) -> bool:
4✔
90
        return self.plugins.exists()
4✔
91

92
    @property
4✔
93
    def has_search(self) -> bool:
4✔
94
        return self.has_plugins and any(i.has_search for i in self.plugins.all())
4✔
95

96
    @property
4✔
97
    def has_refresh(self) -> bool:
4✔
98
        return self.has_plugins and any(i.has_refresh for i in self.plugins.all())
4✔
99

100
    @property
4✔
101
    def has_conditions(self) -> bool:
4✔
102
        return self.conditions.exists()
4✔
103

104
    @property
4✔
105
    def is_locked(self) -> bool:
4✔
106
        return self.locked
4✔
107

108
    @cached_property
4✔
109
    def elements(self) -> list[Option]:
4✔
110
        return [element.option for element in sorted(self.optionset_options.all(), key=lambda e: e.order)]
4✔
111

112
    @classmethod
4✔
113
    def build_uri(cls, uri_prefix, uri_path):
4✔
114
        if not uri_path:
4✔
115
            raise RuntimeError('uri_path is missing')
×
116
        return join_url(uri_prefix or settings.DEFAULT_URI_PREFIX, '/options/', uri_path)
4✔
117

118

119
class OptionSetOption(models.Model):
4✔
120

121
    optionset = models.ForeignKey(
4✔
122
        'OptionSet', on_delete=models.CASCADE, related_name='optionset_options'
123
    )
124
    option = models.ForeignKey(
4✔
125
        'Option', on_delete=models.CASCADE, related_name='option_optionsets'
126
    )
127
    order = models.IntegerField(
4✔
128
        default=0
129
    )
130

131
    class Meta:
4✔
132
        ordering = ('optionset', 'order')
4✔
133

134
    def __str__(self):
4✔
135
        return f'{self.optionset} / {self.option} [{self.order}]'
×
136

137

138
class Option(models.Model, TranslationMixin):
4✔
139

140
    ADDITIONAL_INPUT_NONE = ''
4✔
141
    ADDITIONAL_INPUT_TEXT = 'text'
4✔
142
    ADDITIONAL_INPUT_TEXTAREA = 'textarea'
4✔
143
    ADDITIONAL_INPUT_CHOICES = (
4✔
144
        (ADDITIONAL_INPUT_NONE, '---------'),
145
        (ADDITIONAL_INPUT_TEXT, _('Text')),
146
        (ADDITIONAL_INPUT_TEXTAREA, _('Textarea'))
147
    )
148

149
    uri = models.URLField(
4✔
150
        max_length=800, blank=True,
151
        verbose_name=_('URI'),
152
        help_text=_('The Uniform Resource Identifier of this option (auto-generated).')
153
    )
154
    uri_prefix = models.URLField(
4✔
155
        max_length=256,
156
        verbose_name=_('URI Prefix'),
157
        help_text=_('The prefix for the URI of this option.')
158
    )
159
    uri_path = models.CharField(
4✔
160
        max_length=512, blank=True,
161
        verbose_name=_('URI Path'),
162
        help_text=_('The path for the URI of this option.')
163
    )
164
    comment = models.TextField(
4✔
165
        blank=True,
166
        verbose_name=_('Comment'),
167
        help_text=_('Additional internal information about this option.')
168
    )
169
    locked = models.BooleanField(
4✔
170
        default=False,
171
        verbose_name=_('Locked'),
172
        help_text=_('Designates whether this option can be changed.')
173
    )
174
    editors = models.ManyToManyField(
4✔
175
        Site, related_name='options_as_editor', blank=True,
176
        verbose_name=_('Editors'),
177
        help_text=_('The sites that can edit this option (in a multi site setup).')
178
    )
179
    text_lang1 = models.CharField(
4✔
180
        max_length=256, blank=True,
181
        verbose_name=_('Text (primary)'),
182
        help_text=_('The text for this option (in the primary language).')
183
    )
184
    text_lang2 = models.CharField(
4✔
185
        max_length=256, blank=True,
186
        verbose_name=_('Text (secondary)'),
187
        help_text=_('The text for this option (in the secondary language).')
188
    )
189
    text_lang3 = models.CharField(
4✔
190
        max_length=256, blank=True,
191
        verbose_name=_('Text (tertiary)'),
192
        help_text=_('The text for this option (in the tertiary language).')
193
    )
194
    text_lang4 = models.CharField(
4✔
195
        max_length=256, blank=True,
196
        verbose_name=_('Text (quaternary)'),
197
        help_text=_('The text for this option (in the quaternary language).')
198
    )
199
    text_lang5 = models.CharField(
4✔
200
        max_length=256, blank=True,
201
        verbose_name=_('Text (quinary)'),
202
        help_text=_('The text for this option (in the quinary language).')
203
    )
204
    help_lang1 = models.TextField(
4✔
205
        blank=True, default="",
206
        verbose_name=_('Help (primary)'),
207
        help_text=_('The help text for this option (in the primary language).')
208
    )
209
    help_lang2 = models.TextField(
4✔
210
        blank=True, default="",
211
        verbose_name=_('Help (secondary)'),
212
        help_text=_('The help text for this option (in the secondary language).')
213
    )
214
    help_lang3 = models.TextField(
4✔
215
        blank=True, default="",
216
        verbose_name=_('Help (tertiary)'),
217
        help_text=_('The help text for this option (in the tertiary language).')
218
    )
219
    help_lang4 = models.TextField(
4✔
220
        blank=True, default="",
221
        verbose_name=_('Help (quaternary)'),
222
        help_text=_('The help text for this option (in the quaternary language).')
223
    )
224
    help_lang5 = models.TextField(
4✔
225
        blank=True, default="",
226
        verbose_name=_('Help (quinary)'),
227
        help_text=_('The help text for this option (in the quinary language).')
228
    )
229
    default_text_lang1 = models.TextField(
4✔
230
        blank=True, default="",
231
        verbose_name=_('Default text value (primary)'),
232
        help_text=_('The default text value for the additional input of this option (in the primary language).')
233
    )
234
    default_text_lang2 = models.TextField(
4✔
235
        blank=True, default="",
236
        verbose_name=_('Default text value (secondary)'),
237
        help_text=_('The default text value for the additional input of this option (in the secondary language).')
238
    )
239
    default_text_lang3 = models.TextField(
4✔
240
        blank=True, default="",
241
        verbose_name=_('Default text value (tertiary)'),
242
        help_text=_('The default text value for the additional input of this option (in the tertiary language).')
243
    )
244
    default_text_lang4 = models.TextField(
4✔
245
        blank=True, default="",
246
        verbose_name=_('Default text value (quaternary)'),
247
        help_text=_('The default text value for the additional input of this option (in the quaternary language).')
248
    )
249
    default_text_lang5 = models.TextField(
4✔
250
        blank=True, default="",
251
        verbose_name=_('Default text value (quinary)'),
252
        help_text=_('The default text value for the additional input of this option (in the quinary language).')
253
    )
254
    view_text_lang1 = models.TextField(
4✔
255
        blank=True, default="",
256
        verbose_name=_('View text (primary)'),
257
        help_text=_('The view text for this option (in the primary language).')
258
    )
259
    view_text_lang2 = models.TextField(
4✔
260
        blank=True, default="",
261
        verbose_name=_('View text (secondary)'),
262
        help_text=_('The view text for this option (in the secondary language).')
263
    )
264
    view_text_lang3 = models.TextField(
4✔
265
        blank=True, default="",
266
        verbose_name=_('View text (tertiary)'),
267
        help_text=_('The view text for this option (in the tertiary language).')
268
    )
269
    view_text_lang4 = models.TextField(
4✔
270
        blank=True, default="",
271
        verbose_name=_('View text (quaternary)'),
272
        help_text=_('The view text for this option (in the quaternary language).')
273
    )
274
    view_text_lang5 = models.TextField(
4✔
275
        blank=True, default="",
276
        verbose_name=_('View text (quinary)'),
277
        help_text=_('The view text for this option (in the quinary language).')
278
    )
279
    additional_input = models.CharField(
4✔
280
        max_length=256, blank=True, default=ADDITIONAL_INPUT_NONE, choices=ADDITIONAL_INPUT_CHOICES,
281
        verbose_name=_('Additional input'),
282
        help_text=_('Designates whether an additional input is possible for this option.')
283
    )
284

285
    class Meta:
4✔
286
        ordering = ('uri', )
4✔
287
        verbose_name = _('Option')
4✔
288
        verbose_name_plural = _('Options')
4✔
289

290
    def __str__(self):
4✔
291
        return self.uri
4✔
292

293
    def save(self, *args, **kwargs):
4✔
294
        self.uri = self.build_uri(self.uri_prefix, self.uri_path)
4✔
295
        super().save(*args, **kwargs)
4✔
296

297
    @property
4✔
298
    def text(self) -> str:
4✔
299
        return self.trans('text')
4✔
300

301
    @property
4✔
302
    def help(self) -> str:
4✔
303
        return self.trans('help')
4✔
304

305
    @property
4✔
306
    def default_text(self) -> str:
4✔
307
        return self.trans('default_text')
4✔
308

309
    @property
4✔
310
    def view_text(self) -> str:
4✔
311
        return self.trans('view_text')
4✔
312

313
    @property
4✔
314
    def text_and_help(self) -> str:
4✔
315
        return f'{self.text} [{self.help}]' if self.help else self.text
4✔
316

317
    @property
4✔
318
    def label(self) -> str:
4✔
319
        return f'{self.uri} ("{self.text}")'
4✔
320

321
    @property
4✔
322
    def is_locked(self) -> bool:
4✔
323
        return self.locked or self.optionsets.filter(locked=True).exists()
×
324

325
    @classmethod
4✔
326
    def build_uri(cls, uri_prefix, uri_path):
4✔
327
        if not uri_path:
4✔
328
            raise RuntimeError('uri_path is missing')
×
329
        return join_url(uri_prefix or settings.DEFAULT_URI_PREFIX, '/options/', uri_path)
4✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc