openedx-learning 0.15.0__py2.py3-none-any.whl → 0.16.0__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,4 +1,4 @@
1
1
  """
2
2
  Open edX Learning ("Learning Core").
3
3
  """
4
- __version__ = "0.15.0"
4
+ __version__ = "0.16.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: openedx-learning
3
- Version: 0.15.0
3
+ Version: 0.16.0
4
4
  Summary: Open edX Learning Core and Tagging.
5
5
  Home-page: https://github.com/openedx/openedx-learning
6
6
  Author: David Ormsbee
@@ -18,12 +18,12 @@ Classifier: Programming Language :: Python :: 3.11
18
18
  Classifier: Programming Language :: Python :: 3.12
19
19
  Requires-Python: >=3.11
20
20
  License-File: LICENSE.txt
21
- Requires-Dist: edx-drf-extensions
22
21
  Requires-Dist: celery
22
+ Requires-Dist: djangorestframework<4.0
23
23
  Requires-Dist: attrs
24
24
  Requires-Dist: Django<5.0
25
25
  Requires-Dist: rules<4.0
26
- Requires-Dist: djangorestframework<4.0
26
+ Requires-Dist: edx-drf-extensions
27
27
 
28
28
  Open edX Learning Core (and Tagging)
29
29
  ====================================
@@ -1,4 +1,4 @@
1
- openedx_learning/__init__.py,sha256=fJlzpA8ZDMYQSqZnlzD-fmJITs1x7c9Xb6Nk13d6cVE,68
1
+ openedx_learning/__init__.py,sha256=GHiA1alNbJesOg1QdBMkyg2GfyNlz1kwiYuSV7HUxcY,68
2
2
  openedx_learning/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  openedx_learning/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  openedx_learning/api/authoring.py,sha256=vbRpiQ2wOfN3oR2bbN0-bI2ra0QRtha9tVixKW1ENis,929
@@ -61,7 +61,7 @@ openedx_tagging/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
61
61
  openedx_tagging/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
62
62
  openedx_tagging/core/tagging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
63
63
  openedx_tagging/core/tagging/admin.py,sha256=Ngc2l9Mf6gkzmqu7aOwq-d0mgV8szx0GzSeuWFX7Kyg,1080
64
- openedx_tagging/core/tagging/api.py,sha256=FLGXU16GRdvYE6-A2uH5MYHkSXwu37TGiIm9XqsyUR8,18795
64
+ openedx_tagging/core/tagging/api.py,sha256=HsdCmr1xv54fk6ggPuib3xKOwx5yJFbD3ZL4Lk5-d1o,19815
65
65
  openedx_tagging/core/tagging/apps.py,sha256=-gp0VYqX4XQzwjjd-G68Ev2Op0INLh9Byz5UOqF5_7k,345
66
66
  openedx_tagging/core/tagging/data.py,sha256=421EvmDzdM7H523dBVQk4J0W_UwTT4U5syqPRXUYK4g,1353
67
67
  openedx_tagging/core/tagging/rules.py,sha256=Gzw2RCQxoAv2PpOwOWgpD17XoZfowlFnNgQqYn59q_g,6715
@@ -94,9 +94,10 @@ openedx_tagging/core/tagging/migrations/0014_minor_fixes.py,sha256=46_F-el1UylSR
94
94
  openedx_tagging/core/tagging/migrations/0015_taxonomy_export_id.py,sha256=jhS-T8o2mwu61E7hPbjjE_6MPLKRPQFAVu7pJHZNRz4,1454
95
95
  openedx_tagging/core/tagging/migrations/0016_object_tag_export_id.py,sha256=X62sThGNv_0_wCBhWPcYRcs90EJwz9km9Nszr9yMVkM,2371
96
96
  openedx_tagging/core/tagging/migrations/0017_alter_tagimporttask_status.py,sha256=ljxONZOAGRW-tvPUIFmc0cNXN4Hoo3GJiyzGMncN6LI,567
97
+ openedx_tagging/core/tagging/migrations/0018_objecttag_is_copied.py,sha256=zmr4b65T0vX6fYc8MpvSmQnYkAiNMpx3RKEd5tudsl8,517
97
98
  openedx_tagging/core/tagging/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
98
99
  openedx_tagging/core/tagging/models/__init__.py,sha256=yYdOnthuc7EUdfEULtZgqRwn5Y4bbYQmJCjVZqR5GTM,236
99
- openedx_tagging/core/tagging/models/base.py,sha256=1WAPxssL8thAg8LHh1GbwSo98H81-nVdTVQt1nC1ZdU,39335
100
+ openedx_tagging/core/tagging/models/base.py,sha256=RX6rZcG7njrgLvM4huh79lC1vXzFDLxqElJqOU50Od8,39606
100
101
  openedx_tagging/core/tagging/models/import_export.py,sha256=Aj0pleh0nh2LNS6zmdB1P4bpdgUMmvmobTkqBerORAI,4570
101
102
  openedx_tagging/core/tagging/models/system_defined.py,sha256=_6LfvUZGEltvQMtm2OXy6TOLh3C8GnVTqtZDSAZW6K4,9062
102
103
  openedx_tagging/core/tagging/models/utils.py,sha256=-A3Dj24twmTf65UB7G4WLvb_9qEvduEPIwahZ-FJDlg,1926
@@ -106,12 +107,12 @@ openedx_tagging/core/tagging/rest_api/urls.py,sha256=egXaRQv1EAgF04ThgVZBQuvLK1L
106
107
  openedx_tagging/core/tagging/rest_api/utils.py,sha256=XZXixZ44vpNlxiyFplW8Lktyh_m1EfR3Y-tnyvA7acc,3620
107
108
  openedx_tagging/core/tagging/rest_api/v1/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
108
109
  openedx_tagging/core/tagging/rest_api/v1/permissions.py,sha256=7HPE_NuKku_ISnkeE_HsFNXVYt0IbVkJN6M4wqwHGHU,2443
109
- openedx_tagging/core/tagging/rest_api/v1/serializers.py,sha256=gbvEBLvsmfPc3swWz-_TEK8YpXiOa4oBXsU89_5iXiE,13749
110
+ openedx_tagging/core/tagging/rest_api/v1/serializers.py,sha256=D7brBbgmU7MnbU7Ln_xiLG-TtXno0aciE5AhjsXlClE,13903
110
111
  openedx_tagging/core/tagging/rest_api/v1/urls.py,sha256=dNUKCtUCx_YzrwlbEbpDfjGVQbb2QdJ1VuJCkladj6E,752
111
- openedx_tagging/core/tagging/rest_api/v1/views.py,sha256=ZRkSILdb8g5k_BcuuVVfdffEdY9vFQ_YtMa3JrN0Xz8,35581
112
+ openedx_tagging/core/tagging/rest_api/v1/views.py,sha256=LA0EF7-p91JgVrLZpZaG474elKD1dswODGvoPIw47Mg,35837
112
113
  openedx_tagging/core/tagging/rest_api/v1/views_import.py,sha256=kbHUPe5A6WaaJ3J1lFIcYCt876ecLNQfd19m7YYub6c,1470
113
- openedx_learning-0.15.0.dist-info/LICENSE.txt,sha256=QTW2QN7q3XszgUAXm9Dzgtu5LXYKbR1SGnqMa7ufEuY,35139
114
- openedx_learning-0.15.0.dist-info/METADATA,sha256=6cN09KcBqzdTVsxN7te-_yMVFRCPHldYO9rE81dtEWI,8777
115
- openedx_learning-0.15.0.dist-info/WHEEL,sha256=AHX6tWk3qWuce7vKLrj7lnulVHEdWoltgauo8bgCXgU,109
116
- openedx_learning-0.15.0.dist-info/top_level.txt,sha256=IYFbr5mgiEHd-LOtZmXj3q3a0bkGK1M9LY7GXgnfi4M,33
117
- openedx_learning-0.15.0.dist-info/RECORD,,
114
+ openedx_learning-0.16.0.dist-info/LICENSE.txt,sha256=QTW2QN7q3XszgUAXm9Dzgtu5LXYKbR1SGnqMa7ufEuY,35139
115
+ openedx_learning-0.16.0.dist-info/METADATA,sha256=KFY2MnwnFFqI4lWfz9rVFuvcSvhhLWr9L26T-RIJRtI,8777
116
+ openedx_learning-0.16.0.dist-info/WHEEL,sha256=AHX6tWk3qWuce7vKLrj7lnulVHEdWoltgauo8bgCXgU,109
117
+ openedx_learning-0.16.0.dist-info/top_level.txt,sha256=IYFbr5mgiEHd-LOtZmXj3q3a0bkGK1M9LY7GXgnfi4M,33
118
+ openedx_learning-0.16.0.dist-info/RECORD,,
@@ -484,3 +484,35 @@ def delete_tags_from_taxonomy(
484
484
  """
485
485
  taxonomy = taxonomy.cast()
486
486
  taxonomy.delete_tags(tags, with_subtags)
487
+
488
+
489
+ def copy_tags(source_object_id: str, dest_object_id: str):
490
+ """
491
+ Copy all tags from one object to another.
492
+
493
+ This keeps all not-copied tags and delete all
494
+ previous copied tags of the dest object.
495
+ If there are not-copied tags that also are in 'source_object_id',
496
+ then they become copied.
497
+ """
498
+ source_object_tags = get_object_tags(
499
+ source_object_id,
500
+ )
501
+ copied_tags = ObjectTag.objects.filter(
502
+ object_id=dest_object_id,
503
+ is_copied=True,
504
+ )
505
+
506
+ with transaction.atomic():
507
+ # Delete all copied tags of destination
508
+ copied_tags.delete()
509
+
510
+ # Copy an create object_tags in destination
511
+ for object_tag in source_object_tags:
512
+ ObjectTag.objects.update_or_create(
513
+ object_id=dest_object_id,
514
+ taxonomy_id=object_tag.taxonomy_id,
515
+ tag_id=object_tag.tag_id,
516
+ defaults={"is_copied": True},
517
+ # Note: _value and _export_id are set automatically
518
+ )
@@ -0,0 +1,18 @@
1
+ # Generated by Django 4.2.16 on 2024-10-04 19:21
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('oel_tagging', '0017_alter_tagimporttask_status'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name='objecttag',
15
+ name='is_copied',
16
+ field=models.BooleanField(default=False, help_text="True if this object tag has been copied from one object to another using 'copy_tags' api function"),
17
+ ),
18
+ ]
@@ -795,6 +795,13 @@ class ObjectTag(models.Model):
795
795
  "Tag associated with this object tag. Provides the tag's 'value' if set."
796
796
  ),
797
797
  )
798
+ is_copied = models.BooleanField(
799
+ default=False,
800
+ help_text=_(
801
+ "True if this object tag has been copied from one object to another"
802
+ " using 'copy_tags' api function"
803
+ ),
804
+ )
798
805
  _export_id = case_insensitive_char_field(
799
806
  max_length=255,
800
807
  help_text=_(
@@ -981,6 +988,7 @@ class ObjectTag(models.Model):
981
988
  self.tag = object_tag.tag
982
989
  self.taxonomy = object_tag.taxonomy
983
990
  self.object_id = object_tag.object_id
991
+ self.is_copied = object_tag.is_copied
984
992
  self._value = object_tag._value # pylint: disable=protected-access
985
993
  self._export_id = object_tag._export_id # pylint: disable=protected-access
986
994
  return self
@@ -176,6 +176,9 @@ class ObjectTagsByTaxonomySerializer(UserPermissionsSerializerMixin, serializers
176
176
  """
177
177
  Convert this list of ObjectTags to the serialized dictionary, grouped by Taxonomy
178
178
  """
179
+ # Allows consumers like edx-platform to override this
180
+ ObjectTagViewMinimalSerializer = self.context["view"].minimal_serializer_class
181
+
179
182
  can_tag_object_perm = f"{self.app_label}.can_tag_object"
180
183
  by_object: dict[str, dict[str, Any]] = {}
181
184
  for obj_tag in instance:
@@ -194,7 +197,7 @@ class ObjectTagsByTaxonomySerializer(UserPermissionsSerializerMixin, serializers
194
197
  "export_id": obj_tag.export_id,
195
198
  }
196
199
  taxonomies.append(tax_entry)
197
- tax_entry["tags"].append(ObjectTagMinimalSerializer(obj_tag, context=self.context).data)
200
+ tax_entry["tags"].append(ObjectTagViewMinimalSerializer(obj_tag, context=self.context).data)
198
201
  return by_object
199
202
 
200
203
 
@@ -37,6 +37,7 @@ from ..utils import view_auth_classes
37
37
  from .permissions import ObjectTagObjectPermissions, TaxonomyObjectPermissions, TaxonomyTagsObjectPermissions
38
38
  from .serializers import (
39
39
  ObjectTagListQueryParamsSerializer,
40
+ ObjectTagMinimalSerializer,
40
41
  ObjectTagsByTaxonomySerializer,
41
42
  ObjectTagSerializer,
42
43
  ObjectTagUpdateBodySerializer,
@@ -443,7 +444,10 @@ class ObjectTagView(
443
444
  * 405 - Method not allowed
444
445
  """
445
446
 
447
+ # Serializer used in `get_queryset` when getting tags per taxonomy
446
448
  serializer_class = ObjectTagSerializer
449
+ # Serializer used in the result in `to_representation` in `ObjectTagsByTaxonomySerializer`
450
+ minimal_serializer_class = ObjectTagMinimalSerializer
447
451
  permission_classes = [ObjectTagObjectPermissions]
448
452
  lookup_field = "object_id"
449
453