openedx-learning 0.1.0__py2.py3-none-any.whl → 0.1.2__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openedx_learning/__init__.py +1 -1
- {openedx_learning-0.1.0.dist-info → openedx_learning-0.1.2.dist-info}/METADATA +4 -3
- {openedx_learning-0.1.0.dist-info → openedx_learning-0.1.2.dist-info}/RECORD +25 -9
- openedx_tagging/core/tagging/api.py +45 -1
- openedx_tagging/core/tagging/fixtures/language_taxonomy.yaml +1298 -0
- openedx_tagging/core/tagging/management/commands/__init__.py +0 -0
- openedx_tagging/core/tagging/management/commands/build_language_fixture.py +48 -0
- openedx_tagging/core/tagging/migrations/0003_auto_20230721_1238.py +76 -0
- openedx_tagging/core/tagging/migrations/0004_auto_20230723_2001.py +34 -0
- openedx_tagging/core/tagging/migrations/0005_language_taxonomy.py +29 -0
- openedx_tagging/core/tagging/migrations/0006_alter_objecttag_unique_together.py +16 -0
- openedx_tagging/core/tagging/models/__init__.py +11 -0
- openedx_tagging/core/tagging/{models.py → models/base.py} +112 -24
- openedx_tagging/core/tagging/models/system_defined.py +269 -0
- openedx_tagging/core/tagging/rest_api/urls.py +9 -0
- openedx_tagging/core/tagging/rest_api/v1/__init__.py +0 -0
- openedx_tagging/core/tagging/rest_api/v1/permissions.py +17 -0
- openedx_tagging/core/tagging/rest_api/v1/serializers.py +31 -0
- openedx_tagging/core/tagging/rest_api/v1/urls.py +14 -0
- openedx_tagging/core/tagging/rest_api/v1/views.py +147 -0
- openedx_tagging/core/tagging/rules.py +14 -15
- openedx_tagging/core/tagging/urls.py +10 -0
- {openedx_learning-0.1.0.dist-info → openedx_learning-0.1.2.dist-info}/LICENSE.txt +0 -0
- {openedx_learning-0.1.0.dist-info → openedx_learning-0.1.2.dist-info}/WHEEL +0 -0
- {openedx_learning-0.1.0.dist-info → openedx_learning-0.1.2.dist-info}/top_level.txt +0 -0
|
File without changes
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Script that downloads all the ISO 639-1 languages and processes them
|
|
3
|
+
to write the fixture for the Language system-defined taxonomy.
|
|
4
|
+
|
|
5
|
+
This function is intended to be used only once,
|
|
6
|
+
but can be edited in the future if more data needs to be added to the fixture.
|
|
7
|
+
"""
|
|
8
|
+
import json
|
|
9
|
+
import urllib.request
|
|
10
|
+
|
|
11
|
+
from django.core.management.base import BaseCommand
|
|
12
|
+
|
|
13
|
+
endpoint = "https://pkgstore.datahub.io/core/language-codes/language-codes_json/data/97607046542b532c395cf83df5185246/language-codes_json.json"
|
|
14
|
+
output = "./openedx_tagging/core/tagging/fixtures/language_taxonomy.yaml"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Command(BaseCommand):
|
|
18
|
+
def handle(self, **options):
|
|
19
|
+
json_data = self.download_json()
|
|
20
|
+
self.build_fixture(json_data)
|
|
21
|
+
|
|
22
|
+
def download_json(self):
|
|
23
|
+
with urllib.request.urlopen(endpoint) as response:
|
|
24
|
+
json_data = response.read()
|
|
25
|
+
return json.loads(json_data)
|
|
26
|
+
|
|
27
|
+
def build_fixture(self, json_data):
|
|
28
|
+
tag_pk = -1
|
|
29
|
+
with open(output, "w") as output_file:
|
|
30
|
+
for lang_data in json_data:
|
|
31
|
+
lang_value = self.get_lang_value(lang_data)
|
|
32
|
+
lang_code = lang_data["alpha2"]
|
|
33
|
+
output_file.write("- model: oel_tagging.tag\n")
|
|
34
|
+
output_file.write(f" pk: {tag_pk}\n")
|
|
35
|
+
output_file.write(" fields:\n")
|
|
36
|
+
output_file.write(" taxonomy: -1\n")
|
|
37
|
+
output_file.write(" parent: null\n")
|
|
38
|
+
output_file.write(f" value: {lang_value}\n")
|
|
39
|
+
output_file.write(f" external_id: {lang_code}\n")
|
|
40
|
+
# System tags are identified with negative numbers to avoid clashing with user-created tags.
|
|
41
|
+
tag_pk -= 1
|
|
42
|
+
|
|
43
|
+
def get_lang_value(self, lang_data):
|
|
44
|
+
"""
|
|
45
|
+
Gets the lang value. Some languages has many values.
|
|
46
|
+
"""
|
|
47
|
+
lang_list = lang_data["English"].split(";")
|
|
48
|
+
return lang_list[0]
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Generated by Django 3.2.19 on 2023-07-21 17:38
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
dependencies = [
|
|
8
|
+
("oel_tagging", "0002_auto_20230718_2026"),
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
operations = [
|
|
12
|
+
migrations.CreateModel(
|
|
13
|
+
name="ModelObjectTag",
|
|
14
|
+
fields=[],
|
|
15
|
+
options={
|
|
16
|
+
"proxy": True,
|
|
17
|
+
"indexes": [],
|
|
18
|
+
"constraints": [],
|
|
19
|
+
},
|
|
20
|
+
bases=("oel_tagging.objecttag",),
|
|
21
|
+
),
|
|
22
|
+
migrations.CreateModel(
|
|
23
|
+
name="SystemDefinedTaxonomy",
|
|
24
|
+
fields=[],
|
|
25
|
+
options={
|
|
26
|
+
"proxy": True,
|
|
27
|
+
"indexes": [],
|
|
28
|
+
"constraints": [],
|
|
29
|
+
},
|
|
30
|
+
bases=("oel_tagging.taxonomy",),
|
|
31
|
+
),
|
|
32
|
+
migrations.RemoveField(
|
|
33
|
+
model_name="taxonomy",
|
|
34
|
+
name="system_defined",
|
|
35
|
+
),
|
|
36
|
+
migrations.CreateModel(
|
|
37
|
+
name="LanguageTaxonomy",
|
|
38
|
+
fields=[],
|
|
39
|
+
options={
|
|
40
|
+
"proxy": True,
|
|
41
|
+
"indexes": [],
|
|
42
|
+
"constraints": [],
|
|
43
|
+
},
|
|
44
|
+
bases=("oel_tagging.systemdefinedtaxonomy",),
|
|
45
|
+
),
|
|
46
|
+
migrations.CreateModel(
|
|
47
|
+
name="ModelSystemDefinedTaxonomy",
|
|
48
|
+
fields=[],
|
|
49
|
+
options={
|
|
50
|
+
"proxy": True,
|
|
51
|
+
"indexes": [],
|
|
52
|
+
"constraints": [],
|
|
53
|
+
},
|
|
54
|
+
bases=("oel_tagging.systemdefinedtaxonomy",),
|
|
55
|
+
),
|
|
56
|
+
migrations.CreateModel(
|
|
57
|
+
name="UserModelObjectTag",
|
|
58
|
+
fields=[],
|
|
59
|
+
options={
|
|
60
|
+
"proxy": True,
|
|
61
|
+
"indexes": [],
|
|
62
|
+
"constraints": [],
|
|
63
|
+
},
|
|
64
|
+
bases=("oel_tagging.modelobjecttag",),
|
|
65
|
+
),
|
|
66
|
+
migrations.CreateModel(
|
|
67
|
+
name="UserSystemDefinedTaxonomy",
|
|
68
|
+
fields=[],
|
|
69
|
+
options={
|
|
70
|
+
"proxy": True,
|
|
71
|
+
"indexes": [],
|
|
72
|
+
"constraints": [],
|
|
73
|
+
},
|
|
74
|
+
bases=("oel_tagging.modelsystemdefinedtaxonomy",),
|
|
75
|
+
),
|
|
76
|
+
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Generated by Django 3.2.19 on 2023-07-24 06:25
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
import openedx_learning.lib.fields
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
dependencies = [
|
|
9
|
+
("oel_tagging", "0003_auto_20230721_1238"),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AlterField(
|
|
14
|
+
model_name="objecttag",
|
|
15
|
+
name="object_id",
|
|
16
|
+
field=openedx_learning.lib.fields.MultiCollationCharField(
|
|
17
|
+
db_collations={"mysql": "utf8mb4_unicode_ci", "sqlite": "NOCASE"},
|
|
18
|
+
db_index=True,
|
|
19
|
+
editable=False,
|
|
20
|
+
help_text="Identifier for the object being tagged",
|
|
21
|
+
max_length=255,
|
|
22
|
+
),
|
|
23
|
+
),
|
|
24
|
+
migrations.AlterUniqueTogether(
|
|
25
|
+
name="tag",
|
|
26
|
+
unique_together={("taxonomy", "external_id"), ("taxonomy", "value")},
|
|
27
|
+
),
|
|
28
|
+
migrations.AddIndex(
|
|
29
|
+
model_name="objecttag",
|
|
30
|
+
index=models.Index(
|
|
31
|
+
fields=["taxonomy", "object_id"], name="oel_tagging_taxonom_aa24e6_idx"
|
|
32
|
+
),
|
|
33
|
+
),
|
|
34
|
+
]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Generated by Django 3.2.19 on 2023-07-28 13:33
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
from django.core.management import call_command
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def load_language_taxonomy(apps, schema_editor):
|
|
8
|
+
"""
|
|
9
|
+
Load language taxonomy and tags
|
|
10
|
+
"""
|
|
11
|
+
call_command("loaddata", "--app=oel_tagging", "language_taxonomy.yaml")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def revert(apps, schema_editor):
|
|
15
|
+
"""
|
|
16
|
+
Deletes language taxonomy an tags
|
|
17
|
+
"""
|
|
18
|
+
Taxonomy = apps.get_model("oel_tagging", "Taxonomy")
|
|
19
|
+
Taxonomy.objects.filter(id=-1).delete()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Migration(migrations.Migration):
|
|
23
|
+
dependencies = [
|
|
24
|
+
("oel_tagging", "0004_auto_20230723_2001"),
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
operations = [
|
|
28
|
+
migrations.RunPython(load_language_taxonomy, revert),
|
|
29
|
+
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Generated by Django 3.2.19 on 2023-08-02 16:20
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
dependencies = [
|
|
8
|
+
("oel_tagging", "0005_language_taxonomy"),
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
operations = [
|
|
12
|
+
migrations.AlterUniqueTogether(
|
|
13
|
+
name="objecttag",
|
|
14
|
+
unique_together={("taxonomy", "_value", "object_id")},
|
|
15
|
+
),
|
|
16
|
+
]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
""" Tagging app data models """
|
|
1
|
+
""" Tagging app base data models """
|
|
2
2
|
import logging
|
|
3
3
|
from typing import List, Type, Union
|
|
4
4
|
|
|
@@ -6,7 +6,10 @@ from django.db import models
|
|
|
6
6
|
from django.utils.module_loading import import_string
|
|
7
7
|
from django.utils.translation import gettext_lazy as _
|
|
8
8
|
|
|
9
|
-
from openedx_learning.lib.fields import
|
|
9
|
+
from openedx_learning.lib.fields import (
|
|
10
|
+
MultiCollationTextField,
|
|
11
|
+
case_insensitive_char_field,
|
|
12
|
+
)
|
|
10
13
|
|
|
11
14
|
log = logging.getLogger(__name__)
|
|
12
15
|
|
|
@@ -66,6 +69,10 @@ class Tag(models.Model):
|
|
|
66
69
|
models.Index(fields=["taxonomy", "value"]),
|
|
67
70
|
models.Index(fields=["taxonomy", "external_id"]),
|
|
68
71
|
]
|
|
72
|
+
unique_together = [
|
|
73
|
+
["taxonomy", "external_id"],
|
|
74
|
+
["taxonomy", "value"],
|
|
75
|
+
]
|
|
69
76
|
|
|
70
77
|
def __repr__(self):
|
|
71
78
|
"""
|
|
@@ -140,14 +147,6 @@ class Taxonomy(models.Model):
|
|
|
140
147
|
"Indicates that tags in this taxonomy need not be predefined; authors may enter their own tag values."
|
|
141
148
|
),
|
|
142
149
|
)
|
|
143
|
-
system_defined = models.BooleanField(
|
|
144
|
-
default=False,
|
|
145
|
-
editable=False,
|
|
146
|
-
help_text=_(
|
|
147
|
-
"Indicates that tags and metadata for this taxonomy are maintained by the system;"
|
|
148
|
-
" taxonomy admins will not be permitted to modify them.",
|
|
149
|
-
),
|
|
150
|
-
)
|
|
151
150
|
visible_to_authors = models.BooleanField(
|
|
152
151
|
default=True,
|
|
153
152
|
editable=False,
|
|
@@ -177,8 +176,25 @@ class Taxonomy(models.Model):
|
|
|
177
176
|
"""
|
|
178
177
|
User-facing string representation of a Taxonomy.
|
|
179
178
|
"""
|
|
179
|
+
try:
|
|
180
|
+
if self._taxonomy_class:
|
|
181
|
+
return f"<{self.taxonomy_class.__name__}> ({self.id}) {self.name}"
|
|
182
|
+
except ImportError:
|
|
183
|
+
# Log error and continue
|
|
184
|
+
log.exception(
|
|
185
|
+
f"Unable to import taxonomy_class for {self.id}: {self._taxonomy_class}"
|
|
186
|
+
)
|
|
180
187
|
return f"<{self.__class__.__name__}> ({self.id}) {self.name}"
|
|
181
188
|
|
|
189
|
+
@property
|
|
190
|
+
def object_tag_class(self) -> Type:
|
|
191
|
+
"""
|
|
192
|
+
Returns the ObjectTag subclass associated with this taxonomy, which is ObjectTag by default.
|
|
193
|
+
|
|
194
|
+
Taxonomy subclasses may override this method to use different subclasses of ObjectTag.
|
|
195
|
+
"""
|
|
196
|
+
return ObjectTag
|
|
197
|
+
|
|
182
198
|
@property
|
|
183
199
|
def taxonomy_class(self) -> Type:
|
|
184
200
|
"""
|
|
@@ -190,6 +206,14 @@ class Taxonomy(models.Model):
|
|
|
190
206
|
return import_string(self._taxonomy_class)
|
|
191
207
|
return None
|
|
192
208
|
|
|
209
|
+
@property
|
|
210
|
+
def system_defined(self) -> bool:
|
|
211
|
+
"""
|
|
212
|
+
Indicates that tags and metadata for this taxonomy are maintained by the system;
|
|
213
|
+
taxonomy admins will not be permitted to modify them.
|
|
214
|
+
"""
|
|
215
|
+
return False
|
|
216
|
+
|
|
193
217
|
@taxonomy_class.setter
|
|
194
218
|
def taxonomy_class(self, taxonomy_class: Union[Type, None]):
|
|
195
219
|
"""
|
|
@@ -239,15 +263,16 @@ class Taxonomy(models.Model):
|
|
|
239
263
|
self.required = taxonomy.required
|
|
240
264
|
self.allow_multiple = taxonomy.allow_multiple
|
|
241
265
|
self.allow_free_text = taxonomy.allow_free_text
|
|
242
|
-
self.system_defined = taxonomy.system_defined
|
|
243
266
|
self.visible_to_authors = taxonomy.visible_to_authors
|
|
244
267
|
self._taxonomy_class = taxonomy._taxonomy_class
|
|
245
268
|
return self
|
|
246
269
|
|
|
247
|
-
def get_tags(self) -> List[Tag]:
|
|
270
|
+
def get_tags(self, tag_set: models.QuerySet = None) -> List[Tag]:
|
|
248
271
|
"""
|
|
249
272
|
Returns a list of all Tags in the current taxonomy, from the root(s) down to TAXONOMY_MAX_DEPTH tags, in tree order.
|
|
250
273
|
|
|
274
|
+
Use `tag_set` to do an initial filtering of the tags.
|
|
275
|
+
|
|
251
276
|
Annotates each returned Tag with its ``depth`` in the tree (starting at 0).
|
|
252
277
|
|
|
253
278
|
Performance note: may perform as many as TAXONOMY_MAX_DEPTH select queries.
|
|
@@ -256,9 +281,12 @@ class Taxonomy(models.Model):
|
|
|
256
281
|
if self.allow_free_text:
|
|
257
282
|
return tags
|
|
258
283
|
|
|
284
|
+
if tag_set is None:
|
|
285
|
+
tag_set = self.tag_set
|
|
286
|
+
|
|
259
287
|
parents = None
|
|
260
288
|
for depth in range(TAXONOMY_MAX_DEPTH):
|
|
261
|
-
filtered_tags =
|
|
289
|
+
filtered_tags = tag_set.prefetch_related("parent")
|
|
262
290
|
if parents is None:
|
|
263
291
|
filtered_tags = filtered_tags.filter(parent=None)
|
|
264
292
|
else:
|
|
@@ -366,9 +394,10 @@ class Taxonomy(models.Model):
|
|
|
366
394
|
_(f"Taxonomy ({self.id}) requires at least one tag per object.")
|
|
367
395
|
)
|
|
368
396
|
|
|
397
|
+
ObjectTagClass = self.object_tag_class
|
|
369
398
|
current_tags = {
|
|
370
399
|
tag.tag_ref: tag
|
|
371
|
-
for tag in
|
|
400
|
+
for tag in ObjectTagClass.objects.filter(
|
|
372
401
|
taxonomy=self,
|
|
373
402
|
object_id=object_id,
|
|
374
403
|
)
|
|
@@ -378,20 +407,12 @@ class Taxonomy(models.Model):
|
|
|
378
407
|
if tag_ref in current_tags:
|
|
379
408
|
object_tag = current_tags.pop(tag_ref)
|
|
380
409
|
else:
|
|
381
|
-
object_tag =
|
|
410
|
+
object_tag = ObjectTagClass(
|
|
382
411
|
taxonomy=self,
|
|
383
412
|
object_id=object_id,
|
|
384
413
|
)
|
|
385
414
|
|
|
386
|
-
|
|
387
|
-
object_tag.tag = self.tag_set.get(
|
|
388
|
-
id=tag_ref,
|
|
389
|
-
)
|
|
390
|
-
except (ValueError, Tag.DoesNotExist):
|
|
391
|
-
# This might be ok, e.g. if self.allow_free_text.
|
|
392
|
-
# We'll validate below before saving.
|
|
393
|
-
object_tag.value = tag_ref
|
|
394
|
-
|
|
415
|
+
object_tag.tag_ref = tag_ref
|
|
395
416
|
object_tag.resync()
|
|
396
417
|
if not self.validate_object_tag(object_tag):
|
|
397
418
|
raise ValueError(
|
|
@@ -409,6 +430,52 @@ class Taxonomy(models.Model):
|
|
|
409
430
|
|
|
410
431
|
return updated_tags
|
|
411
432
|
|
|
433
|
+
def autocomplete_tags(
|
|
434
|
+
self,
|
|
435
|
+
search: str,
|
|
436
|
+
object_id: str = None,
|
|
437
|
+
) -> models.QuerySet:
|
|
438
|
+
"""
|
|
439
|
+
Provides auto-complete suggestions by matching the `search` string against existing
|
|
440
|
+
ObjectTags linked to the given taxonomy. A case-insensitive search is used in order
|
|
441
|
+
to return the highest number of relevant tags.
|
|
442
|
+
|
|
443
|
+
If `object_id` is provided, then object tag values already linked to this object
|
|
444
|
+
are omitted from the returned suggestions. (ObjectTag values must be unique for a
|
|
445
|
+
given object + taxonomy, and so omitting these suggestions helps users avoid
|
|
446
|
+
duplication errors.).
|
|
447
|
+
|
|
448
|
+
Returns a QuerySet of dictionaries containing distinct `value` (string) and `tag`
|
|
449
|
+
(numeric ID) values, sorted alphabetically by `value`.
|
|
450
|
+
|
|
451
|
+
Subclasses can override this method to perform their own autocomplete process.
|
|
452
|
+
Subclass use cases:
|
|
453
|
+
* Large taxonomy associated with a model. It can be overridden to get
|
|
454
|
+
the suggestions directly from the model by doing own filtering.
|
|
455
|
+
* Taxonomy with a list of available tags: It can be overridden to only
|
|
456
|
+
search the suggestions on a list of available tags.
|
|
457
|
+
"""
|
|
458
|
+
# Fetch tags that the object already has to exclude them from the result
|
|
459
|
+
excluded_tags = []
|
|
460
|
+
if object_id:
|
|
461
|
+
excluded_tags = self.objecttag_set.filter(object_id=object_id).values_list(
|
|
462
|
+
"_value", flat=True
|
|
463
|
+
)
|
|
464
|
+
return (
|
|
465
|
+
# Fetch object tags from this taxonomy whose value contains the search
|
|
466
|
+
self.objecttag_set.filter(_value__icontains=search)
|
|
467
|
+
# omit any tags whose values match the tags on the given object
|
|
468
|
+
.exclude(_value__in=excluded_tags)
|
|
469
|
+
# alphabetical ordering
|
|
470
|
+
.order_by("_value")
|
|
471
|
+
# Alias the `_value` field to `value` to make it nicer for users
|
|
472
|
+
.annotate(value=models.F("_value"))
|
|
473
|
+
# obtain tag values
|
|
474
|
+
.values("value", "tag_id")
|
|
475
|
+
# remove repeats
|
|
476
|
+
.distinct()
|
|
477
|
+
)
|
|
478
|
+
|
|
412
479
|
|
|
413
480
|
class ObjectTag(models.Model):
|
|
414
481
|
"""
|
|
@@ -431,6 +498,7 @@ class ObjectTag(models.Model):
|
|
|
431
498
|
id = models.BigAutoField(primary_key=True)
|
|
432
499
|
object_id = case_insensitive_char_field(
|
|
433
500
|
max_length=255,
|
|
501
|
+
db_index=True,
|
|
434
502
|
editable=False,
|
|
435
503
|
help_text=_("Identifier for the object being tagged"),
|
|
436
504
|
)
|
|
@@ -472,8 +540,10 @@ class ObjectTag(models.Model):
|
|
|
472
540
|
|
|
473
541
|
class Meta:
|
|
474
542
|
indexes = [
|
|
543
|
+
models.Index(fields=["taxonomy", "object_id"]),
|
|
475
544
|
models.Index(fields=["taxonomy", "_value"]),
|
|
476
545
|
]
|
|
546
|
+
unique_together = ("taxonomy", "_value", "object_id")
|
|
477
547
|
|
|
478
548
|
def __repr__(self):
|
|
479
549
|
"""
|
|
@@ -531,6 +601,24 @@ class ObjectTag(models.Model):
|
|
|
531
601
|
"""
|
|
532
602
|
return self.tag.id if self.tag_id else self._value
|
|
533
603
|
|
|
604
|
+
@tag_ref.setter
|
|
605
|
+
def tag_ref(self, tag_ref: str):
|
|
606
|
+
"""
|
|
607
|
+
Sets the ObjectTag's Tag and/or value, depending on whether a valid Tag is found.
|
|
608
|
+
|
|
609
|
+
Subclasses may override this method to dynamically create Tags.
|
|
610
|
+
"""
|
|
611
|
+
self.value = tag_ref
|
|
612
|
+
|
|
613
|
+
if self.taxonomy_id:
|
|
614
|
+
try:
|
|
615
|
+
self.tag = self.taxonomy.tag_set.get(pk=tag_ref)
|
|
616
|
+
self.value = self.tag.value
|
|
617
|
+
except (ValueError, Tag.DoesNotExist):
|
|
618
|
+
# This might be ok, e.g. if our taxonomy.allow_free_text, so we just pass through here.
|
|
619
|
+
# We rely on the caller to validate before saving.
|
|
620
|
+
pass
|
|
621
|
+
|
|
534
622
|
def is_valid(self) -> bool:
|
|
535
623
|
"""
|
|
536
624
|
Returns True if this ObjectTag represents a valid taxonomy tag.
|