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
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
""" Tagging app system-defined taxonomies data models """
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Any, List, Type, Union
|
|
4
|
+
|
|
5
|
+
from django.conf import settings
|
|
6
|
+
from django.contrib.auth import get_user_model
|
|
7
|
+
from django.db import models
|
|
8
|
+
|
|
9
|
+
from openedx_tagging.core.tagging.models.base import ObjectTag
|
|
10
|
+
|
|
11
|
+
from .base import Tag, Taxonomy, ObjectTag
|
|
12
|
+
|
|
13
|
+
log = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SystemDefinedTaxonomy(Taxonomy):
|
|
17
|
+
"""
|
|
18
|
+
Simple subclass of Taxonomy which requires the system_defined flag to be set.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
class Meta:
|
|
22
|
+
proxy = True
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def system_defined(self) -> bool:
|
|
26
|
+
"""
|
|
27
|
+
Indicates that tags and metadata for this taxonomy are maintained by the system;
|
|
28
|
+
taxonomy admins will not be permitted to modify them.
|
|
29
|
+
"""
|
|
30
|
+
return True
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ModelObjectTag(ObjectTag):
|
|
34
|
+
"""
|
|
35
|
+
Model-based ObjectTag, abstract class.
|
|
36
|
+
|
|
37
|
+
Used by ModelSystemDefinedTaxonomy to maintain dynamic Tags which are associated with a configured Model instance.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
class Meta:
|
|
41
|
+
proxy = True
|
|
42
|
+
|
|
43
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
44
|
+
"""
|
|
45
|
+
Checks if the `tag_class_model` is correct
|
|
46
|
+
"""
|
|
47
|
+
assert issubclass(self.tag_class_model, models.Model)
|
|
48
|
+
super().__init__(*args, **kwargs)
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def tag_class_model(self) -> Type:
|
|
52
|
+
"""
|
|
53
|
+
Subclasses must implement this method to return the Django.model
|
|
54
|
+
class referenced by these object tags.
|
|
55
|
+
"""
|
|
56
|
+
raise NotImplementedError
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def tag_class_value(self) -> str:
|
|
60
|
+
"""
|
|
61
|
+
Returns the name of the tag_class_model field to use as the Tag.value when creating Tags for this taxonomy.
|
|
62
|
+
|
|
63
|
+
Subclasses may override this method to use different fields.
|
|
64
|
+
"""
|
|
65
|
+
return "pk"
|
|
66
|
+
|
|
67
|
+
def get_instance(self) -> Union[models.Model, None]:
|
|
68
|
+
"""
|
|
69
|
+
Returns the instance of tag_class_model associated with this object tag, or None if not found.
|
|
70
|
+
"""
|
|
71
|
+
instance_id = self.tag.external_id if self.tag else None
|
|
72
|
+
if instance_id:
|
|
73
|
+
try:
|
|
74
|
+
return self.tag_class_model.objects.get(pk=instance_id)
|
|
75
|
+
except ValueError as e:
|
|
76
|
+
log.exception(f"{self}: {str(e)}")
|
|
77
|
+
except self.tag_class_model.DoesNotExist:
|
|
78
|
+
log.exception(
|
|
79
|
+
f"{self}: {self.tag_class_model.__name__} pk={instance_id} does not exist."
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
def _resync_tag(self) -> bool:
|
|
85
|
+
"""
|
|
86
|
+
Resync our tag's value with the value from the instance.
|
|
87
|
+
|
|
88
|
+
If the instance associated with the tag no longer exists, we unset our tag, because it's no longer valid.
|
|
89
|
+
|
|
90
|
+
Returns True if the given tag was changed, False otherwise.
|
|
91
|
+
"""
|
|
92
|
+
instance = self.get_instance()
|
|
93
|
+
if instance:
|
|
94
|
+
value = getattr(instance, self.tag_class_value)
|
|
95
|
+
self.value = value
|
|
96
|
+
if self.tag and self.tag.value != value:
|
|
97
|
+
self.tag.value = value
|
|
98
|
+
self.tag.save()
|
|
99
|
+
return True
|
|
100
|
+
else:
|
|
101
|
+
self.tag = None
|
|
102
|
+
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def tag_ref(self) -> str:
|
|
107
|
+
return (self.tag.external_id or self.tag.id) if self.tag_id else self._value
|
|
108
|
+
|
|
109
|
+
@tag_ref.setter
|
|
110
|
+
def tag_ref(self, tag_ref: str):
|
|
111
|
+
"""
|
|
112
|
+
Sets the ObjectTag's Tag and/or value, depending on whether a valid Tag is found, or can be created.
|
|
113
|
+
|
|
114
|
+
Creates a Tag for the given tag_ref value, if one containing that external_id not already exist.
|
|
115
|
+
"""
|
|
116
|
+
self.value = tag_ref
|
|
117
|
+
|
|
118
|
+
if self.taxonomy_id:
|
|
119
|
+
try:
|
|
120
|
+
self.tag = self.taxonomy.tag_set.get(
|
|
121
|
+
external_id=tag_ref,
|
|
122
|
+
)
|
|
123
|
+
except (ValueError, Tag.DoesNotExist):
|
|
124
|
+
# Creates a new Tag for this instance
|
|
125
|
+
self.tag = Tag(
|
|
126
|
+
taxonomy=self.taxonomy,
|
|
127
|
+
external_id=tag_ref,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
self._resync_tag()
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class ModelSystemDefinedTaxonomy(SystemDefinedTaxonomy):
|
|
134
|
+
"""
|
|
135
|
+
Model based system taxonomy abstract class.
|
|
136
|
+
|
|
137
|
+
This type of taxonomy has an associated Django model in ModelObjectTag.tag_class_model().
|
|
138
|
+
They are designed to create Tags when required for new ObjectTags, to maintain
|
|
139
|
+
their status as "closed" taxonomies.
|
|
140
|
+
The Tags are representations of the instances of the associated model.
|
|
141
|
+
|
|
142
|
+
Tag.external_id stores an identifier from the instance (`pk` as default)
|
|
143
|
+
and Tag.value stores a human readable representation of the instance
|
|
144
|
+
(e.g. `username`).
|
|
145
|
+
The subclasses can override this behavior, to choose the right field.
|
|
146
|
+
|
|
147
|
+
When an ObjectTag is created with an existing Tag,
|
|
148
|
+
the Tag is re-synchronized with its instance.
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
class Meta:
|
|
152
|
+
proxy = True
|
|
153
|
+
|
|
154
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
155
|
+
"""
|
|
156
|
+
Checks if the `object_tag_class` is a subclass of ModelObjectTag.
|
|
157
|
+
"""
|
|
158
|
+
assert issubclass(self.object_tag_class, ModelObjectTag)
|
|
159
|
+
super().__init__(*args, **kwargs)
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
def object_tag_class(self) -> Type:
|
|
163
|
+
"""
|
|
164
|
+
Returns the ObjectTag subclass associated with this taxonomy.
|
|
165
|
+
|
|
166
|
+
Model Taxonomy subclasses must implement this to provide a ModelObjectTag subclass.
|
|
167
|
+
"""
|
|
168
|
+
raise NotImplementedError
|
|
169
|
+
|
|
170
|
+
def _check_instance(self, object_tag: ObjectTag) -> bool:
|
|
171
|
+
"""
|
|
172
|
+
Returns True if the instance exists
|
|
173
|
+
|
|
174
|
+
Subclasses can override this method to perform their own instance validation checks.
|
|
175
|
+
"""
|
|
176
|
+
object_tag = self.object_tag_class.cast(object_tag)
|
|
177
|
+
return bool(object_tag.get_instance())
|
|
178
|
+
|
|
179
|
+
def _check_tag(self, object_tag: ObjectTag) -> bool:
|
|
180
|
+
"""
|
|
181
|
+
Returns True if the instance is valid
|
|
182
|
+
"""
|
|
183
|
+
return super()._check_tag(object_tag) and self._check_instance(object_tag)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class UserModelObjectTag(ModelObjectTag):
|
|
187
|
+
"""
|
|
188
|
+
ObjectTags for the UserSystemDefinedTaxonomy.
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
class Meta:
|
|
192
|
+
proxy = True
|
|
193
|
+
|
|
194
|
+
@property
|
|
195
|
+
def tag_class_model(self) -> Type:
|
|
196
|
+
"""
|
|
197
|
+
Associate the user model
|
|
198
|
+
"""
|
|
199
|
+
return get_user_model()
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def tag_class_value(self) -> str:
|
|
203
|
+
"""
|
|
204
|
+
Returns the name of the tag_class_model field to use as the Tag.value when creating Tags for this taxonomy.
|
|
205
|
+
|
|
206
|
+
Subclasses may override this method to use different fields.
|
|
207
|
+
"""
|
|
208
|
+
return "username"
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class UserSystemDefinedTaxonomy(ModelSystemDefinedTaxonomy):
|
|
212
|
+
"""
|
|
213
|
+
User based system taxonomy class.
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
class Meta:
|
|
217
|
+
proxy = True
|
|
218
|
+
|
|
219
|
+
@property
|
|
220
|
+
def object_tag_class(self) -> Type:
|
|
221
|
+
"""
|
|
222
|
+
Returns the ObjectTag subclass associated with this taxonomy, which is ModelObjectTag by default.
|
|
223
|
+
|
|
224
|
+
Model Taxonomy subclasses must implement this to provide a ModelObjectTag subclass.
|
|
225
|
+
"""
|
|
226
|
+
return UserModelObjectTag
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class LanguageTaxonomy(SystemDefinedTaxonomy):
|
|
230
|
+
"""
|
|
231
|
+
Language System-defined taxonomy
|
|
232
|
+
|
|
233
|
+
The tags are filtered and validated taking into account the
|
|
234
|
+
languages available in Django LANGUAGES settings var
|
|
235
|
+
"""
|
|
236
|
+
|
|
237
|
+
class Meta:
|
|
238
|
+
proxy = True
|
|
239
|
+
|
|
240
|
+
def get_tags(self, tag_set: models.QuerySet = None) -> List[Tag]:
|
|
241
|
+
"""
|
|
242
|
+
Returns a list of all the available Language Tags, annotated with ``depth`` = 0.
|
|
243
|
+
"""
|
|
244
|
+
available_langs = self._get_available_languages()
|
|
245
|
+
tag_set = self.tag_set.filter(external_id__in=available_langs)
|
|
246
|
+
return super().get_tags(tag_set=tag_set)
|
|
247
|
+
|
|
248
|
+
def _get_available_languages(cls) -> List[str]:
|
|
249
|
+
"""
|
|
250
|
+
Get available languages from Django LANGUAGE.
|
|
251
|
+
"""
|
|
252
|
+
langs = set()
|
|
253
|
+
for django_lang in settings.LANGUAGES:
|
|
254
|
+
# Split to get the language part
|
|
255
|
+
langs.add(django_lang[0].split("-")[0])
|
|
256
|
+
return langs
|
|
257
|
+
|
|
258
|
+
def _check_valid_language(self, object_tag: ObjectTag) -> bool:
|
|
259
|
+
"""
|
|
260
|
+
Returns True if the tag is on the available languages
|
|
261
|
+
"""
|
|
262
|
+
available_langs = self._get_available_languages()
|
|
263
|
+
return object_tag.tag.external_id in available_langs
|
|
264
|
+
|
|
265
|
+
def _check_tag(self, object_tag: ObjectTag) -> bool:
|
|
266
|
+
"""
|
|
267
|
+
Returns True if the tag is on the available languages
|
|
268
|
+
"""
|
|
269
|
+
return super()._check_tag(object_tag) and self._check_valid_language(object_tag)
|
|
File without changes
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Taxonomy permissions
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from rest_framework.permissions import DjangoObjectPermissions
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TaxonomyObjectPermissions(DjangoObjectPermissions):
|
|
9
|
+
perms_map = {
|
|
10
|
+
"GET": ["%(app_label)s.view_%(model_name)s"],
|
|
11
|
+
"OPTIONS": [],
|
|
12
|
+
"HEAD": ["%(app_label)s.view_%(model_name)s"],
|
|
13
|
+
"POST": ["%(app_label)s.add_%(model_name)s"],
|
|
14
|
+
"PUT": ["%(app_label)s.change_%(model_name)s"],
|
|
15
|
+
"PATCH": ["%(app_label)s.change_%(model_name)s"],
|
|
16
|
+
"DELETE": ["%(app_label)s.delete_%(model_name)s"],
|
|
17
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
API Serializers for taxonomies
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from rest_framework import serializers
|
|
6
|
+
|
|
7
|
+
from openedx_tagging.core.tagging.models import Taxonomy
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TaxonomyListQueryParamsSerializer(serializers.Serializer):
|
|
11
|
+
"""
|
|
12
|
+
Serializer for the query params for the GET view
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
enabled = serializers.BooleanField(required=False)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TaxonomySerializer(serializers.ModelSerializer):
|
|
19
|
+
class Meta:
|
|
20
|
+
model = Taxonomy
|
|
21
|
+
fields = [
|
|
22
|
+
"id",
|
|
23
|
+
"name",
|
|
24
|
+
"description",
|
|
25
|
+
"enabled",
|
|
26
|
+
"required",
|
|
27
|
+
"allow_multiple",
|
|
28
|
+
"allow_free_text",
|
|
29
|
+
"system_defined",
|
|
30
|
+
"visible_to_authors",
|
|
31
|
+
]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Taxonomies API v1 URLs.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from rest_framework.routers import DefaultRouter
|
|
6
|
+
|
|
7
|
+
from django.urls.conf import path, include
|
|
8
|
+
|
|
9
|
+
from . import views
|
|
10
|
+
|
|
11
|
+
router = DefaultRouter()
|
|
12
|
+
router.register("taxonomies", views.TaxonomyView, basename="taxonomy")
|
|
13
|
+
|
|
14
|
+
urlpatterns = [path("", include(router.urls))]
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tagging API Views
|
|
3
|
+
"""
|
|
4
|
+
from django.http import Http404
|
|
5
|
+
from rest_framework.viewsets import ModelViewSet
|
|
6
|
+
|
|
7
|
+
from ...api import (
|
|
8
|
+
create_taxonomy,
|
|
9
|
+
get_taxonomy,
|
|
10
|
+
get_taxonomies,
|
|
11
|
+
)
|
|
12
|
+
from .permissions import TaxonomyObjectPermissions
|
|
13
|
+
from .serializers import TaxonomyListQueryParamsSerializer, TaxonomySerializer
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TaxonomyView(ModelViewSet):
|
|
17
|
+
"""
|
|
18
|
+
View to list, create, retrieve, update, or delete Taxonomies.
|
|
19
|
+
|
|
20
|
+
**List Query Parameters**
|
|
21
|
+
* enabled (optional) - Filter by enabled status. Valid values: true, false, 1, 0, "true", "false", "1"
|
|
22
|
+
* page (optional) - Page number (default: 1)
|
|
23
|
+
* page_size (optional) - Number of items per page (default: 10)
|
|
24
|
+
|
|
25
|
+
**List Example Requests**
|
|
26
|
+
GET api/tagging/v1/taxonomy - Get all taxonomies
|
|
27
|
+
GET api/tagging/v1/taxonomy?enabled=true - Get all enabled taxonomies
|
|
28
|
+
GET api/tagging/v1/taxonomy?enabled=false - Get all disabled taxonomies
|
|
29
|
+
|
|
30
|
+
**List Query Returns**
|
|
31
|
+
* 200 - Success
|
|
32
|
+
* 400 - Invalid query parameter
|
|
33
|
+
* 403 - Permission denied
|
|
34
|
+
|
|
35
|
+
**Retrieve Parameters**
|
|
36
|
+
* pk (required): - The pk of the taxonomy to retrieve
|
|
37
|
+
|
|
38
|
+
**Retrieve Example Requests**
|
|
39
|
+
GET api/tagging/v1/taxonomy/:pk - Get a specific taxonomy
|
|
40
|
+
|
|
41
|
+
**Retrieve Query Returns**
|
|
42
|
+
* 200 - Success
|
|
43
|
+
* 404 - Taxonomy not found or User does not have permission to access the taxonomy
|
|
44
|
+
|
|
45
|
+
**Create Parameters**
|
|
46
|
+
* name (required): User-facing label used when applying tags from this taxonomy to Open edX objects.
|
|
47
|
+
* description (optional): Provides extra information for the user when applying tags from this taxonomy to an object.
|
|
48
|
+
* enabled (optional): Only enabled taxonomies will be shown to authors (default: true).
|
|
49
|
+
* required (optional): Indicates that one or more tags from this taxonomy must be added to an object (default: False).
|
|
50
|
+
* allow_multiple (optional): Indicates that multiple tags from this taxonomy may be added to an object (default: False).
|
|
51
|
+
* allow_free_text (optional): Indicates that tags in this taxonomy need not be predefined; authors may enter their own tag values (default: False).
|
|
52
|
+
|
|
53
|
+
**Create Example Requests**
|
|
54
|
+
POST api/tagging/v1/taxonomy - Create a taxonomy
|
|
55
|
+
{
|
|
56
|
+
"name": "Taxonomy Name", - User-facing label used when applying tags from this taxonomy to Open edX objects."
|
|
57
|
+
"description": "This is a description",
|
|
58
|
+
"enabled": True,
|
|
59
|
+
"required": True,
|
|
60
|
+
"allow_multiple": True,
|
|
61
|
+
"allow_free_text": True,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
**Create Query Returns**
|
|
65
|
+
* 201 - Success
|
|
66
|
+
* 403 - Permission denied
|
|
67
|
+
|
|
68
|
+
**Update Parameters**
|
|
69
|
+
* pk (required): - The pk of the taxonomy to update
|
|
70
|
+
|
|
71
|
+
**Update Request Body**
|
|
72
|
+
* name (optional): User-facing label used when applying tags from this taxonomy to Open edX objects.
|
|
73
|
+
* description (optional): Provides extra information for the user when applying tags from this taxonomy to an object.
|
|
74
|
+
* enabled (optional): Only enabled taxonomies will be shown to authors.
|
|
75
|
+
* required (optional): Indicates that one or more tags from this taxonomy must be added to an object.
|
|
76
|
+
* allow_multiple (optional): Indicates that multiple tags from this taxonomy may be added to an object.
|
|
77
|
+
* allow_free_text (optional): Indicates that tags in this taxonomy need not be predefined; authors may enter their own tag values.
|
|
78
|
+
|
|
79
|
+
**Update Example Requests**
|
|
80
|
+
PUT api/tagging/v1/taxonomy/:pk - Update a taxonomy
|
|
81
|
+
{
|
|
82
|
+
"name": "Taxonomy New Name",
|
|
83
|
+
"description": "This is a new description",
|
|
84
|
+
"enabled": False,
|
|
85
|
+
"required": False,
|
|
86
|
+
"allow_multiple": False,
|
|
87
|
+
"allow_free_text": True,
|
|
88
|
+
}
|
|
89
|
+
PATCH api/tagging/v1/taxonomy/:pk - Partially update a taxonomy
|
|
90
|
+
{
|
|
91
|
+
"name": "Taxonomy New Name",
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
**Update Query Returns**
|
|
95
|
+
* 200 - Success
|
|
96
|
+
* 403 - Permission denied
|
|
97
|
+
|
|
98
|
+
**Delete Parameters**
|
|
99
|
+
* pk (required): - The pk of the taxonomy to delete
|
|
100
|
+
|
|
101
|
+
**Delete Example Requests**
|
|
102
|
+
DELETE api/tagging/v1/taxonomy/:pk - Delete a taxonomy
|
|
103
|
+
|
|
104
|
+
**Delete Query Returns**
|
|
105
|
+
* 200 - Success
|
|
106
|
+
* 404 - Taxonomy not found
|
|
107
|
+
* 403 - Permission denied
|
|
108
|
+
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
serializer_class = TaxonomySerializer
|
|
112
|
+
permission_classes = [TaxonomyObjectPermissions]
|
|
113
|
+
|
|
114
|
+
def get_object(self):
|
|
115
|
+
"""
|
|
116
|
+
Return the requested taxonomy object, if the user has appropriate
|
|
117
|
+
permissions.
|
|
118
|
+
"""
|
|
119
|
+
pk = self.kwargs.get("pk")
|
|
120
|
+
taxonomy = get_taxonomy(pk)
|
|
121
|
+
if not taxonomy:
|
|
122
|
+
raise Http404("Taxonomy not found")
|
|
123
|
+
self.check_object_permissions(self.request, taxonomy)
|
|
124
|
+
|
|
125
|
+
return taxonomy
|
|
126
|
+
|
|
127
|
+
def get_queryset(self):
|
|
128
|
+
"""
|
|
129
|
+
Return a list of taxonomies.
|
|
130
|
+
|
|
131
|
+
Returns all taxonomies by default.
|
|
132
|
+
If you want the disabled taxonomies, pass enabled=False.
|
|
133
|
+
If you want the enabled taxonomies, pass enabled=True.
|
|
134
|
+
"""
|
|
135
|
+
query_params = TaxonomyListQueryParamsSerializer(
|
|
136
|
+
data=self.request.query_params.dict()
|
|
137
|
+
)
|
|
138
|
+
query_params.is_valid(raise_exception=True)
|
|
139
|
+
enabled = query_params.data.get("enabled", None)
|
|
140
|
+
|
|
141
|
+
return get_taxonomies(enabled)
|
|
142
|
+
|
|
143
|
+
def perform_create(self, serializer):
|
|
144
|
+
"""
|
|
145
|
+
Create a new taxonomy.
|
|
146
|
+
"""
|
|
147
|
+
serializer.instance = create_taxonomy(**serializer.validated_data)
|
|
@@ -16,10 +16,10 @@ is_taxonomy_admin = rules.is_staff
|
|
|
16
16
|
@rules.predicate
|
|
17
17
|
def can_view_taxonomy(user: User, taxonomy: Taxonomy = None) -> bool:
|
|
18
18
|
"""
|
|
19
|
-
Anyone can view an enabled taxonomy,
|
|
19
|
+
Anyone can view an enabled taxonomy or list all taxonomies,
|
|
20
20
|
but only taxonomy admins can view a disabled taxonomy.
|
|
21
21
|
"""
|
|
22
|
-
return
|
|
22
|
+
return not taxonomy or taxonomy.cast().enabled or is_taxonomy_admin(user)
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
@rules.predicate
|
|
@@ -28,24 +28,21 @@ def can_change_taxonomy(user: User, taxonomy: Taxonomy = None) -> bool:
|
|
|
28
28
|
Even taxonomy admins cannot change system taxonomies.
|
|
29
29
|
"""
|
|
30
30
|
return is_taxonomy_admin(user) and (
|
|
31
|
-
not taxonomy or
|
|
31
|
+
not taxonomy or (taxonomy and not taxonomy.cast().system_defined)
|
|
32
32
|
)
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
@rules.predicate
|
|
36
|
-
def
|
|
36
|
+
def can_change_tag(user: User, tag: Tag = None) -> bool:
|
|
37
37
|
"""
|
|
38
38
|
Even taxonomy admins cannot add tags to system taxonomies (their tags are system-defined), or free-text taxonomies
|
|
39
39
|
(these don't have predefined tags).
|
|
40
40
|
"""
|
|
41
|
+
taxonomy = tag.taxonomy.cast() if (tag and tag.taxonomy_id) else None
|
|
41
42
|
return is_taxonomy_admin(user) and (
|
|
42
43
|
not tag
|
|
43
|
-
or not
|
|
44
|
-
or (
|
|
45
|
-
tag.taxonomy
|
|
46
|
-
and not tag.taxonomy.allow_free_text
|
|
47
|
-
and not tag.taxonomy.system_defined
|
|
48
|
-
)
|
|
44
|
+
or not taxonomy
|
|
45
|
+
or (taxonomy and not taxonomy.allow_free_text and not taxonomy.system_defined)
|
|
49
46
|
)
|
|
50
47
|
|
|
51
48
|
|
|
@@ -54,10 +51,12 @@ def can_change_object_tag(user: User, object_tag: ObjectTag = None) -> bool:
|
|
|
54
51
|
"""
|
|
55
52
|
Taxonomy admins can create or modify object tags on enabled taxonomies.
|
|
56
53
|
"""
|
|
54
|
+
taxonomy = (
|
|
55
|
+
object_tag.taxonomy.cast() if (object_tag and object_tag.taxonomy_id) else None
|
|
56
|
+
)
|
|
57
|
+
object_tag = taxonomy.object_tag_class.cast(object_tag) if taxonomy else object_tag
|
|
57
58
|
return is_taxonomy_admin(user) and (
|
|
58
|
-
not object_tag
|
|
59
|
-
or not object_tag.taxonomy
|
|
60
|
-
or (object_tag.taxonomy and object_tag.taxonomy.enabled)
|
|
59
|
+
not object_tag or not taxonomy or (taxonomy and taxonomy.enabled)
|
|
61
60
|
)
|
|
62
61
|
|
|
63
62
|
|
|
@@ -68,8 +67,8 @@ rules.add_perm("oel_tagging.delete_taxonomy", can_change_taxonomy)
|
|
|
68
67
|
rules.add_perm("oel_tagging.view_taxonomy", can_view_taxonomy)
|
|
69
68
|
|
|
70
69
|
# Tag
|
|
71
|
-
rules.add_perm("oel_tagging.add_tag",
|
|
72
|
-
rules.add_perm("oel_tagging.change_tag",
|
|
70
|
+
rules.add_perm("oel_tagging.add_tag", can_change_tag)
|
|
71
|
+
rules.add_perm("oel_tagging.change_tag", can_change_tag)
|
|
73
72
|
rules.add_perm("oel_tagging.delete_tag", is_taxonomy_admin)
|
|
74
73
|
rules.add_perm("oel_tagging.view_tag", rules.always_allow)
|
|
75
74
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|