openedx-learning 0.11.3__tar.gz → 0.11.5__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 (121) hide show
  1. {openedx_learning-0.11.3/openedx_learning.egg-info → openedx_learning-0.11.5}/PKG-INFO +5 -5
  2. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/__init__.py +1 -1
  3. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/apps/authoring/collections/api.py +17 -12
  4. openedx_learning-0.11.5/openedx_learning/apps/authoring/collections/migrations/0004_collection_key.py +57 -0
  5. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/apps/authoring/collections/models.py +33 -3
  6. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/apps/authoring/publishing/model_mixins.py +4 -0
  7. {openedx_learning-0.11.3 → openedx_learning-0.11.5/openedx_learning.egg-info}/PKG-INFO +5 -5
  8. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning.egg-info/SOURCES.txt +1 -0
  9. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/setup.py +1 -1
  10. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/CHANGELOG.rst +0 -0
  11. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/LICENSE.txt +0 -0
  12. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/MANIFEST.in +0 -0
  13. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/README.rst +0 -0
  14. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/api/__init__.py +0 -0
  15. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/api/authoring.py +0 -0
  16. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/api/authoring_models.py +0 -0
  17. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/apps/__init__.py +0 -0
  18. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/apps/authoring/__init__.py +0 -0
  19. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/apps/authoring/collections/__init__.py +0 -0
  20. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/apps/authoring/collections/apps.py +0 -0
  21. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/apps/authoring/collections/migrations/0001_initial.py +0 -0
  22. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/apps/authoring/collections/migrations/0002_remove_collection_name_collection_created_by_and_more.py +0 -0
  23. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/apps/authoring/collections/migrations/0003_collection_entities.py +0 -0
  24. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/apps/authoring/collections/migrations/__init__.py +0 -0
  25. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/apps/authoring/components/__init__.py +0 -0
  26. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/apps/authoring/components/admin.py +0 -0
  27. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/apps/authoring/components/api.py +0 -0
  28. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/apps/authoring/components/apps.py +0 -0
  29. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/apps/authoring/components/migrations/0001_initial.py +0 -0
  30. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/apps/authoring/components/migrations/0002_alter_componentversioncontent_key.py +0 -0
  31. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/apps/authoring/components/migrations/__init__.py +0 -0
  32. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/apps/authoring/components/models.py +0 -0
  33. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/apps/authoring/contents/__init__.py +0 -0
  34. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/apps/authoring/contents/admin.py +0 -0
  35. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/apps/authoring/contents/api.py +0 -0
  36. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/apps/authoring/contents/apps.py +0 -0
  37. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/apps/authoring/contents/migrations/0001_initial.py +0 -0
  38. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/apps/authoring/contents/migrations/__init__.py +0 -0
  39. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/apps/authoring/contents/models.py +0 -0
  40. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/apps/authoring/publishing/__init__.py +0 -0
  41. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/apps/authoring/publishing/admin.py +0 -0
  42. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/apps/authoring/publishing/api.py +0 -0
  43. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/apps/authoring/publishing/apps.py +0 -0
  44. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/apps/authoring/publishing/migrations/0001_initial.py +0 -0
  45. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/apps/authoring/publishing/migrations/0002_alter_learningpackage_key_and_more.py +0 -0
  46. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/apps/authoring/publishing/migrations/__init__.py +0 -0
  47. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/apps/authoring/publishing/models.py +0 -0
  48. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/contrib/__init__.py +0 -0
  49. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/contrib/media_server/__init__.py +0 -0
  50. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/contrib/media_server/apps.py +0 -0
  51. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/contrib/media_server/urls.py +0 -0
  52. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/contrib/media_server/views.py +0 -0
  53. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/lib/__init__.py +0 -0
  54. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/lib/admin_utils.py +0 -0
  55. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/lib/cache.py +0 -0
  56. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/lib/collations.py +0 -0
  57. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/lib/fields.py +0 -0
  58. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/lib/managers.py +0 -0
  59. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/lib/test_utils.py +0 -0
  60. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/lib/validators.py +0 -0
  61. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning/py.typed +0 -0
  62. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning.egg-info/dependency_links.txt +0 -0
  63. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning.egg-info/not-zip-safe +0 -0
  64. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning.egg-info/requires.txt +3 -3
  65. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_learning.egg-info/top_level.txt +0 -0
  66. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/__init__.py +0 -0
  67. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/__init__.py +0 -0
  68. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/__init__.py +0 -0
  69. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/admin.py +0 -0
  70. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/api.py +0 -0
  71. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/apps.py +0 -0
  72. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/data.py +0 -0
  73. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/import_export/__init__.py +0 -0
  74. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/import_export/actions.py +0 -0
  75. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/import_export/api.py +0 -0
  76. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/import_export/exceptions.py +0 -0
  77. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/import_export/import_plan.py +0 -0
  78. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/import_export/parsers.py +0 -0
  79. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/import_export/tasks.py +0 -0
  80. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/import_export/template.csv +0 -0
  81. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/import_export/template.json +0 -0
  82. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/migrations/0001_initial.py +0 -0
  83. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/migrations/0001_squashed.py +0 -0
  84. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/migrations/0002_auto_20230718_2026.py +0 -0
  85. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/migrations/0003_auto_20230721_1238.py +0 -0
  86. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/migrations/0004_auto_20230723_2001.py +0 -0
  87. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/migrations/0005_language_taxonomy.py +0 -0
  88. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/migrations/0006_alter_objecttag_unique_together.py +0 -0
  89. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/migrations/0006_auto_20230802_1631.py +0 -0
  90. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/migrations/0007_tag_import_task_log_null_fix.py +0 -0
  91. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/migrations/0008_taxonomy_description_not_null.py +0 -0
  92. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/migrations/0009_alter_objecttag_object_id.py +0 -0
  93. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/migrations/0010_cleanups.py +0 -0
  94. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/migrations/0011_remove_required.py +0 -0
  95. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/migrations/0012_language_taxonomy.py +0 -0
  96. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/migrations/0013_tag_parent_blank.py +0 -0
  97. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/migrations/0014_minor_fixes.py +0 -0
  98. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/migrations/0015_taxonomy_export_id.py +0 -0
  99. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/migrations/0016_object_tag_export_id.py +0 -0
  100. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/migrations/0017_alter_tagimporttask_status.py +0 -0
  101. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/migrations/__init__.py +0 -0
  102. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/models/__init__.py +0 -0
  103. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/models/base.py +0 -0
  104. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/models/import_export.py +0 -0
  105. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/models/system_defined.py +0 -0
  106. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/models/utils.py +0 -0
  107. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/rest_api/__init__.py +0 -0
  108. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/rest_api/paginators.py +0 -0
  109. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/rest_api/urls.py +0 -0
  110. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/rest_api/utils.py +0 -0
  111. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/rest_api/v1/__init__.py +0 -0
  112. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/rest_api/v1/permissions.py +0 -0
  113. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/rest_api/v1/serializers.py +0 -0
  114. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/rest_api/v1/urls.py +0 -0
  115. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/rest_api/v1/views.py +0 -0
  116. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/rest_api/v1/views_import.py +0 -0
  117. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/rules.py +0 -0
  118. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/core/tagging/urls.py +0 -0
  119. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/openedx_tagging/py.typed +0 -0
  120. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/requirements/base.in +0 -0
  121. {openedx_learning-0.11.3 → openedx_learning-0.11.5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: openedx-learning
3
- Version: 0.11.3
3
+ Version: 0.11.5
4
4
  Summary: Open edX Learning Core and Tagging.
5
5
  Home-page: https://github.com/openedx/openedx-learning
6
6
  Author: David Ormsbee
@@ -9,7 +9,7 @@ License: AGPL 3.0
9
9
  Keywords: Python edx
10
10
  Classifier: Development Status :: 3 - Alpha
11
11
  Classifier: Framework :: Django
12
- Classifier: Framework :: Django :: 3.2
12
+ Classifier: Framework :: Django :: 4.2
13
13
  Classifier: Intended Audience :: Developers
14
14
  Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
15
15
  Classifier: Natural Language :: English
@@ -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: celery
21
22
  Requires-Dist: Django<5.0
22
- Requires-Dist: djangorestframework<4.0
23
- Requires-Dist: edx-drf-extensions
24
23
  Requires-Dist: attrs
24
+ Requires-Dist: djangorestframework<4.0
25
25
  Requires-Dist: rules<4.0
26
- Requires-Dist: celery
26
+ Requires-Dist: edx-drf-extensions
27
27
 
28
28
  Open edX Learning Core (and Tagging)
29
29
  ====================================
@@ -1,4 +1,4 @@
1
1
  """
2
2
  Open edX Learning ("Learning Core").
3
3
  """
4
- __version__ = "0.11.3"
4
+ __version__ = "0.11.5"
@@ -30,6 +30,8 @@ __all__ = [
30
30
 
31
31
  def create_collection(
32
32
  learning_package_id: int,
33
+ key: str,
34
+ *,
33
35
  title: str,
34
36
  created_by: int | None,
35
37
  description: str = "",
@@ -40,6 +42,7 @@ def create_collection(
40
42
  """
41
43
  collection = Collection.objects.create(
42
44
  learning_package_id=learning_package_id,
45
+ key=key,
43
46
  title=title,
44
47
  created_by_id=created_by,
45
48
  description=description,
@@ -48,22 +51,24 @@ def create_collection(
48
51
  return collection
49
52
 
50
53
 
51
- def get_collection(collection_id: int) -> Collection:
54
+ def get_collection(learning_package_id: int, collection_key: str) -> Collection:
52
55
  """
53
56
  Get a Collection by ID
54
57
  """
55
- return Collection.objects.get(id=collection_id)
58
+ return Collection.objects.get_by_key(learning_package_id, collection_key)
56
59
 
57
60
 
58
61
  def update_collection(
59
- collection_id: int,
62
+ learning_package_id: int,
63
+ key: str,
64
+ *,
60
65
  title: str | None = None,
61
66
  description: str | None = None,
62
67
  ) -> Collection:
63
68
  """
64
- Update a Collection
69
+ Update a Collection identified by the learning_package_id + key.
65
70
  """
66
- collection = Collection.objects.get(id=collection_id)
71
+ collection = get_collection(learning_package_id, key)
67
72
 
68
73
  # If no changes were requested, there's nothing to update, so just return
69
74
  # the Collection as-is
@@ -80,7 +85,8 @@ def update_collection(
80
85
 
81
86
 
82
87
  def add_to_collection(
83
- collection_id: int,
88
+ learning_package_id: int,
89
+ key: str,
84
90
  entities_qset: QuerySet[PublishableEntity],
85
91
  created_by: int | None = None,
86
92
  ) -> Collection:
@@ -95,17 +101,15 @@ def add_to_collection(
95
101
 
96
102
  Returns the updated Collection object.
97
103
  """
98
- collection = get_collection(collection_id)
99
- learning_package_id = collection.learning_package_id
100
-
101
104
  # Disallow adding entities outside the collection's learning package
102
105
  invalid_entity = entities_qset.exclude(learning_package_id=learning_package_id).first()
103
106
  if invalid_entity:
104
107
  raise ValidationError(
105
108
  f"Cannot add entity {invalid_entity.pk} in learning package {invalid_entity.learning_package_id} "
106
- f"to collection {collection_id} in learning package {learning_package_id}."
109
+ f"to collection {key} in learning package {learning_package_id}."
107
110
  )
108
111
 
112
+ collection = get_collection(learning_package_id, key)
109
113
  collection.entities.add(
110
114
  *entities_qset.all(),
111
115
  through_defaults={"created_by_id": created_by},
@@ -117,7 +121,8 @@ def add_to_collection(
117
121
 
118
122
 
119
123
  def remove_from_collection(
120
- collection_id: int,
124
+ learning_package_id: int,
125
+ key: str,
121
126
  entities_qset: QuerySet[PublishableEntity],
122
127
  ) -> Collection:
123
128
  """
@@ -129,7 +134,7 @@ def remove_from_collection(
129
134
 
130
135
  Returns the updated Collection.
131
136
  """
132
- collection = get_collection(collection_id)
137
+ collection = get_collection(learning_package_id, key)
133
138
 
134
139
  collection.entities.remove(*entities_qset.all())
135
140
  collection.modified = datetime.now(tz=timezone.utc)
@@ -0,0 +1,57 @@
1
+ # Generated by Django 4.2.15 on 2024-09-04 23:15
2
+
3
+ from django.db import migrations, models
4
+ from django.utils.crypto import get_random_string
5
+
6
+ import openedx_learning.lib.fields
7
+
8
+
9
+ def generate_keys(apps, schema_editor):
10
+ """
11
+ Generates a random strings to initialize the key field where NULL.
12
+ """
13
+ length = 50
14
+ Collection = apps.get_model("oel_collections", "Collection")
15
+ for collection in Collection.objects.filter(key=None):
16
+ # Keep generating keys until we get a unique one
17
+ key = get_random_string(length)
18
+ while Collection.objects.filter(key=key).exists():
19
+ key = get_random_string(length)
20
+
21
+ collection.key = key
22
+ collection.save()
23
+
24
+
25
+ class Migration(migrations.Migration):
26
+
27
+ dependencies = [
28
+ ('oel_collections', '0003_collection_entities'),
29
+ ]
30
+
31
+ operations = [
32
+ # 1. Temporarily add this field with null=True, blank=True
33
+ migrations.AddField(
34
+ model_name='collection',
35
+ name='key',
36
+ field=openedx_learning.lib.fields.MultiCollationCharField(
37
+ db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'},
38
+ db_column='_key', max_length=500, null=True, blank=True),
39
+ preserve_default=False,
40
+ ),
41
+ # 2. Populate the null keys
42
+ migrations.RunPython(generate_keys),
43
+ # 3. Add null=False, blank=False to disallow NULL values
44
+ migrations.AlterField(
45
+ model_name='collection',
46
+ name='key',
47
+ field=openedx_learning.lib.fields.MultiCollationCharField(
48
+ db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'},
49
+ db_column='_key', max_length=500, null=False, blank=False),
50
+ preserve_default=False,
51
+ ),
52
+ # 4. Enforce unique constraint
53
+ migrations.AddConstraint(
54
+ model_name='collection',
55
+ constraint=models.UniqueConstraint(fields=('learning_package', 'key'), name='oel_coll_uniq_lp_key'),
56
+ ),
57
+ ]
@@ -70,8 +70,9 @@ from django.conf import settings
70
70
  from django.db import models
71
71
  from django.utils.translation import gettext_lazy as _
72
72
 
73
- from ....lib.fields import MultiCollationTextField, case_insensitive_char_field
74
- from ....lib.validators import validate_utc_datetime
73
+ from openedx_learning.lib.fields import MultiCollationTextField, case_insensitive_char_field, key_field
74
+ from openedx_learning.lib.validators import validate_utc_datetime
75
+
75
76
  from ..publishing.models import LearningPackage, PublishableEntity
76
77
 
77
78
  __all__ = [
@@ -80,16 +81,35 @@ __all__ = [
80
81
  ]
81
82
 
82
83
 
84
+ class CollectionManager(models.Manager):
85
+ """
86
+ Custom manager for Collection class.
87
+ """
88
+ def get_by_key(self, learning_package_id: int, key: str):
89
+ """
90
+ Get the Collection for the given Learning Package + key.
91
+ """
92
+ return self.select_related('learning_package') \
93
+ .get(learning_package_id=learning_package_id, key=key)
94
+
95
+
83
96
  class Collection(models.Model):
84
97
  """
85
98
  Represents a collection of library components
86
99
  """
100
+ objects: CollectionManager[Collection] = CollectionManager()
87
101
 
88
102
  id = models.AutoField(primary_key=True)
89
103
 
90
104
  # Each collection belongs to a learning package
91
105
  learning_package = models.ForeignKey(LearningPackage, on_delete=models.CASCADE)
92
106
 
107
+ # Every collection is uniquely and permanently identified within its learning package
108
+ # by a 'key' that is set during creation. Both will appear in the
109
+ # collection's opaque key:
110
+ # e.g. "lib-collection:lib:key" is the opaque key for a library collection.
111
+ key = key_field(db_column='_key')
112
+
93
113
  title = case_insensitive_char_field(
94
114
  null=False,
95
115
  blank=False,
@@ -151,6 +171,16 @@ class Collection(models.Model):
151
171
 
152
172
  class Meta:
153
173
  verbose_name_plural = "Collections"
174
+ constraints = [
175
+ # Keys are unique within a given LearningPackage.
176
+ models.UniqueConstraint(
177
+ fields=[
178
+ "learning_package",
179
+ "key",
180
+ ],
181
+ name="oel_coll_uniq_lp_key",
182
+ ),
183
+ ]
154
184
  indexes = [
155
185
  models.Index(fields=["learning_package", "title"]),
156
186
  ]
@@ -165,7 +195,7 @@ class Collection(models.Model):
165
195
  """
166
196
  User-facing string representation of a Collection.
167
197
  """
168
- return f"<{self.__class__.__name__}> ({self.id}:{self.title})"
198
+ return f"<{self.__class__.__name__}> (lp:{self.learning_package_id} {self.key}:{self.title})"
169
199
 
170
200
 
171
201
  class CollectionPublishableEntity(models.Model):
@@ -60,6 +60,10 @@ class PublishableEntityMixin(models.Model):
60
60
  def created(self):
61
61
  return self.publishable_entity.created
62
62
 
63
+ @property
64
+ def created_by(self):
65
+ return self.publishable_entity.created_by
66
+
63
67
  class Meta:
64
68
  abstract = True
65
69
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: openedx-learning
3
- Version: 0.11.3
3
+ Version: 0.11.5
4
4
  Summary: Open edX Learning Core and Tagging.
5
5
  Home-page: https://github.com/openedx/openedx-learning
6
6
  Author: David Ormsbee
@@ -9,7 +9,7 @@ License: AGPL 3.0
9
9
  Keywords: Python edx
10
10
  Classifier: Development Status :: 3 - Alpha
11
11
  Classifier: Framework :: Django
12
- Classifier: Framework :: Django :: 3.2
12
+ Classifier: Framework :: Django :: 4.2
13
13
  Classifier: Intended Audience :: Developers
14
14
  Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
15
15
  Classifier: Natural Language :: English
@@ -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: celery
21
22
  Requires-Dist: Django<5.0
22
- Requires-Dist: djangorestframework<4.0
23
- Requires-Dist: edx-drf-extensions
24
23
  Requires-Dist: attrs
24
+ Requires-Dist: djangorestframework<4.0
25
25
  Requires-Dist: rules<4.0
26
- Requires-Dist: celery
26
+ Requires-Dist: edx-drf-extensions
27
27
 
28
28
  Open edX Learning Core (and Tagging)
29
29
  ====================================
@@ -24,6 +24,7 @@ openedx_learning/apps/authoring/collections/models.py
24
24
  openedx_learning/apps/authoring/collections/migrations/0001_initial.py
25
25
  openedx_learning/apps/authoring/collections/migrations/0002_remove_collection_name_collection_created_by_and_more.py
26
26
  openedx_learning/apps/authoring/collections/migrations/0003_collection_entities.py
27
+ openedx_learning/apps/authoring/collections/migrations/0004_collection_key.py
27
28
  openedx_learning/apps/authoring/collections/migrations/__init__.py
28
29
  openedx_learning/apps/authoring/components/__init__.py
29
30
  openedx_learning/apps/authoring/components/admin.py
@@ -85,7 +85,7 @@ setup(
85
85
  classifiers=[
86
86
  'Development Status :: 3 - Alpha',
87
87
  'Framework :: Django',
88
- 'Framework :: Django :: 3.2',
88
+ 'Framework :: Django :: 4.2',
89
89
  'Intended Audience :: Developers',
90
90
  'License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)',
91
91
  'Natural Language :: English',
@@ -1,6 +1,6 @@
1
+ celery
1
2
  Django<5.0
2
- djangorestframework<4.0
3
- edx-drf-extensions
4
3
  attrs
4
+ djangorestframework<4.0
5
5
  rules<4.0
6
- celery
6
+ edx-drf-extensions