openedx-learning 0.14.0__tar.gz → 0.16.0__tar.gz

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.
Files changed (127) hide show
  1. {openedx_learning-0.14.0/openedx_learning.egg-info → openedx_learning-0.16.0}/PKG-INFO +3 -3
  2. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/__init__.py +1 -1
  3. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/components/api.py +55 -1
  4. {openedx_learning-0.14.0 → openedx_learning-0.16.0/openedx_learning.egg-info}/PKG-INFO +3 -3
  5. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning.egg-info/SOURCES.txt +1 -0
  6. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/api.py +32 -0
  7. openedx_learning-0.16.0/openedx_tagging/core/tagging/migrations/0018_objecttag_is_copied.py +18 -0
  8. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/models/base.py +8 -0
  9. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/rest_api/v1/serializers.py +4 -1
  10. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/rest_api/v1/views.py +4 -0
  11. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/CHANGELOG.rst +0 -0
  12. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/LICENSE.txt +0 -0
  13. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/MANIFEST.in +0 -0
  14. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/README.rst +0 -0
  15. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/api/__init__.py +0 -0
  16. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/api/authoring.py +0 -0
  17. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/api/authoring_models.py +0 -0
  18. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/__init__.py +0 -0
  19. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/__init__.py +0 -0
  20. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/collections/__init__.py +0 -0
  21. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/collections/admin.py +0 -0
  22. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/collections/api.py +0 -0
  23. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/collections/apps.py +0 -0
  24. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/collections/migrations/0001_initial.py +0 -0
  25. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/collections/migrations/0002_remove_collection_name_collection_created_by_and_more.py +0 -0
  26. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/collections/migrations/0003_collection_entities.py +0 -0
  27. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/collections/migrations/0004_collection_key.py +0 -0
  28. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/collections/migrations/0005_alter_collection_options_alter_collection_enabled.py +0 -0
  29. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/collections/migrations/__init__.py +0 -0
  30. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/collections/models.py +0 -0
  31. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/components/__init__.py +0 -0
  32. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/components/admin.py +0 -0
  33. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/components/apps.py +0 -0
  34. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/components/management/__init__.py +0 -0
  35. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/components/management/commands/__init__.py +0 -0
  36. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/components/management/commands/add_assets_to_component.py +0 -0
  37. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/components/migrations/0001_initial.py +0 -0
  38. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/components/migrations/0002_alter_componentversioncontent_key.py +0 -0
  39. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/components/migrations/__init__.py +0 -0
  40. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/components/models.py +0 -0
  41. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/contents/__init__.py +0 -0
  42. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/contents/admin.py +0 -0
  43. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/contents/api.py +0 -0
  44. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/contents/apps.py +0 -0
  45. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/contents/migrations/0001_initial.py +0 -0
  46. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/contents/migrations/__init__.py +0 -0
  47. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/contents/models.py +0 -0
  48. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/publishing/__init__.py +0 -0
  49. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/publishing/admin.py +0 -0
  50. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/publishing/api.py +0 -0
  51. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/publishing/apps.py +0 -0
  52. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/publishing/migrations/0001_initial.py +0 -0
  53. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/publishing/migrations/0002_alter_learningpackage_key_and_more.py +0 -0
  54. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/publishing/migrations/__init__.py +0 -0
  55. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/publishing/model_mixins.py +0 -0
  56. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/apps/authoring/publishing/models.py +0 -0
  57. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/contrib/__init__.py +0 -0
  58. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/contrib/media_server/__init__.py +0 -0
  59. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/contrib/media_server/apps.py +0 -0
  60. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/contrib/media_server/urls.py +0 -0
  61. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/contrib/media_server/views.py +0 -0
  62. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/lib/__init__.py +0 -0
  63. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/lib/admin_utils.py +0 -0
  64. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/lib/cache.py +0 -0
  65. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/lib/collations.py +0 -0
  66. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/lib/fields.py +0 -0
  67. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/lib/managers.py +0 -0
  68. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/lib/test_utils.py +0 -0
  69. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/lib/validators.py +0 -0
  70. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning/py.typed +0 -0
  71. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning.egg-info/dependency_links.txt +0 -0
  72. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning.egg-info/not-zip-safe +0 -0
  73. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning.egg-info/requires.txt +2 -2
  74. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_learning.egg-info/top_level.txt +0 -0
  75. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/__init__.py +0 -0
  76. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/__init__.py +0 -0
  77. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/__init__.py +0 -0
  78. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/admin.py +0 -0
  79. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/apps.py +0 -0
  80. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/data.py +0 -0
  81. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/import_export/__init__.py +0 -0
  82. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/import_export/actions.py +0 -0
  83. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/import_export/api.py +0 -0
  84. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/import_export/exceptions.py +0 -0
  85. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/import_export/import_plan.py +0 -0
  86. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/import_export/parsers.py +0 -0
  87. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/import_export/tasks.py +0 -0
  88. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/import_export/template.csv +0 -0
  89. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/import_export/template.json +0 -0
  90. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/migrations/0001_initial.py +0 -0
  91. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/migrations/0001_squashed.py +0 -0
  92. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/migrations/0002_auto_20230718_2026.py +0 -0
  93. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/migrations/0003_auto_20230721_1238.py +0 -0
  94. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/migrations/0004_auto_20230723_2001.py +0 -0
  95. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/migrations/0005_language_taxonomy.py +0 -0
  96. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/migrations/0006_alter_objecttag_unique_together.py +0 -0
  97. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/migrations/0006_auto_20230802_1631.py +0 -0
  98. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/migrations/0007_tag_import_task_log_null_fix.py +0 -0
  99. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/migrations/0008_taxonomy_description_not_null.py +0 -0
  100. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/migrations/0009_alter_objecttag_object_id.py +0 -0
  101. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/migrations/0010_cleanups.py +0 -0
  102. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/migrations/0011_remove_required.py +0 -0
  103. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/migrations/0012_language_taxonomy.py +0 -0
  104. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/migrations/0013_tag_parent_blank.py +0 -0
  105. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/migrations/0014_minor_fixes.py +0 -0
  106. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/migrations/0015_taxonomy_export_id.py +0 -0
  107. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/migrations/0016_object_tag_export_id.py +0 -0
  108. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/migrations/0017_alter_tagimporttask_status.py +0 -0
  109. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/migrations/__init__.py +0 -0
  110. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/models/__init__.py +0 -0
  111. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/models/import_export.py +0 -0
  112. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/models/system_defined.py +0 -0
  113. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/models/utils.py +0 -0
  114. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/rest_api/__init__.py +0 -0
  115. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/rest_api/paginators.py +0 -0
  116. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/rest_api/urls.py +0 -0
  117. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/rest_api/utils.py +0 -0
  118. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/rest_api/v1/__init__.py +0 -0
  119. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/rest_api/v1/permissions.py +0 -0
  120. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/rest_api/v1/urls.py +0 -0
  121. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/rest_api/v1/views_import.py +0 -0
  122. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/rules.py +0 -0
  123. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/core/tagging/urls.py +0 -0
  124. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/openedx_tagging/py.typed +0 -0
  125. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/requirements/base.in +0 -0
  126. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/setup.cfg +0 -0
  127. {openedx_learning-0.14.0 → openedx_learning-0.16.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: openedx-learning
3
- Version: 0.14.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,11 +18,11 @@ 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: attrs
22
21
  Requires-Dist: celery
23
22
  Requires-Dist: djangorestframework<4.0
24
- Requires-Dist: rules<4.0
23
+ Requires-Dist: attrs
25
24
  Requires-Dist: Django<5.0
25
+ Requires-Dist: rules<4.0
26
26
  Requires-Dist: edx-drf-extensions
27
27
 
28
28
  Open edX Learning Core (and Tagging)
@@ -1,4 +1,4 @@
1
1
  """
2
2
  Open edX Learning ("Learning Core").
3
3
  """
4
- __version__ = "0.14.0"
4
+ __version__ = "0.16.0"
@@ -12,16 +12,18 @@ are stored in this app.
12
12
  """
13
13
  from __future__ import annotations
14
14
 
15
- from datetime import datetime
15
+ from datetime import datetime, timezone
16
16
  from enum import StrEnum, auto
17
17
  from logging import getLogger
18
18
  from pathlib import Path
19
19
  from uuid import UUID
20
20
 
21
+ from django.core.exceptions import ValidationError
21
22
  from django.db.models import Q, QuerySet
22
23
  from django.db.transaction import atomic
23
24
  from django.http.response import HttpResponse, HttpResponseNotFound
24
25
 
26
+ from ..collections.models import Collection, CollectionPublishableEntity
25
27
  from ..contents import api as contents_api
26
28
  from ..publishing import api as publishing_api
27
29
  from .models import Component, ComponentType, ComponentVersion, ComponentVersionContent
@@ -48,6 +50,7 @@ __all__ = [
48
50
  "look_up_component_version_content",
49
51
  "AssetError",
50
52
  "get_redirect_response_for_component_asset",
53
+ "set_collections",
51
54
  ]
52
55
 
53
56
 
@@ -603,3 +606,54 @@ def get_redirect_response_for_component_asset(
603
606
  )
604
607
 
605
608
  return HttpResponse(headers={**info_headers, **redirect_headers})
609
+
610
+
611
+ def set_collections(
612
+ learning_package_id: int,
613
+ component: Component,
614
+ collection_qset: QuerySet[Collection],
615
+ created_by: int | None = None,
616
+ ) -> set[Collection]:
617
+ """
618
+ Set collections for a given component.
619
+
620
+ These Collections must belong to the same LearningPackage as the Component, or a ValidationError will be raised.
621
+
622
+ Modified date of all collections related to component is updated.
623
+
624
+ Returns the updated collections.
625
+ """
626
+ # Disallow adding entities outside the collection's learning package
627
+ invalid_collection = collection_qset.exclude(learning_package_id=learning_package_id).first()
628
+ if invalid_collection:
629
+ raise ValidationError(
630
+ f"Cannot add collection {invalid_collection.pk} in learning package "
631
+ f"{invalid_collection.learning_package_id} to component {component} in "
632
+ f"learning package {learning_package_id}."
633
+ )
634
+ current_relations = CollectionPublishableEntity.objects.filter(
635
+ entity=component.publishable_entity
636
+ ).select_related('collection')
637
+ # Clear other collections for given component and add only new collections from collection_qset
638
+ removed_collections = set(
639
+ r.collection for r in current_relations.exclude(collection__in=collection_qset)
640
+ )
641
+ new_collections = set(collection_qset.exclude(
642
+ id__in=current_relations.values_list('collection', flat=True)
643
+ ))
644
+ # Use `remove` instead of `CollectionPublishableEntity.delete()` to trigger m2m_changed signal which will handle
645
+ # updating component index.
646
+ component.publishable_entity.collections.remove(*removed_collections)
647
+ component.publishable_entity.collections.add(
648
+ *new_collections,
649
+ through_defaults={"created_by_id": created_by},
650
+ )
651
+ # Update modified date via update to avoid triggering post_save signal for collections
652
+ # The signal triggers index update for each collection synchronously which will be very slow in this case.
653
+ # Instead trigger the index update in the caller function asynchronously.
654
+ affected_collection = removed_collections | new_collections
655
+ Collection.objects.filter(
656
+ id__in=[collection.id for collection in affected_collection]
657
+ ).update(modified=datetime.now(tz=timezone.utc))
658
+
659
+ return affected_collection
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: openedx-learning
3
- Version: 0.14.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,11 +18,11 @@ 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: attrs
22
21
  Requires-Dist: celery
23
22
  Requires-Dist: djangorestframework<4.0
24
- Requires-Dist: rules<4.0
23
+ Requires-Dist: attrs
25
24
  Requires-Dist: Django<5.0
25
+ Requires-Dist: rules<4.0
26
26
  Requires-Dist: edx-drf-extensions
27
27
 
28
28
  Open edX Learning Core (and Tagging)
@@ -106,6 +106,7 @@ openedx_tagging/core/tagging/migrations/0014_minor_fixes.py
106
106
  openedx_tagging/core/tagging/migrations/0015_taxonomy_export_id.py
107
107
  openedx_tagging/core/tagging/migrations/0016_object_tag_export_id.py
108
108
  openedx_tagging/core/tagging/migrations/0017_alter_tagimporttask_status.py
109
+ openedx_tagging/core/tagging/migrations/0018_objecttag_is_copied.py
109
110
  openedx_tagging/core/tagging/migrations/__init__.py
110
111
  openedx_tagging/core/tagging/models/__init__.py
111
112
  openedx_tagging/core/tagging/models/base.py
@@ -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
 
@@ -1,6 +1,6 @@
1
- attrs
2
1
  celery
3
2
  djangorestframework<4.0
4
- rules<4.0
3
+ attrs
5
4
  Django<5.0
5
+ rules<4.0
6
6
  edx-drf-extensions