openedx-learning 0.19.2__py2.py3-none-any.whl → 0.22.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.
@@ -2,4 +2,4 @@
2
2
  Open edX Learning ("Learning Core").
3
3
  """
4
4
 
5
- __version__ = "0.19.2"
5
+ __version__ = "0.22.0"
@@ -10,7 +10,7 @@ from django.db.models import QuerySet
10
10
 
11
11
  from ..publishing import api as publishing_api
12
12
  from ..publishing.models import PublishableEntity
13
- from .models import Collection
13
+ from .models import Collection, CollectionPublishableEntity
14
14
 
15
15
  # The public API that will be re-exported by openedx_learning.apps.authoring.api
16
16
  # is listed in the __all__ entries below. Internal helper functions that are
@@ -27,6 +27,7 @@ __all__ = [
27
27
  "remove_from_collection",
28
28
  "restore_collection",
29
29
  "update_collection",
30
+ "set_collections",
30
31
  ]
31
32
 
32
33
 
@@ -204,3 +205,47 @@ def get_collections(learning_package_id: int, enabled: bool | None = True) -> Qu
204
205
  if enabled is not None:
205
206
  qs = qs.filter(enabled=enabled)
206
207
  return qs.select_related("learning_package").order_by('pk')
208
+
209
+
210
+ def set_collections(
211
+ publishable_entity: PublishableEntity,
212
+ collection_qset: QuerySet[Collection],
213
+ created_by: int | None = None,
214
+ ) -> set[Collection]:
215
+ """
216
+ Set collections for a given publishable entity.
217
+
218
+ These Collections must belong to the same LearningPackage as the PublishableEntity,
219
+ or a ValidationError will be raised.
220
+
221
+ Modified date of all collections related to entity is updated.
222
+
223
+ Returns the updated collections.
224
+ """
225
+ # Disallow adding entities outside the collection's learning package
226
+ if collection_qset.exclude(learning_package_id=publishable_entity.learning_package_id).count():
227
+ raise ValidationError(
228
+ "Collection entities must be from the same learning package as the collection.",
229
+ )
230
+ current_relations = CollectionPublishableEntity.objects.filter(
231
+ entity=publishable_entity
232
+ ).select_related('collection')
233
+ # Clear other collections for given entity and add only new collections from collection_qset
234
+ removed_collections = set(
235
+ r.collection for r in current_relations.exclude(collection__in=collection_qset)
236
+ )
237
+ new_collections = set(collection_qset.exclude(
238
+ id__in=current_relations.values_list('collection', flat=True)
239
+ ))
240
+ # Triggers a m2m_changed signal
241
+ publishable_entity.collections.set(
242
+ objs=collection_qset,
243
+ through_defaults={"created_by_id": created_by},
244
+ )
245
+ # Update modified date via update to avoid triggering post_save signal for all collections, which can be very slow.
246
+ affected_collection = removed_collections | new_collections
247
+ Collection.objects.filter(
248
+ id__in=[collection.id for collection in affected_collection]
249
+ ).update(modified=datetime.now(tz=timezone.utc))
250
+
251
+ return affected_collection
@@ -13,18 +13,16 @@ are stored in this app.
13
13
  from __future__ import annotations
14
14
 
15
15
  import mimetypes
16
- from datetime import datetime, timezone
16
+ from datetime import datetime
17
17
  from enum import StrEnum, auto
18
18
  from logging import getLogger
19
19
  from pathlib import Path
20
20
  from uuid import UUID
21
21
 
22
- from django.core.exceptions import ValidationError
23
22
  from django.db.models import Q, QuerySet
24
23
  from django.db.transaction import atomic
25
24
  from django.http.response import HttpResponse, HttpResponseNotFound
26
25
 
27
- from ..collections.models import Collection, CollectionPublishableEntity
28
26
  from ..contents import api as contents_api
29
27
  from ..publishing import api as publishing_api
30
28
  from .models import Component, ComponentType, ComponentVersion, ComponentVersionContent
@@ -51,7 +49,6 @@ __all__ = [
51
49
  "look_up_component_version_content",
52
50
  "AssetError",
53
51
  "get_redirect_response_for_component_asset",
54
- "set_collections",
55
52
  ]
56
53
 
57
54
 
@@ -605,54 +602,3 @@ def get_redirect_response_for_component_asset(
605
602
  )
606
603
 
607
604
  return HttpResponse(headers={**info_headers, **redirect_headers})
608
-
609
-
610
- def set_collections(
611
- learning_package_id: int,
612
- component: Component,
613
- collection_qset: QuerySet[Collection],
614
- created_by: int | None = None,
615
- ) -> set[Collection]:
616
- """
617
- Set collections for a given component.
618
-
619
- These Collections must belong to the same LearningPackage as the Component, or a ValidationError will be raised.
620
-
621
- Modified date of all collections related to component is updated.
622
-
623
- Returns the updated collections.
624
- """
625
- # Disallow adding entities outside the collection's learning package
626
- invalid_collection = collection_qset.exclude(learning_package_id=learning_package_id).first()
627
- if invalid_collection:
628
- raise ValidationError(
629
- f"Cannot add collection {invalid_collection.pk} in learning package "
630
- f"{invalid_collection.learning_package_id} to component {component} in "
631
- f"learning package {learning_package_id}."
632
- )
633
- current_relations = CollectionPublishableEntity.objects.filter(
634
- entity=component.publishable_entity
635
- ).select_related('collection')
636
- # Clear other collections for given component and add only new collections from collection_qset
637
- removed_collections = set(
638
- r.collection for r in current_relations.exclude(collection__in=collection_qset)
639
- )
640
- new_collections = set(collection_qset.exclude(
641
- id__in=current_relations.values_list('collection', flat=True)
642
- ))
643
- # Use `remove` instead of `CollectionPublishableEntity.delete()` to trigger m2m_changed signal which will handle
644
- # updating component index.
645
- component.publishable_entity.collections.remove(*removed_collections)
646
- component.publishable_entity.collections.add(
647
- *new_collections,
648
- through_defaults={"created_by_id": created_by},
649
- )
650
- # Update modified date via update to avoid triggering post_save signal for collections
651
- # The signal triggers index update for each collection synchronously which will be very slow in this case.
652
- # Instead trigger the index update in the caller function asynchronously.
653
- affected_collection = removed_collections | new_collections
654
- Collection.objects.filter(
655
- id__in=[collection.id for collection in affected_collection]
656
- ).update(modified=datetime.now(tz=timezone.utc))
657
-
658
- return affected_collection
@@ -74,8 +74,10 @@ __all__ = [
74
74
  "get_container",
75
75
  "get_container_by_key",
76
76
  "get_containers",
77
+ "get_collection_containers",
77
78
  "ChildrenEntitiesAction",
78
79
  "ContainerEntityListEntry",
80
+ "ContainerEntityRow",
79
81
  "get_entities_in_container",
80
82
  "contains_unpublished_changes",
81
83
  "get_containers_with_entity",
@@ -639,8 +641,7 @@ def create_entity_list() -> EntityList:
639
641
 
640
642
 
641
643
  def create_entity_list_with_rows(
642
- entity_pks: list[int],
643
- entity_version_pks: list[int | None],
644
+ entity_rows: list[ContainerEntityRow],
644
645
  *,
645
646
  learning_package_id: int | None,
646
647
  ) -> EntityList:
@@ -649,10 +650,7 @@ def create_entity_list_with_rows(
649
650
  Create new entity list rows for an entity list.
650
651
 
651
652
  Args:
652
- entity_pks: The IDs of the publishable entities that the entity list rows reference.
653
- entity_version_pks: The IDs of the versions of the entities
654
- (PublishableEntityVersion) that the entity list rows reference, or
655
- Nones for "unpinned" (default).
653
+ entity_rows: List of ContainerEntityRows specifying the publishable entity ID and version ID (if pinned).
656
654
  learning_package_id: Optional. Verify that all the entities are from
657
655
  the specified learning package.
658
656
 
@@ -662,13 +660,25 @@ def create_entity_list_with_rows(
662
660
  # Do a quick check that the given entities are in the right learning package:
663
661
  if learning_package_id:
664
662
  if PublishableEntity.objects.filter(
665
- pk__in=entity_pks,
663
+ pk__in=[entity.entity_pk for entity in entity_rows],
666
664
  ).exclude(
667
665
  learning_package_id=learning_package_id,
668
666
  ).exists():
669
667
  raise ValidationError("Container entities must be from the same learning package.")
670
668
 
671
- order_nums = range(len(entity_pks))
669
+ # Ensure that any pinned entity versions are linked to the correct entity
670
+ pinned_entities = {
671
+ entity.version_pk: entity.entity_pk
672
+ for entity in entity_rows if entity.pinned
673
+ }
674
+ if pinned_entities:
675
+ entity_versions = PublishableEntityVersion.objects.filter(
676
+ pk__in=pinned_entities.keys(),
677
+ ).only('pk', 'entity_id')
678
+ for entity_version in entity_versions:
679
+ if pinned_entities[entity_version.pk] != entity_version.entity_id:
680
+ raise ValidationError("Container entity versions must belong to the specified entity.")
681
+
672
682
  with atomic(savepoint=False):
673
683
 
674
684
  entity_list = create_entity_list()
@@ -676,13 +686,11 @@ def create_entity_list_with_rows(
676
686
  [
677
687
  EntityListRow(
678
688
  entity_list=entity_list,
679
- entity_id=entity_pk,
689
+ entity_id=entity.entity_pk,
680
690
  order_num=order_num,
681
- entity_version_id=entity_version_pk,
682
- )
683
- for order_num, entity_pk, entity_version_pk in zip(
684
- order_nums, entity_pks, entity_version_pks
691
+ entity_version_id=entity.version_pk,
685
692
  )
693
+ for order_num, entity in enumerate(entity_rows)
686
694
  ]
687
695
  )
688
696
  return entity_list
@@ -725,8 +733,7 @@ def create_container_version(
725
733
  version_num: int,
726
734
  *,
727
735
  title: str,
728
- publishable_entities_pks: list[int],
729
- entity_version_pks: list[int | None] | None,
736
+ entity_rows: list[ContainerEntityRow],
730
737
  created: datetime,
731
738
  created_by: int | None,
732
739
  container_version_cls: type[ContainerVersionModel] = ContainerVersion, # type: ignore[assignment]
@@ -739,8 +746,7 @@ def create_container_version(
739
746
  container_id: The ID of the container that the version belongs to.
740
747
  version_num: The version number of the container.
741
748
  title: The title of the container.
742
- publishable_entities_pks: The IDs of the members of the container.
743
- entity_version_pks: The IDs of the versions to pin to, if pinning is desired.
749
+ entity_rows: List of ContainerEntityRows specifying the publishable entity ID and version ID (if pinned).
744
750
  created: The date and time the container version was created.
745
751
  created_by: The ID of the user who created the container version.
746
752
  container_version_cls: The subclass of ContainerVersion to use, if applicable.
@@ -749,16 +755,13 @@ def create_container_version(
749
755
  The newly created container version.
750
756
  """
751
757
  assert title is not None
752
- assert publishable_entities_pks is not None
758
+ assert entity_rows is not None
753
759
 
754
760
  with atomic(savepoint=False):
755
761
  container = Container.objects.select_related("publishable_entity").get(pk=container_id)
756
762
  entity = container.publishable_entity
757
- if entity_version_pks is None:
758
- entity_version_pks = [None] * len(publishable_entities_pks)
759
763
  entity_list = create_entity_list_with_rows(
760
- entity_pks=publishable_entities_pks,
761
- entity_version_pks=entity_version_pks,
764
+ entity_rows=entity_rows,
762
765
  learning_package_id=entity.learning_package_id,
763
766
  )
764
767
  container_version = _create_container_version(
@@ -785,8 +788,7 @@ class ChildrenEntitiesAction(Enum):
785
788
  def create_next_entity_list(
786
789
  learning_package_id: int,
787
790
  last_version: ContainerVersion,
788
- publishable_entities_pks: list[int],
789
- entity_version_pks: list[int | None] | None,
791
+ entity_rows: list[ContainerEntityRow],
790
792
  entities_action: ChildrenEntitiesAction = ChildrenEntitiesAction.REPLACE,
791
793
  ) -> EntityList:
792
794
  """
@@ -795,55 +797,53 @@ def create_next_entity_list(
795
797
  Args:
796
798
  learning_package_id: Learning package ID
797
799
  last_version: Last version of container.
798
- publishable_entities_pks: The IDs of the members current members of the container.
799
- entity_version_pks: The IDs of the versions to pin to, if pinning is desired.
800
+ entity_rows: List of ContainerEntityRows specifying the publishable entity ID and version ID (if pinned).
800
801
  entities_action: APPEND, REMOVE or REPLACE given entities from/to the container
801
802
 
802
803
  Returns:
803
804
  The newly created entity list.
804
805
  """
805
- if entity_version_pks is None:
806
- entity_version_pks: list[int | None] = [None] * len(publishable_entities_pks) # type: ignore[no-redef]
807
806
  if entities_action == ChildrenEntitiesAction.APPEND:
808
807
  # get previous entity list rows
809
808
  last_entities = last_version.entity_list.entitylistrow_set.only(
810
809
  "entity_id",
811
810
  "entity_version_id"
812
811
  ).order_by("order_num")
813
- # append given publishable_entities_pks and entity_version_pks
814
- publishable_entities_pks = [entity.entity_id for entity in last_entities] + publishable_entities_pks
815
- entity_version_pks = [ # type: ignore[operator, assignment]
816
- entity.entity_version_id
812
+ # append given entity_rows to the existing children
813
+ entity_rows = [
814
+ ContainerEntityRow(
815
+ entity_pk=entity.entity_id,
816
+ version_pk=entity.entity_version_id,
817
+ )
817
818
  for entity in last_entities
818
- ] + entity_version_pks
819
+ ] + entity_rows
819
820
  elif entities_action == ChildrenEntitiesAction.REMOVE:
820
- # get previous entity list rows
821
+ # get previous entity list, excluding the entities in entity_rows
821
822
  last_entities = last_version.entity_list.entitylistrow_set.only(
822
823
  "entity_id",
823
824
  "entity_version_id"
825
+ ).exclude(
826
+ entity_id__in=[entity.entity_pk for entity in entity_rows]
824
827
  ).order_by("order_num")
825
- # Remove entities that are in publishable_entities_pks
826
- new_entities = [
827
- entity
828
- for entity in last_entities
829
- if entity.entity_id not in publishable_entities_pks
828
+ entity_rows = [
829
+ ContainerEntityRow(
830
+ entity_pk=entity.entity_id,
831
+ version_pk=entity.entity_version_id,
832
+ )
833
+ for entity in last_entities.all()
830
834
  ]
831
- publishable_entities_pks = [entity.entity_id for entity in new_entities]
832
- entity_version_pks = [entity.entity_version_id for entity in new_entities]
833
- next_entity_list = create_entity_list_with_rows(
834
- entity_pks=publishable_entities_pks,
835
- entity_version_pks=entity_version_pks, # type: ignore[arg-type]
835
+
836
+ return create_entity_list_with_rows(
837
+ entity_rows=entity_rows,
836
838
  learning_package_id=learning_package_id,
837
839
  )
838
- return next_entity_list
839
840
 
840
841
 
841
842
  def create_next_container_version(
842
843
  container_pk: int,
843
844
  *,
844
845
  title: str | None,
845
- publishable_entities_pks: list[int] | None,
846
- entity_version_pks: list[int | None] | None,
846
+ entity_rows: list[ContainerEntityRow] | None,
847
847
  created: datetime,
848
848
  created_by: int | None,
849
849
  container_version_cls: type[ContainerVersionModel] = ContainerVersion, # type: ignore[assignment]
@@ -863,8 +863,8 @@ def create_next_container_version(
863
863
  Args:
864
864
  container_pk: The ID of the container to create the next version of.
865
865
  title: The title of the container. None to keep the current title.
866
- publishable_entities_pks: The IDs of the members current members of the container. Or None for no change.
867
- entity_version_pks: The IDs of the versions to pin to, if pinning is desired.
866
+ entity_rows: List of ContainerEntityRows specifying the publishable entity ID and version ID (if pinned).
867
+ Or None for no change.
868
868
  created: The date and time the container version was created.
869
869
  created_by: The ID of the user who created the container version.
870
870
  container_version_cls: The subclass of ContainerVersion to use, if applicable.
@@ -879,15 +879,15 @@ def create_next_container_version(
879
879
  last_version = container.versioning.latest
880
880
  assert last_version is not None
881
881
  next_version_num = last_version.version_num + 1
882
- if publishable_entities_pks is None:
882
+
883
+ if entity_rows is None:
883
884
  # We're only changing metadata. Keep the same entity list.
884
885
  next_entity_list = last_version.entity_list
885
886
  else:
886
887
  next_entity_list = create_next_entity_list(
887
888
  entity.learning_package_id,
888
889
  last_version,
889
- publishable_entities_pks,
890
- entity_version_pks,
890
+ entity_rows,
891
891
  entities_action
892
892
  )
893
893
 
@@ -955,6 +955,21 @@ def get_containers(
955
955
  return container_cls.objects.filter(publishable_entity__learning_package=learning_package_id)
956
956
 
957
957
 
958
+ def get_collection_containers(
959
+ learning_package_id: int,
960
+ collection_key: str,
961
+ ) -> QuerySet[Container]:
962
+ """
963
+ Returns a QuerySet of Containers relating to the PublishableEntities in a Collection.
964
+
965
+ Containers have a one-to-one relationship with PublishableEntity, but the reverse may not always be true.
966
+ """
967
+ return Container.objects.filter(
968
+ publishable_entity__learning_package_id=learning_package_id,
969
+ publishable_entity__collections__key=collection_key,
970
+ ).order_by('pk')
971
+
972
+
958
973
  @dataclass(frozen=True)
959
974
  class ContainerEntityListEntry:
960
975
  """
@@ -969,6 +984,23 @@ class ContainerEntityListEntry:
969
984
  return self.entity_version.entity
970
985
 
971
986
 
987
+ @dataclass(frozen=True, kw_only=True, slots=True)
988
+ class ContainerEntityRow:
989
+ """
990
+ [ 🛑 UNSTABLE ]
991
+ Used to specify the primary key of PublishableEntity and optional PublishableEntityVersion.
992
+
993
+ If version_pk is None (default), then the entity is considered "unpinned",
994
+ meaning that the latest version of the entity will be used.
995
+ """
996
+ entity_pk: int
997
+ version_pk: int | None = None
998
+
999
+ @property
1000
+ def pinned(self):
1001
+ return self.entity_pk and self.version_pk is not None
1002
+
1003
+
972
1004
  def get_entities_in_container(
973
1005
  container: Container,
974
1006
  *,
@@ -0,0 +1,17 @@
1
+ # Generated by Django 5.2 on 2025-04-08 10:50
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('oel_publishing', '0004_publishableentity_can_stand_alone'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterModelOptions(
14
+ name='entitylistrow',
15
+ options={'ordering': ['order_num']},
16
+ ),
17
+ ]
@@ -62,8 +62,7 @@ def create_unit_version(
62
62
  version_num: int,
63
63
  *,
64
64
  title: str,
65
- publishable_entities_pks: list[int],
66
- entity_version_pks: list[int | None],
65
+ entity_rows: list[publishing_api.ContainerEntityRow],
67
66
  created: datetime,
68
67
  created_by: int | None = None,
69
68
  ) -> UnitVersion:
@@ -75,11 +74,10 @@ def create_unit_version(
75
74
  `create_next_unit_version()` instead.
76
75
 
77
76
  Args:
78
- unit_pk: The unit ID.
77
+ unit: The unit object.
79
78
  version_num: The version number.
80
79
  title: The title.
81
- publishable_entities_pk: The publishable entities.
82
- entity: The entity.
80
+ entity_rows: child entities/versions
83
81
  created: The creation date.
84
82
  created_by: The user who created the unit.
85
83
  """
@@ -87,8 +85,7 @@ def create_unit_version(
87
85
  unit.pk,
88
86
  version_num,
89
87
  title=title,
90
- publishable_entities_pks=publishable_entities_pks,
91
- entity_version_pks=entity_version_pks,
88
+ entity_rows=entity_rows,
92
89
  created=created,
93
90
  created_by=created_by,
94
91
  container_version_cls=UnitVersion,
@@ -97,30 +94,34 @@ def create_unit_version(
97
94
 
98
95
  def _pub_entities_for_components(
99
96
  components: list[Component | ComponentVersion] | None,
100
- ) -> tuple[list[int], list[int | None]] | tuple[None, None]:
97
+ ) -> list[publishing_api.ContainerEntityRow] | None:
101
98
  """
102
99
  Helper method: given a list of Component | ComponentVersion, return the
103
- lists of publishable_entities_pks and entity_version_pks needed for the
104
- base container APIs.
100
+ list of ContainerEntityRows needed for the base container APIs.
105
101
 
106
102
  ComponentVersion is passed when we want to pin a specific version, otherwise
107
103
  Component is used for unpinned.
108
104
  """
109
105
  if components is None:
110
106
  # When these are None, that means don't change the entities in the list.
111
- return None, None
107
+ return None
112
108
  for c in components:
113
109
  if not isinstance(c, (Component, ComponentVersion)):
114
110
  raise TypeError("Unit components must be either Component or ComponentVersion.")
115
- publishable_entities_pks = [
116
- (c.publishable_entity_id if isinstance(c, Component) else c.component.publishable_entity_id)
111
+ return [
112
+ (
113
+ publishing_api.ContainerEntityRow(
114
+ entity_pk=c.publishable_entity_id,
115
+ version_pk=None,
116
+ ) if isinstance(c, Component)
117
+ else # isinstance(c, ComponentVersion)
118
+ publishing_api.ContainerEntityRow(
119
+ entity_pk=c.component.publishable_entity_id,
120
+ version_pk=c.pk,
121
+ )
122
+ )
117
123
  for c in components
118
124
  ]
119
- entity_version_pks = [
120
- (cv.pk if isinstance(cv, ComponentVersion) else None)
121
- for cv in components
122
- ]
123
- return publishable_entities_pks, entity_version_pks
124
125
 
125
126
 
126
127
  def create_next_unit_version(
@@ -143,12 +144,11 @@ def create_next_unit_version(
143
144
  created: The creation date.
144
145
  created_by: The user who created the unit.
145
146
  """
146
- publishable_entities_pks, entity_version_pks = _pub_entities_for_components(components)
147
+ entity_rows = _pub_entities_for_components(components)
147
148
  unit_version = publishing_api.create_next_container_version(
148
149
  unit.pk,
149
150
  title=title,
150
- publishable_entities_pks=publishable_entities_pks,
151
- entity_version_pks=entity_version_pks,
151
+ entity_rows=entity_rows,
152
152
  created=created,
153
153
  created_by=created_by,
154
154
  container_version_cls=UnitVersion,
@@ -177,7 +177,7 @@ def create_unit_and_version(
177
177
  created_by: The user who created the unit.
178
178
  can_stand_alone: Set to False when created as part of containers
179
179
  """
180
- publishable_entities_pks, entity_version_pks = _pub_entities_for_components(components)
180
+ entity_rows = _pub_entities_for_components(components)
181
181
  with atomic():
182
182
  unit = create_unit(
183
183
  learning_package_id,
@@ -190,8 +190,7 @@ def create_unit_and_version(
190
190
  unit,
191
191
  1,
192
192
  title=title,
193
- publishable_entities_pks=publishable_entities_pks or [],
194
- entity_version_pks=entity_version_pks or [],
193
+ entity_rows=entity_rows or [],
195
194
  created=created,
196
195
  created_by=created_by,
197
196
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openedx-learning
3
- Version: 0.19.2
3
+ Version: 0.22.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
@@ -10,6 +10,7 @@ Keywords: Python edx
10
10
  Classifier: Development Status :: 3 - Alpha
11
11
  Classifier: Framework :: Django
12
12
  Classifier: Framework :: Django :: 4.2
13
+ Classifier: Framework :: Django :: 5.2
13
14
  Classifier: Intended Audience :: Developers
14
15
  Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
15
16
  Classifier: Natural Language :: English
@@ -18,12 +19,12 @@ Classifier: Programming Language :: Python :: 3.11
18
19
  Classifier: Programming Language :: Python :: 3.12
19
20
  Requires-Python: >=3.11
20
21
  License-File: LICENSE.txt
21
- Requires-Dist: edx-drf-extensions
22
+ Requires-Dist: attrs
22
23
  Requires-Dist: rules<4.0
23
24
  Requires-Dist: djangorestframework<4.0
24
- Requires-Dist: Django<5.0
25
25
  Requires-Dist: celery
26
- Requires-Dist: attrs
26
+ Requires-Dist: Django
27
+ Requires-Dist: edx-drf-extensions
27
28
  Dynamic: author
28
29
  Dynamic: author-email
29
30
  Dynamic: classifier
@@ -1,4 +1,4 @@
1
- openedx_learning/__init__.py,sha256=z4XTeo1JUHOOsjfd2UHkU0k4ZI_v0ZxK3_TeHwEHpDY,69
1
+ openedx_learning/__init__.py,sha256=4P3K5TuZBereSAjMSdqtp6Cw_lXqcIivQmWqQf_ui_M,69
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=AuEegFTT3tAer_3zSxcxqLq3Yd_eilk-Hxt4ESYearA,970
@@ -7,7 +7,7 @@ openedx_learning/apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
7
7
  openedx_learning/apps/authoring/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  openedx_learning/apps/authoring/collections/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  openedx_learning/apps/authoring/collections/admin.py,sha256=f0hySjDMfIphVDEGkCSMIUHoEiqHRA7EE2NiO7lvL4g,1156
10
- openedx_learning/apps/authoring/collections/api.py,sha256=Lui-9mvlUjXSfCcaHrYJc2y2aJ1obJPCIp5hvxYwnkI,5894
10
+ openedx_learning/apps/authoring/collections/api.py,sha256=DaGg73iom7fN9fODajo8B2e9Jkx2syfLEVjip0cAzlQ,7747
11
11
  openedx_learning/apps/authoring/collections/apps.py,sha256=67imy9vnyXml4cfLNGFJhuZr2yx1q92-xz4ih3yJgNU,416
12
12
  openedx_learning/apps/authoring/collections/models.py,sha256=os8EKUVZ2IhMfFo2XN9tklByCUXphEtzAkKAOc0KyW8,8451
13
13
  openedx_learning/apps/authoring/collections/migrations/0001_initial.py,sha256=xcJoqMLROW9FTILYrz6UsyotVu9wBN5tRBwRoAWG6dg,1496
@@ -18,7 +18,7 @@ openedx_learning/apps/authoring/collections/migrations/0005_alter_collection_opt
18
18
  openedx_learning/apps/authoring/collections/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
19
  openedx_learning/apps/authoring/components/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
20
  openedx_learning/apps/authoring/components/admin.py,sha256=zfEpuBEySMYpUZzygaE2MDoI8SH-2H3xIL20YCSCMLo,4582
21
- openedx_learning/apps/authoring/components/api.py,sha256=PmCtJiyw0Vy4PGSRt9JCStJQv9OJ4FchtOyiY1sfJ-s,25254
21
+ openedx_learning/apps/authoring/components/api.py,sha256=nJZcGXN5gOz8EYX3XHCiDILowNRcm__RxTfTNt5rZZw,22780
22
22
  openedx_learning/apps/authoring/components/apps.py,sha256=Rcydv_FH-rVvuWIFqUezBNOh8DtrZHyozZM2yqX2JKE,778
23
23
  openedx_learning/apps/authoring/components/models.py,sha256=ttZzVnMZTa14-qudrLb4CFuCanEQJT8cuC_iVPH8XTA,10887
24
24
  openedx_learning/apps/authoring/components/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -37,12 +37,13 @@ openedx_learning/apps/authoring/contents/migrations/0001_initial.py,sha256=FtOTm
37
37
  openedx_learning/apps/authoring/contents/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
38
  openedx_learning/apps/authoring/publishing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
39
  openedx_learning/apps/authoring/publishing/admin.py,sha256=yPtqznDzlWHj0xpJ_FGFRviyftgPmuql5EoqKLeZQFo,4911
40
- openedx_learning/apps/authoring/publishing/api.py,sha256=tNpDYfQXwjpkp3acVXNItNXN4oCCekGSQa9M5TozXEs,41130
40
+ openedx_learning/apps/authoring/publishing/api.py,sha256=cTkqFZF3ecswV6tHC1dAbWSFnEmD8UYMumKMNlh55Dk,41609
41
41
  openedx_learning/apps/authoring/publishing/apps.py,sha256=v9PTe3YoICaYT9wfu268ZkVAlnZFvxi-DqYdbRi25bY,750
42
42
  openedx_learning/apps/authoring/publishing/migrations/0001_initial.py,sha256=wvekNV19YRSdxRmQaFnLSn_nCsQlHIucPDVMmgKf_OE,9272
43
43
  openedx_learning/apps/authoring/publishing/migrations/0002_alter_learningpackage_key_and_more.py,sha256=toI7qJhNukk6hirKfFx9EpqTpzF2O2Yq1VpFJusDn2M,806
44
44
  openedx_learning/apps/authoring/publishing/migrations/0003_containers.py,sha256=k5P1LFkjQT-42yyHE2f57dAMQKafLuJCTRaCBMKWcjc,2519
45
45
  openedx_learning/apps/authoring/publishing/migrations/0004_publishableentity_can_stand_alone.py,sha256=RapDZKcjTp5QGgob9DUaf-zl_pQgg-5nz8PZWoXMlVA,549
46
+ openedx_learning/apps/authoring/publishing/migrations/0005_alter_entitylistrow_options.py,sha256=Pp4O-VSI75n3UTZZW6CU4TOVIVc5lZvUg5YfMvZyrYM,377
46
47
  openedx_learning/apps/authoring/publishing/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
48
  openedx_learning/apps/authoring/publishing/models/__init__.py,sha256=Ou6Ks3qxEhel9uHLpzi77jlkb_IHc9_WIfqxOZJuqaE,1059
48
49
  openedx_learning/apps/authoring/publishing/models/container.py,sha256=GCUD3WTlgvgZSQOKcoOsfhNAfc5pz3Wbs9ClE9mhtB0,2594
@@ -52,7 +53,7 @@ openedx_learning/apps/authoring/publishing/models/learning_package.py,sha256=1fu
52
53
  openedx_learning/apps/authoring/publishing/models/publish_log.py,sha256=-uMj1X8umqRM259ucOqNt6POeKDpY5RLdA8XnWXt7Go,4109
53
54
  openedx_learning/apps/authoring/publishing/models/publishable_entity.py,sha256=-6Fde_sYqaq1AUC4sw4Rf1X9Hh4jun2Py7Xhsl4VWuY,25419
54
55
  openedx_learning/apps/authoring/units/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
- openedx_learning/apps/authoring/units/api.py,sha256=nhAATIC0j4We1-3K5yr9FvNr7Dyg-LVHyxfa1ik6IMw,10106
56
+ openedx_learning/apps/authoring/units/api.py,sha256=KJA7Bh7IBE22E6cMSZNp6pamjjA1Vf8EhHw0rvQtlPM,9797
56
57
  openedx_learning/apps/authoring/units/apps.py,sha256=cIzphjDw5sjIZ3NLE911N7IMUa8JQSXMReNl03uI7jg,701
57
58
  openedx_learning/apps/authoring/units/models.py,sha256=eTOwFWC9coQLf0ovx08Mj7zi8mPAWCw9QOznybajRk0,1418
58
59
  openedx_learning/apps/authoring/units/migrations/0001_initial.py,sha256=qM_0JGffxECVgXzncHXfgSE-g8u3L3a14R0M1Bnj_Ys,1129
@@ -70,7 +71,7 @@ openedx_learning/lib/fields.py,sha256=eiGoXMPhRuq25EH2qf6BAODshAQE3DBVdIYAMIUAXW
70
71
  openedx_learning/lib/managers.py,sha256=-Q3gxalSqyPZ9Im4DTROW5tF8wVTZLlmfTe62_xmowY,1643
71
72
  openedx_learning/lib/test_utils.py,sha256=g3KLuepIZbaDBCsaj9711YuqyUx7LD4gXDcfNC-mWdc,527
72
73
  openedx_learning/lib/validators.py,sha256=iqEdEAvFV2tC7Ecssx69kjecpdU8nE87AlDJYrqrsnc,404
73
- openedx_learning-0.19.2.dist-info/licenses/LICENSE.txt,sha256=QTW2QN7q3XszgUAXm9Dzgtu5LXYKbR1SGnqMa7ufEuY,35139
74
+ openedx_learning-0.22.0.dist-info/licenses/LICENSE.txt,sha256=QTW2QN7q3XszgUAXm9Dzgtu5LXYKbR1SGnqMa7ufEuY,35139
74
75
  openedx_tagging/__init__.py,sha256=V9N8M7f9LYlAbA_DdPUsHzTnWjYRXKGa5qHw9P1JnNI,30
75
76
  openedx_tagging/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
76
77
  openedx_tagging/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -126,7 +127,7 @@ openedx_tagging/core/tagging/rest_api/v1/serializers.py,sha256=0HQD_Jrf6-YpocYfz
126
127
  openedx_tagging/core/tagging/rest_api/v1/urls.py,sha256=dNUKCtUCx_YzrwlbEbpDfjGVQbb2QdJ1VuJCkladj6E,752
127
128
  openedx_tagging/core/tagging/rest_api/v1/views.py,sha256=Hf92cy-tE767DE9FgsZcPKiCYrf5ihfETz8qGKBnuiU,36278
128
129
  openedx_tagging/core/tagging/rest_api/v1/views_import.py,sha256=kbHUPe5A6WaaJ3J1lFIcYCt876ecLNQfd19m7YYub6c,1470
129
- openedx_learning-0.19.2.dist-info/METADATA,sha256=ThUAT_bbUaS5Xzmsb56SN-YSkYmBKvA3Yg0npDMQReE,8997
130
- openedx_learning-0.19.2.dist-info/WHEEL,sha256=aoLN90hLOL0c0qxXMxWYUM3HA3WmFGZQqEJHX1V_OJE,109
131
- openedx_learning-0.19.2.dist-info/top_level.txt,sha256=IYFbr5mgiEHd-LOtZmXj3q3a0bkGK1M9LY7GXgnfi4M,33
132
- openedx_learning-0.19.2.dist-info/RECORD,,
130
+ openedx_learning-0.22.0.dist-info/METADATA,sha256=2MuvaSMI2NZxb_0DGJv2VEoxq2FH4DiYJ_wnBwmIPa8,9032
131
+ openedx_learning-0.22.0.dist-info/WHEEL,sha256=MAQBAzGbXNI3bUmkDsiV_duv8i-gcdnLzw7cfUFwqhU,109
132
+ openedx_learning-0.22.0.dist-info/top_level.txt,sha256=IYFbr5mgiEHd-LOtZmXj3q3a0bkGK1M9LY7GXgnfi4M,33
133
+ openedx_learning-0.22.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (77.0.3)
2
+ Generator: setuptools (78.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py2-none-any
5
5
  Tag: py3-none-any