openedx-learning 0.1.1__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.
@@ -1 +1 @@
1
- __version__ = "0.1.1"
1
+ __version__ = "0.1.2"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: openedx-learning
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: An experiment.
5
5
  Home-page: https://github.com/openedx/openedx-learning
6
6
  Author: David Ormsbee
@@ -18,8 +18,9 @@ Classifier: Programming Language :: Python :: 3
18
18
  Classifier: Programming Language :: Python :: 3.8
19
19
  Requires-Python: >=3.8
20
20
  Requires-Dist: djangorestframework (<4.0)
21
- Requires-Dist: Django (<5.0)
21
+ Requires-Dist: edx-drf-extensions
22
22
  Requires-Dist: rules (<4.0)
23
+ Requires-Dist: Django (<5.0)
23
24
 
24
25
  openedx-learning
25
26
  =============================
@@ -1,4 +1,4 @@
1
- openedx_learning/__init__.py,sha256=rnObPjuBcEStqSO0S6gsdS_ot8ITOQjVj_-P1LUUYpg,22
1
+ openedx_learning/__init__.py,sha256=YvuYzWnKtqBb-IqG8HAu-nhIYAsgj9Vmc_b9o7vO-js,22
2
2
  openedx_learning/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  openedx_learning/contrib/media_server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  openedx_learning/contrib/media_server/apps.py,sha256=DWPy77j5hdv_tk8Y9MsoscgwRBxplO-sBVJT5grhHPA,754
@@ -41,7 +41,7 @@ openedx_learning/rest_api/v1/urls.py,sha256=lY-i3VzANvtdcJHjwygMtqEhfyMOjgIfaWpn
41
41
  openedx_tagging/__init__.py,sha256=N_pklgjdCZO62lWysvHk0w9RnDRXCBVhok0GheQrFaE,28
42
42
  openedx_tagging/core/tagging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
43
  openedx_tagging/core/tagging/admin.py,sha256=efVW_mA5MEey14VJdazacyxnBgbYPPfJQVS7XLKaWEg,192
44
- openedx_tagging/core/tagging/api.py,sha256=IL5ACbyaOKMaYsOz1s_1JScpTfqCTDbOEfosuWa0OAM,4393
44
+ openedx_tagging/core/tagging/api.py,sha256=ilRZVGWPq2AtQkF6n_jeiwOPIKEDvTbkYrneUiuEeXw,6602
45
45
  openedx_tagging/core/tagging/apps.py,sha256=-gp0VYqX4XQzwjjd-G68Ev2Op0INLh9Byz5UOqF5_7k,345
46
46
  openedx_tagging/core/tagging/rules.py,sha256=_a5waGxbHmAvSeFG6SefRCF4hpVdCw1hlJuyd440X_k,2688
47
47
  openedx_tagging/core/tagging/urls.py,sha256=XOZ0CNp7w1Z4zcTZ9orX-uEfqogCRYTpLhVkNG8yfw8,159
@@ -53,18 +53,19 @@ openedx_tagging/core/tagging/migrations/0002_auto_20230718_2026.py,sha256=UG_q6U
53
53
  openedx_tagging/core/tagging/migrations/0003_auto_20230721_1238.py,sha256=c8q2apdZPNcr9U4VstFaLSO5z1k0ZlzL87qObobN7Mw,2109
54
54
  openedx_tagging/core/tagging/migrations/0004_auto_20230723_2001.py,sha256=Onnc6T12BxOQX5QTFUC9Qc5leiR7014qT2jVE7N8MBU,1080
55
55
  openedx_tagging/core/tagging/migrations/0005_language_taxonomy.py,sha256=WaWh4JAnbPCqPo2uFRWHPZfUC1wa9zgGuLU8Qgri68U,708
56
+ openedx_tagging/core/tagging/migrations/0006_alter_objecttag_unique_together.py,sha256=Jx7B8ZQ5u8sZksKEkpZFrcaQpBXkaJVBtTXa-k3c4xY,376
56
57
  openedx_tagging/core/tagging/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
58
  openedx_tagging/core/tagging/models/__init__.py,sha256=2THzqS9MSZvFiKTh_tGTjzXPw0GRoP7BleQMvhEJkqw,197
58
- openedx_tagging/core/tagging/models/base.py,sha256=XCUT9jtbK87UFqY-wNDt3vaxXr30P7wNgZt0vMIHwEg,22815
59
+ openedx_tagging/core/tagging/models/base.py,sha256=OYNMHqB2UHhgoAFv6E66KqtzSolsoguA337hQxMmZVU,25000
59
60
  openedx_tagging/core/tagging/models/system_defined.py,sha256=o-x-ckdbiGSjLWQu0DJJsm5nETCuW4i9pPOPo-yqfxY,8422
60
61
  openedx_tagging/core/tagging/rest_api/urls.py,sha256=HWK1uWlslaBeqcPdlgvRjcSk0_lm48Sq3cR3TPvTiKk,148
61
62
  openedx_tagging/core/tagging/rest_api/v1/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
62
63
  openedx_tagging/core/tagging/rest_api/v1/permissions.py,sha256=xfiSuGzJ6b01XWzdPrbZWlJAAC8zjaEDhV6W7y_pq0Y,536
63
64
  openedx_tagging/core/tagging/rest_api/v1/serializers.py,sha256=NX9AAVVTT9EChTIshLvitwej-TO1UJEt6VB5vgTR2y0,693
64
65
  openedx_tagging/core/tagging/rest_api/v1/urls.py,sha256=Fc2nN4yFLZeKFOPPMYU_bXxNxd3Cw2gGXxiIJd5rV0c,292
65
- openedx_tagging/core/tagging/rest_api/v1/views.py,sha256=lVFG04C5uonyGpjJnuoLqeo54jYSWXM-PiOMnW3hvFc,5757
66
- openedx_learning-0.1.1.dist-info/LICENSE.txt,sha256=QTW2QN7q3XszgUAXm9Dzgtu5LXYKbR1SGnqMa7ufEuY,35139
67
- openedx_learning-0.1.1.dist-info/METADATA,sha256=IPVuHfTujw9uO1-9M0TZrEAXFujuiqhBRhx_pyb7AA4,8606
68
- openedx_learning-0.1.1.dist-info/WHEEL,sha256=Z-nyYpwrcSqxfdux5Mbn_DQ525iP7J2DG3JgGvOYyTQ,110
69
- openedx_learning-0.1.1.dist-info/top_level.txt,sha256=IYFbr5mgiEHd-LOtZmXj3q3a0bkGK1M9LY7GXgnfi4M,33
70
- openedx_learning-0.1.1.dist-info/RECORD,,
66
+ openedx_tagging/core/tagging/rest_api/v1/views.py,sha256=uUl_aNEGVTu1vj36HWpZjQfw154Qq-iEGNpQ-0koZVs,5881
67
+ openedx_learning-0.1.2.dist-info/LICENSE.txt,sha256=QTW2QN7q3XszgUAXm9Dzgtu5LXYKbR1SGnqMa7ufEuY,35139
68
+ openedx_learning-0.1.2.dist-info/METADATA,sha256=xgX75erxBaiHLfBiefAd-j2VPJiws5LdA9yBt8OlfKI,8640
69
+ openedx_learning-0.1.2.dist-info/WHEEL,sha256=Z-nyYpwrcSqxfdux5Mbn_DQ525iP7J2DG3JgGvOYyTQ,110
70
+ openedx_learning-0.1.2.dist-info/top_level.txt,sha256=IYFbr5mgiEHd-LOtZmXj3q3a0bkGK1M9LY7GXgnfi4M,33
71
+ openedx_learning-0.1.2.dist-info/RECORD,,
@@ -137,3 +137,46 @@ def tag_object(
137
137
  Preserves existing (valid) tags, adds new (valid) tags, and removes omitted (or invalid) tags.
138
138
  """
139
139
  return taxonomy.cast().tag_object(tags, object_id)
140
+
141
+
142
+ def autocomplete_tags(
143
+ taxonomy: Taxonomy,
144
+ search: str,
145
+ object_id: str = None,
146
+ object_tags_only=True,
147
+ ) -> QuerySet:
148
+ """
149
+ Provides auto-complete suggestions by matching the `search` string against existing
150
+ ObjectTags linked to the given taxonomy. A case-insensitive search is used in order
151
+ to return the highest number of relevant tags.
152
+
153
+ If `object_id` is provided, then object tag values already linked to this object
154
+ are omitted from the returned suggestions. (ObjectTag values must be unique for a
155
+ given object + taxonomy, and so omitting these suggestions helps users avoid
156
+ duplication errors.).
157
+
158
+ Returns a QuerySet of dictionaries containing distinct `value` (string) and
159
+ `tag` (numeric ID) values, sorted alphabetically by `value`.
160
+ The `value` is what should be shown as a suggestion to users,
161
+ and if it's a free-text taxonomy, `tag` will be `None`: we include the `tag` ID
162
+ in anticipation of the second use case listed below.
163
+
164
+ Use cases:
165
+ * This method is useful for reducing tag variation in free-text taxonomies by showing
166
+ users tags that are similar to what they're typing. E.g., if the `search` string "dn"
167
+ shows that other objects have been tagged with "DNA", "DNA electrophoresis", and "DNA fingerprinting",
168
+ this encourages users to use those existing tags if relevant, instead of creating new ones that
169
+ look similar (e.g. "dna finger-printing").
170
+ * It could also be used to assist tagging for closed taxonomies with a list of possible tags which is too
171
+ large to return all at once, e.g. a user model taxonomy that dynamically creates tags on request for any
172
+ registered user in the database. (Note that this is not implemented yet, but may be as part of a future change.)
173
+ """
174
+ if not object_tags_only:
175
+ raise NotImplementedError(
176
+ _(
177
+ "Using this would return a query set of tags instead of object tags."
178
+ "For now we recommend fetching all of the taxonomy's tags "
179
+ "using get_tags() and filtering them on the frontend."
180
+ )
181
+ )
182
+ return taxonomy.cast().autocomplete_tags(search, object_id)
@@ -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
+ ]
@@ -430,6 +430,52 @@ class Taxonomy(models.Model):
430
430
 
431
431
  return updated_tags
432
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
+
433
479
 
434
480
  class ObjectTag(models.Model):
435
481
  """
@@ -497,6 +543,7 @@ class ObjectTag(models.Model):
497
543
  models.Index(fields=["taxonomy", "object_id"]),
498
544
  models.Index(fields=["taxonomy", "_value"]),
499
545
  ]
546
+ unique_together = ("taxonomy", "_value", "object_id")
500
547
 
501
548
  def __repr__(self):
502
549
  """
@@ -9,8 +9,8 @@ from ...api import (
9
9
  get_taxonomy,
10
10
  get_taxonomies,
11
11
  )
12
- from .serializers import TaxonomyListQueryParamsSerializer, TaxonomySerializer
13
12
  from .permissions import TaxonomyObjectPermissions
13
+ from .serializers import TaxonomyListQueryParamsSerializer, TaxonomySerializer
14
14
 
15
15
 
16
16
  class TaxonomyView(ModelViewSet):
@@ -19,6 +19,8 @@ class TaxonomyView(ModelViewSet):
19
19
 
20
20
  **List Query Parameters**
21
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)
22
24
 
23
25
  **List Example Requests**
24
26
  GET api/tagging/v1/taxonomy - Get all taxonomies
@@ -59,7 +61,6 @@ class TaxonomyView(ModelViewSet):
59
61
  "allow_free_text": True,
60
62
  }
61
63
 
62
-
63
64
  **Create Query Returns**
64
65
  * 201 - Success
65
66
  * 403 - Permission denied