openedx-learning 0.9.3__py2.py3-none-any.whl → 0.10.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.
Files changed (44) hide show
  1. openedx_learning/__init__.py +1 -1
  2. openedx_learning/api/authoring.py +14 -0
  3. openedx_learning/api/authoring_models.py +13 -0
  4. openedx_learning/{core → apps/authoring}/components/api.py +21 -0
  5. openedx_learning/{core → apps/authoring}/components/apps.py +2 -2
  6. openedx_learning/{core → apps/authoring}/components/models.py +9 -2
  7. openedx_learning/{core → apps/authoring}/contents/api.py +13 -1
  8. openedx_learning/{core → apps/authoring}/contents/apps.py +2 -2
  9. openedx_learning/{core → apps/authoring}/contents/models.py +7 -2
  10. openedx_learning/{core → apps/authoring}/publishing/api.py +35 -7
  11. openedx_learning/{core → apps/authoring}/publishing/apps.py +2 -2
  12. openedx_learning/{core → apps/authoring}/publishing/model_mixins.py +6 -0
  13. openedx_learning/{core → apps/authoring}/publishing/models.py +10 -0
  14. openedx_learning/contrib/media_server/views.py +1 -1
  15. {openedx_learning-0.9.3.dist-info → openedx_learning-0.10.0.dist-info}/METADATA +15 -20
  16. {openedx_learning-0.9.3.dist-info → openedx_learning-0.10.0.dist-info}/RECORD +40 -42
  17. openedx_tagging/core/tagging/import_export/actions.py +4 -8
  18. openedx_tagging/core/tagging/import_export/api.py +34 -4
  19. openedx_tagging/core/tagging/import_export/import_plan.py +6 -2
  20. openedx_tagging/core/tagging/models/import_export.py +9 -6
  21. openedx_learning/rest_api/apps.py +0 -14
  22. openedx_learning/rest_api/urls.py +0 -6
  23. openedx_learning/rest_api/v1/components.py +0 -30
  24. openedx_learning/rest_api/v1/urls.py +0 -10
  25. /openedx_learning/{core → api}/__init__.py +0 -0
  26. /openedx_learning/{core/components → apps}/__init__.py +0 -0
  27. /openedx_learning/{core/components/migrations → apps/authoring}/__init__.py +0 -0
  28. /openedx_learning/{core/contents → apps/authoring/components}/__init__.py +0 -0
  29. /openedx_learning/{core → apps/authoring}/components/admin.py +0 -0
  30. /openedx_learning/{core → apps/authoring}/components/migrations/0001_initial.py +0 -0
  31. /openedx_learning/{core → apps/authoring}/components/migrations/0002_alter_componentversioncontent_key.py +0 -0
  32. /openedx_learning/{core/contents → apps/authoring/components}/migrations/__init__.py +0 -0
  33. /openedx_learning/{core/publishing → apps/authoring/contents}/__init__.py +0 -0
  34. /openedx_learning/{core → apps/authoring}/contents/admin.py +0 -0
  35. /openedx_learning/{core → apps/authoring}/contents/migrations/0001_initial.py +0 -0
  36. /openedx_learning/{core/publishing → apps/authoring/contents}/migrations/__init__.py +0 -0
  37. /openedx_learning/{rest_api → apps/authoring/publishing}/__init__.py +0 -0
  38. /openedx_learning/{core → apps/authoring}/publishing/admin.py +0 -0
  39. /openedx_learning/{core → apps/authoring}/publishing/migrations/0001_initial.py +0 -0
  40. /openedx_learning/{core → apps/authoring}/publishing/migrations/0002_alter_learningpackage_key_and_more.py +0 -0
  41. /openedx_learning/{rest_api/v1 → apps/authoring/publishing/migrations}/__init__.py +0 -0
  42. {openedx_learning-0.9.3.dist-info → openedx_learning-0.10.0.dist-info}/LICENSE.txt +0 -0
  43. {openedx_learning-0.9.3.dist-info → openedx_learning-0.10.0.dist-info}/WHEEL +0 -0
  44. {openedx_learning-0.9.3.dist-info → openedx_learning-0.10.0.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,4 @@
1
1
  """
2
2
  Open edX Learning ("Learning Core").
3
3
  """
4
- __version__ = "0.9.3"
4
+ __version__ = "0.10.0"
@@ -0,0 +1,14 @@
1
+ """
2
+ This is the public API for content authoring in Learning Core.
3
+
4
+ This is the single ``api`` module that code outside of the
5
+ ``openedx_learning.apps.authoring.*`` package should import from. It will
6
+ re-export the public functions from all api.py modules of all authoring apps. It
7
+ may also implement its own convenience APIs that wrap calls to multiple app
8
+ APIs.
9
+ """
10
+ # These wildcard imports are okay because these api modules declare __all__.
11
+ # pylint: disable=wildcard-import
12
+ from ..apps.authoring.components.api import *
13
+ from ..apps.authoring.contents.api import *
14
+ from ..apps.authoring.publishing.api import *
@@ -0,0 +1,13 @@
1
+ """
2
+ This is where we expose a small handful of models and model mixins that we want
3
+ callers to extend or make foreign keys to. Callers importing this module should
4
+ never instantiate any of the models themselves–there are API functions in
5
+ authoring.py to create and modify data models in a way that keeps those models
6
+ consistent.
7
+ """
8
+ # These wildcard imports are okay because these modules declare __all__.
9
+ # pylint: disable=wildcard-import
10
+ from ..apps.authoring.components.models import *
11
+ from ..apps.authoring.contents.models import *
12
+ from ..apps.authoring.publishing.model_mixins import *
13
+ from ..apps.authoring.publishing.models import *
@@ -21,6 +21,24 @@ from django.db.transaction import atomic
21
21
  from ..publishing import api as publishing_api
22
22
  from .models import Component, ComponentType, ComponentVersion, ComponentVersionContent
23
23
 
24
+ # The public API that will be re-exported by openedx_learning.apps.authoring.api
25
+ # is listed in the __all__ entries below. Internal helper functions that are
26
+ # private to this module should start with an underscore. If a function does not
27
+ # start with an underscore AND it is not in __all__, that function is considered
28
+ # to be callable only by other apps in the authoring package.
29
+ __all__ = [
30
+ "get_or_create_component_type",
31
+ "create_component",
32
+ "create_component_version",
33
+ "create_next_version",
34
+ "create_component_and_version",
35
+ "get_component",
36
+ "get_component_by_key",
37
+ "component_exists_by_key",
38
+ "get_components",
39
+ "create_component_version_content",
40
+ ]
41
+
24
42
 
25
43
  def get_or_create_component_type(namespace: str, name: str) -> ComponentType:
26
44
  """
@@ -308,6 +326,9 @@ def look_up_component_version_content(
308
326
 
309
327
  Can raise a django.core.exceptions.ObjectDoesNotExist error if there is no
310
328
  matching ComponentVersionContent.
329
+
330
+ This API call was only used in our proof-of-concept assets media server, and
331
+ I don't know if we wantto make it a part of the public interface.
311
332
  """
312
333
  queries = (
313
334
  Q(component_version__component__learning_package__key=learning_package_key)
@@ -9,8 +9,8 @@ class ComponentsConfig(AppConfig):
9
9
  Configuration for the Components Django application.
10
10
  """
11
11
 
12
- name = "openedx_learning.core.components"
13
- verbose_name = "Learning Core: Components"
12
+ name = "openedx_learning.apps.authoring.components"
13
+ verbose_name = "Learning Core > Authoring > Components"
14
14
  default_auto_field = "django.db.models.BigAutoField"
15
15
  label = "oel_components"
16
16
 
@@ -19,12 +19,19 @@ from __future__ import annotations
19
19
 
20
20
  from django.db import models
21
21
 
22
- from ...lib.fields import case_sensitive_char_field, immutable_uuid_field, key_field
23
- from ...lib.managers import WithRelationsManager
22
+ from ....lib.fields import case_sensitive_char_field, immutable_uuid_field, key_field
23
+ from ....lib.managers import WithRelationsManager
24
24
  from ..contents.models import Content
25
25
  from ..publishing.model_mixins import PublishableEntityMixin, PublishableEntityVersionMixin
26
26
  from ..publishing.models import LearningPackage
27
27
 
28
+ __all__ = [
29
+ "ComponentType",
30
+ "Component",
31
+ "ComponentVersion",
32
+ "ComponentVersionContent",
33
+ ]
34
+
28
35
 
29
36
  class ComponentType(models.Model):
30
37
  """
@@ -11,9 +11,21 @@ from datetime import datetime
11
11
  from django.core.files.base import ContentFile
12
12
  from django.db.transaction import atomic
13
13
 
14
- from ...lib.fields import create_hash_digest
14
+ from ....lib.fields import create_hash_digest
15
15
  from .models import Content, MediaType
16
16
 
17
+ # The public API that will be re-exported by openedx_learning.apps.authoring.api
18
+ # is listed in the __all__ entries below. Internal helper functions that are
19
+ # private to this module should start with an underscore. If a function does not
20
+ # start with an underscore AND it is not in __all__, that function is considered
21
+ # to be callable only by other apps in the authoring package.
22
+ __all__ = [
23
+ "get_or_create_media_type",
24
+ "get_content",
25
+ "get_or_create_text_content",
26
+ "get_or_create_file_content",
27
+ ]
28
+
17
29
 
18
30
  def get_or_create_media_type(mime_type: str) -> MediaType:
19
31
  """
@@ -9,7 +9,7 @@ class ContentsConfig(AppConfig):
9
9
  Configuration for the Contents Django application.
10
10
  """
11
11
 
12
- name = "openedx_learning.core.contents"
13
- verbose_name = "Learning Core: Contents"
12
+ name = "openedx_learning.apps.authoring.contents"
13
+ verbose_name = "Learning Core > Authoring > Contents"
14
14
  default_auto_field = "django.db.models.BigAutoField"
15
15
  label = "oel_contents"
@@ -13,10 +13,15 @@ from django.core.files.storage import Storage, default_storage
13
13
  from django.core.validators import MaxValueValidator
14
14
  from django.db import models
15
15
 
16
- from ...lib.fields import MultiCollationTextField, case_insensitive_char_field, hash_field, manual_date_time_field
17
- from ...lib.managers import WithRelationsManager
16
+ from ....lib.fields import MultiCollationTextField, case_insensitive_char_field, hash_field, manual_date_time_field
17
+ from ....lib.managers import WithRelationsManager
18
18
  from ..publishing.models import LearningPackage
19
19
 
20
+ __all__ = [
21
+ "MediaType",
22
+ "Content",
23
+ ]
24
+
20
25
 
21
26
  def get_storage() -> Storage:
22
27
  """
@@ -23,6 +23,34 @@ from .models import (
23
23
  PublishLogRecord,
24
24
  )
25
25
 
26
+ # The public API that will be re-exported by openedx_learning.apps.authoring.api
27
+ # is listed in the __all__ entries below. Internal helper functions that are
28
+ # private to this module should start with an underscore. If a function does not
29
+ # start with an underscore AND it is not in __all__, that function is considered
30
+ # to be callable only by other apps in the authoring package.
31
+ __all__ = [
32
+ "get_learning_package",
33
+ "get_learning_package_by_key",
34
+ "create_learning_package",
35
+ "update_learning_package",
36
+ "learning_package_exists",
37
+ "create_publishable_entity",
38
+ "create_publishable_entity_version",
39
+ "get_publishable_entity",
40
+ "get_publishable_entity_by_key",
41
+ "get_last_publish",
42
+ "get_all_drafts",
43
+ "get_entities_with_unpublished_changes",
44
+ "get_entities_with_unpublished_deletes",
45
+ "publish_all_drafts",
46
+ "get_draft_version",
47
+ "get_published_version",
48
+ "set_draft_version",
49
+ "soft_delete_draft",
50
+ "reset_drafts_to_published",
51
+ "register_content_models",
52
+ ]
53
+
26
54
 
27
55
  def get_learning_package(learning_package_id: int, /) -> LearningPackage:
28
56
  """
@@ -105,6 +133,13 @@ def update_learning_package(
105
133
  return lp
106
134
 
107
135
 
136
+ def learning_package_exists(key: str) -> bool:
137
+ """
138
+ Check whether a LearningPackage with a particular key exists.
139
+ """
140
+ return LearningPackage.objects.filter(key=key).exists()
141
+
142
+
108
143
  def create_publishable_entity(
109
144
  learning_package_id: int,
110
145
  /,
@@ -167,13 +202,6 @@ def get_publishable_entity_by_key(learning_package_id, /, key) -> PublishableEnt
167
202
  )
168
203
 
169
204
 
170
- def learning_package_exists(key: str) -> bool:
171
- """
172
- Check whether a LearningPackage with a particular key exists.
173
- """
174
- return LearningPackage.objects.filter(key=key).exists()
175
-
176
-
177
205
  def get_last_publish(learning_package_id: int, /) -> PublishLog | None:
178
206
  return PublishLog.objects \
179
207
  .filter(learning_package_id=learning_package_id) \
@@ -10,7 +10,7 @@ class PublishingConfig(AppConfig):
10
10
  Configuration for the publishing Django application.
11
11
  """
12
12
 
13
- name = "openedx_learning.core.publishing"
14
- verbose_name = "Learning Core: Publishing"
13
+ name = "openedx_learning.apps.authoring.publishing"
14
+ verbose_name = "Learning Core > Authoring > Publishing"
15
15
  default_auto_field = "django.db.models.BigAutoField"
16
16
  label = "oel_publishing"
@@ -11,6 +11,12 @@ from django.db.models.query import QuerySet
11
11
 
12
12
  from .models import PublishableEntity, PublishableEntityVersion
13
13
 
14
+ __all__ = [
15
+ "PublishableEntityMixin",
16
+ "PublishableEntityVersionMixin",
17
+ "PublishableContentModelRegistry",
18
+ ]
19
+
14
20
 
15
21
  class PublishableEntityMixin(models.Model):
16
22
  """
@@ -23,6 +23,16 @@ from openedx_learning.lib.fields import (
23
23
  manual_date_time_field,
24
24
  )
25
25
 
26
+ __all__ = [
27
+ "LearningPackage",
28
+ "PublishableEntity",
29
+ "PublishableEntityVersion",
30
+ "Draft",
31
+ "PublishLog",
32
+ "PublishLogRecord",
33
+ "Published",
34
+ ]
35
+
26
36
 
27
37
  class LearningPackage(models.Model): # type: ignore[django-manager-missing]
28
38
  """
@@ -8,7 +8,7 @@ from pathlib import Path
8
8
  from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
9
9
  from django.http import FileResponse, Http404
10
10
 
11
- from openedx_learning.core.components.api import look_up_component_version_content
11
+ from openedx_learning.apps.authoring.components.api import look_up_component_version_content
12
12
 
13
13
 
14
14
  def component_asset(
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: openedx-learning
3
- Version: 0.9.3
4
- Summary: An experiment.
3
+ Version: 0.10.0
4
+ Summary: Open edX Learning Core and Tagging.
5
5
  Home-page: https://github.com/openedx/openedx-learning
6
6
  Author: David Ormsbee
7
7
  Author-email: dave@tcril.org
@@ -19,26 +19,23 @@ Classifier: Programming Language :: Python :: 3.11
19
19
  Classifier: Programming Language :: Python :: 3.12
20
20
  Requires-Python: >=3.8
21
21
  License-File: LICENSE.txt
22
- Requires-Dist: djangorestframework <4.0
22
+ Requires-Dist: rules <4.0
23
23
  Requires-Dist: Django <5.0
24
24
  Requires-Dist: edx-drf-extensions
25
25
  Requires-Dist: attrs
26
26
  Requires-Dist: celery
27
- Requires-Dist: rules <4.0
27
+ Requires-Dist: djangorestframework <4.0
28
28
 
29
- openedx-learning
30
- =============================
29
+ Open edX Learning Core (and Tagging)
30
+ ====================================
31
31
 
32
32
  |pypi-badge| |ci-badge| |codecov-badge| |doc-badge| |pyversions-badge|
33
33
  |license-badge|
34
34
 
35
- This is experimentation/prototyping and not in any way production ready!
36
- ------------------------------------------------------------------------
37
-
38
35
  Overview
39
36
  --------
40
37
 
41
- The Open edX Learning repository holds Django apps that represent core learning concepts and data models that have been extracted from edx-platform.
38
+ The ``openedx_learning`` package holds Django apps that represent core learning concepts and data models that have been extracted from edx-platform. At the moment, this repo also contains the ``openedx_tagging`` package, but this will likely be moved out in the future.
42
39
 
43
40
  Motivation
44
41
  ----------
@@ -54,18 +51,16 @@ Parts
54
51
  ~~~~~
55
52
 
56
53
  * ``openedx_learning.lib`` is for shared utilities, and may include things like custom field types, plugin registration code, etc.
57
- * ``openedx_learning.core`` contains our Core Django apps, where foundational data structures and APIs will live.
54
+ * ``openedx_learning.apps`` contains our Learning Core Django apps, where foundational data structures and APIs will live. The first of these is ``authoring``, which holds apps related to the editing and publishing of learning content.
58
55
  * ``openedx_tagging.core`` contains the core Tagging app, which provides data structures and apis for tagging Open edX objects.
59
56
 
60
- App Dependencies
61
- ~~~~~~~~~~~~~~~~
57
+ Learning Core Package Dependencies
58
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
62
59
 
63
- Anything can import from ``openedx_learning.lib``.
60
+ Learning Core code should never import from ``edx-platform``.
64
61
 
65
- Core apps can import from each other, but cannot import from other apps outside of core. For those apps:
62
+ We want to be very strict about dependency management internally as well. Please read the `.importlinter config file <.importlinter>`_ file and the `Python API Conventions ADR <docs/decisions/0016-python-public-api-conventions>`_ for more details.
66
63
 
67
- * ``learning_publishing`` has no dependencies. All the other apps depend on it.
68
- * ``learning_composition`` and ``learning_navigation`` both depend on ``learning_partitioning``
69
64
 
70
65
  Model Conventions
71
66
  ~~~~~~~~~~~~~~~~~
@@ -74,7 +69,7 @@ We have a few different identifier types in the schema, and we try to avoid ``_i
74
69
 
75
70
  * ``id`` is the auto-generated, internal row ID and primary key. This never changes. Data models should make foreign keys to this field, as per Django convention.
76
71
  * ``uuid`` is a randomly generated UUID4. This is the stable way to refer to a row/resource from an external service. This never changes. This is separate from ``id`` mostly because there are performance penalties when using UUIDs as primary keys with MySQL.
77
- * ``key`` is intended to be a case-sensitive, alphanumeric key, which holds some meaning to library clients. This is usually stable, but can be changed, depending on the business logic of the client. The apps in this repo should make no assumptions about it being stable. It can be used as a suffix.
72
+ * ``key`` is intended to be a case-sensitive, alphanumeric key, which holds some meaning to library clients. This is usually stable, but can be changed, depending on the business logic of the client. The apps in this repo should make no assumptions about it being stable. It can be used as a suffix. Since ``key`` is a reserved name on certain database systems, the database field is ``_key``.
78
73
  * ``num`` is like ``key``, but for use when it's strictly numeric. It can also be used as a suffix.
79
74
 
80
75
 
@@ -89,7 +84,7 @@ The structure of this repo follows [OEP-0049](https://open-edx-proposals.readthe
89
84
  Code Overview
90
85
  -------------
91
86
 
92
- The ``openedx_learning.apps`` package contains all our Django applications. All apps are named with a ``learning_`` prefix to better avoid name conflicts, because Django's app namespace is flat. Apps will adhere to `OEP-0049: Django App Patterns <https://open-edx-proposals.readthedocs.io/en/latest/architectural-decisions/oep-0049-django-app-patterns.html>`_.
87
+ The ``openedx_learning.apps`` package contains all our Django applications.
93
88
 
94
89
  Development Workflow
95
90
  --------------------
@@ -103,7 +98,7 @@ One Time Setup
103
98
  cd openedx-learning
104
99
 
105
100
  # Set up a virtualenv using virtualenvwrapper with the same name as the repo and activate it
106
- mkvirtualenv -p python3.8 openedx-learning
101
+ mkvirtualenv -p python3.11 openedx-learning
107
102
 
108
103
 
109
104
  Every time you develop something in this repo
@@ -1,35 +1,39 @@
1
- openedx_learning/__init__.py,sha256=EfjfyOQVIjOVYMNlUo9QxJoi0su5KwN9ncFBkCrXPAk,67
1
+ openedx_learning/__init__.py,sha256=DnOb5RNB5dogIDUc96QphWmX4TqWV73iB0Ikmixury4,68
2
2
  openedx_learning/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ openedx_learning/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ openedx_learning/api/authoring.py,sha256=HPfDBTr4Sxf_GciObkv_7vaUiC9P1GdGEMAZgCFQv_w,615
5
+ openedx_learning/api/authoring_models.py,sha256=jcYkX6-MYiNf-e4BnzSDEc3qS17dg8UVXyX1bxeyjR8,642
6
+ openedx_learning/apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ openedx_learning/apps/authoring/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ openedx_learning/apps/authoring/components/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ openedx_learning/apps/authoring/components/admin.py,sha256=QE7e76C6X2V1AQPxQe6SayQPfCMmbs4RZPEPIGcvTWw,4672
10
+ openedx_learning/apps/authoring/components/api.py,sha256=1A0xcpW4ciVs_vA-Xd16k4YfFBXhNYjISQyamyq03Uc,13191
11
+ openedx_learning/apps/authoring/components/apps.py,sha256=YoYPsI9gcleA3uEs8CiLIrjUncRMo2DKbYt4mDfzePg,770
12
+ openedx_learning/apps/authoring/components/models.py,sha256=j9k0-FmOkySxYbJ-frMuCR82zrPgsu9K3aCk2IKRdFs,13280
13
+ openedx_learning/apps/authoring/components/migrations/0001_initial.py,sha256=446LkJSFeK8J_-l-bxakZ_BVx_CiJIllGcBYqWcEenA,4664
14
+ openedx_learning/apps/authoring/components/migrations/0002_alter_componentversioncontent_key.py,sha256=98724dtucRjJCRyLt5p45qXYb2d6-ouVGp7PB6zTG6E,539
15
+ openedx_learning/apps/authoring/components/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ openedx_learning/apps/authoring/contents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ openedx_learning/apps/authoring/contents/admin.py,sha256=4ILH_cEiAXKUlfPVwJJZeh5yupgF3v7kf-xJ6ZTTFDE,1174
18
+ openedx_learning/apps/authoring/contents/api.py,sha256=QRHCZNKLvKqvl1S4j8lfUjVsvilcWetuCQ_GO_TXYGk,6070
19
+ openedx_learning/apps/authoring/contents/apps.py,sha256=EEUZEnww7TcYcyxMovZthG2muNxd7j7nxBIf21gKrp4,398
20
+ openedx_learning/apps/authoring/contents/models.py,sha256=4XzHqQ3Fgz4lt2LfGBXAViQ4q_jT9thHj2vpayszspA,15192
21
+ openedx_learning/apps/authoring/contents/migrations/0001_initial.py,sha256=FtOTmIGX2KHpjw-PHbfRjxkFEomI5CEDhNKCZ7IpFeE,3060
22
+ openedx_learning/apps/authoring/contents/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
+ openedx_learning/apps/authoring/publishing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
+ openedx_learning/apps/authoring/publishing/admin.py,sha256=F-0QlVQmuovLIF258XK_vKJdOnn7lLa_0A5veE72TKc,4830
25
+ openedx_learning/apps/authoring/publishing/api.py,sha256=kZ5tayJzScmaKUmpuWr-EBQ62nDDCZb4713awMpSzrM,15278
26
+ openedx_learning/apps/authoring/publishing/apps.py,sha256=jUfd78xvXaZg3dwkqXihatbeajJGm3Uz1rJpuyd-3g0,402
27
+ openedx_learning/apps/authoring/publishing/model_mixins.py,sha256=cau70b3boi2J3DvkC0UE-sd7Nyjk9TLHJ47-nq89ELQ,13533
28
+ openedx_learning/apps/authoring/publishing/models.py,sha256=ImMAujPDc2-CECZw_yvVlUOdtGYwmt99TJ2r1HJkkV8,20488
29
+ openedx_learning/apps/authoring/publishing/migrations/0001_initial.py,sha256=wvekNV19YRSdxRmQaFnLSn_nCsQlHIucPDVMmgKf_OE,9272
30
+ openedx_learning/apps/authoring/publishing/migrations/0002_alter_learningpackage_key_and_more.py,sha256=toI7qJhNukk6hirKfFx9EpqTpzF2O2Yq1VpFJusDn2M,806
31
+ openedx_learning/apps/authoring/publishing/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
32
  openedx_learning/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
33
  openedx_learning/contrib/media_server/__init__.py,sha256=iYijWFCl5RNR9omSu22kMl49EfponoqXBqXr0HMp4QI,56
5
34
  openedx_learning/contrib/media_server/apps.py,sha256=FPT0rsUFtPyhFpWKjSI1e_s58wU0IbDyaAW_66V6sY4,816
6
35
  openedx_learning/contrib/media_server/urls.py,sha256=newNjV41sM9A9Oy_rgnZSXdkTFxSHiupIiAsVIGE2CE,365
7
- openedx_learning/contrib/media_server/views.py,sha256=V-eq0SFi5815lkGdRz8dye4fF3dHzO5lVfm78gfG3mg,1537
8
- openedx_learning/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- openedx_learning/core/components/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- openedx_learning/core/components/admin.py,sha256=QE7e76C6X2V1AQPxQe6SayQPfCMmbs4RZPEPIGcvTWw,4672
11
- openedx_learning/core/components/api.py,sha256=2ux1s9Bpfvs5sgTViHLagMb1IHpMo_vRR-MbrbXh0_U,12345
12
- openedx_learning/core/components/apps.py,sha256=invLeAY0B-d_Ac9xu_b5ZWeb7v5NJe_-938BO9U2-jo,747
13
- openedx_learning/core/components/models.py,sha256=duVLdkTFnM7ixsqXQPlvmqmgf32hYyXdeRw1W7MdOpg,13170
14
- openedx_learning/core/components/migrations/0001_initial.py,sha256=446LkJSFeK8J_-l-bxakZ_BVx_CiJIllGcBYqWcEenA,4664
15
- openedx_learning/core/components/migrations/0002_alter_componentversioncontent_key.py,sha256=98724dtucRjJCRyLt5p45qXYb2d6-ouVGp7PB6zTG6E,539
16
- openedx_learning/core/components/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
- openedx_learning/core/contents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- openedx_learning/core/contents/admin.py,sha256=4ILH_cEiAXKUlfPVwJJZeh5yupgF3v7kf-xJ6ZTTFDE,1174
19
- openedx_learning/core/contents/api.py,sha256=09159G0e_r7glDvi-G8c5k6ads2LmhmDLHnHeTSpotI,5553
20
- openedx_learning/core/contents/apps.py,sha256=6O04ikajbc4FsSdzx7yEl0cdQpEHpK1LdE471vygdII,375
21
- openedx_learning/core/contents/models.py,sha256=HInsxr3Bnl48_0fbdoDKnGmrpIxTFTpoQTUg6_opdB4,15143
22
- openedx_learning/core/contents/migrations/0001_initial.py,sha256=FtOTmIGX2KHpjw-PHbfRjxkFEomI5CEDhNKCZ7IpFeE,3060
23
- openedx_learning/core/contents/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
- openedx_learning/core/publishing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
- openedx_learning/core/publishing/admin.py,sha256=F-0QlVQmuovLIF258XK_vKJdOnn7lLa_0A5veE72TKc,4830
26
- openedx_learning/core/publishing/api.py,sha256=pfBePrmP0jnU5YTt4x7qhT37RN02P95gw2-xQNA_e9A,14254
27
- openedx_learning/core/publishing/apps.py,sha256=skY84raPW6gvrb5VPzhrLR2_gIHTUG67tMhSO7umVN4,379
28
- openedx_learning/core/publishing/model_mixins.py,sha256=Y6hGh0MKVOx0dUyNL0YkLL9ErUnYI2Nv2ZGMhc3CMng,13412
29
- openedx_learning/core/publishing/models.py,sha256=MGezxLoKFqHYnZccknqWyuNYrgiqu9eGDc4H9yIYzrg,20321
30
- openedx_learning/core/publishing/migrations/0001_initial.py,sha256=wvekNV19YRSdxRmQaFnLSn_nCsQlHIucPDVMmgKf_OE,9272
31
- openedx_learning/core/publishing/migrations/0002_alter_learningpackage_key_and_more.py,sha256=toI7qJhNukk6hirKfFx9EpqTpzF2O2Yq1VpFJusDn2M,806
32
- openedx_learning/core/publishing/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
+ openedx_learning/contrib/media_server/views.py,sha256=A4umcQr9xU5l-7dCSUX9jP9-gtwf4LGjoMn7NlsdLsk,1547
33
37
  openedx_learning/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
38
  openedx_learning/lib/admin_utils.py,sha256=5z9NrXxmT5j8azx9u1t0AgxV5PIDTc2jPyM5z5yW8cw,4021
35
39
  openedx_learning/lib/cache.py,sha256=ppT36KiPLdsAF3GfZCF0IdiHodckd2gLiF1sNhjSJuk,958
@@ -38,12 +42,6 @@ openedx_learning/lib/fields.py,sha256=eiGoXMPhRuq25EH2qf6BAODshAQE3DBVdIYAMIUAXW
38
42
  openedx_learning/lib/managers.py,sha256=ofcUxHS2wsJSFP4CB7OCkN6JiWFxNlIyNh_VDgFtgug,1538
39
43
  openedx_learning/lib/test_utils.py,sha256=g3KLuepIZbaDBCsaj9711YuqyUx7LD4gXDcfNC-mWdc,527
40
44
  openedx_learning/lib/validators.py,sha256=98UldFhFxd77zF1VxzQcOPt0blh64gMZDv-d_uQ67mg,396
41
- openedx_learning/rest_api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
42
- openedx_learning/rest_api/apps.py,sha256=aZpox3cdQEKl0idiFyyjrtwWXuoxR0HRm9BQjr6Lz-8,346
43
- openedx_learning/rest_api/urls.py,sha256=r73z_2aiFUfDYPZnSADTwVXNjJpvvkPHRscvtdLn1tM,157
44
- openedx_learning/rest_api/v1/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
45
- openedx_learning/rest_api/v1/components.py,sha256=OYNWqarNtVmiKunIMlZWNGVCeZ5ATco2ZTuxPR-BS9I,757
46
- openedx_learning/rest_api/v1/urls.py,sha256=g0nYjq6qWjd08TUzsb-tQ_ZFRmQsxCIQvQRNa6oCAns,256
47
45
  openedx_tagging/__init__.py,sha256=V9N8M7f9LYlAbA_DdPUsHzTnWjYRXKGa5qHw9P1JnNI,30
48
46
  openedx_tagging/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
47
  openedx_tagging/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -55,10 +53,10 @@ openedx_tagging/core/tagging/data.py,sha256=421EvmDzdM7H523dBVQk4J0W_UwTT4U5syqP
55
53
  openedx_tagging/core/tagging/rules.py,sha256=Gzw2RCQxoAv2PpOwOWgpD17XoZfowlFnNgQqYn59q_g,6715
56
54
  openedx_tagging/core/tagging/urls.py,sha256=-0Nzmh0mlF9-GgEuocwBdSJn6n8EINnxR4m4pmPou44,159
57
55
  openedx_tagging/core/tagging/import_export/__init__.py,sha256=q5K4JalFQlJxAFUFyqhLY5zQtAskDnRM1H_aVuP_E3Q,83
58
- openedx_tagging/core/tagging/import_export/actions.py,sha256=1nsGoa7eSfMF4gB-GqNRlTkl22z3Lqo7y8ay1rnNy0E,13368
59
- openedx_tagging/core/tagging/import_export/api.py,sha256=Eio1KeVZB5lsRwZYLR-ECtJfIoPZSyV6ghuUAUSpWNE,6173
56
+ openedx_tagging/core/tagging/import_export/actions.py,sha256=n007-M59D0mXYcwi9CDldDDy5JHAaGCVv9dQbiY4pZ4,13275
57
+ openedx_tagging/core/tagging/import_export/api.py,sha256=n8xamMNsFoucyC_qynpjzkIS2EbOfFDWdhsG_SNT4V0,7120
60
58
  openedx_tagging/core/tagging/import_export/exceptions.py,sha256=GGBldoW0tjYBrSlqDDwKkl6N0FIg1Yt5xcl7efxfo20,3116
61
- openedx_tagging/core/tagging/import_export/import_plan.py,sha256=9JbZ8lMCLPpGqrw9sbcG1b1VvMpiaSuY1vMZCzXR4MA,6627
59
+ openedx_tagging/core/tagging/import_export/import_plan.py,sha256=ol9mLfqqR0t1q0Om7D-iC9zKw1EI9MX3yAEKL8q6ias,6824
62
60
  openedx_tagging/core/tagging/import_export/parsers.py,sha256=eXjMPfMfqcCUKSaXc5xfPEI5PFEK1tNJNGnKHz51tY8,9864
63
61
  openedx_tagging/core/tagging/import_export/tasks.py,sha256=ZNtCGZuWn7z5E5o6OskrKjeV3vzzVa_yHaa-KlCnaEs,924
64
62
  openedx_tagging/core/tagging/import_export/template.csv,sha256=HbmXyW94oyfqfVyiaPv1uBEyul9qb8oPmbk9UiMQYo4,1559
@@ -85,7 +83,7 @@ openedx_tagging/core/tagging/migrations/0017_alter_tagimporttask_status.py,sha25
85
83
  openedx_tagging/core/tagging/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
86
84
  openedx_tagging/core/tagging/models/__init__.py,sha256=yYdOnthuc7EUdfEULtZgqRwn5Y4bbYQmJCjVZqR5GTM,236
87
85
  openedx_tagging/core/tagging/models/base.py,sha256=1WAPxssL8thAg8LHh1GbwSo98H81-nVdTVQt1nC1ZdU,39335
88
- openedx_tagging/core/tagging/models/import_export.py,sha256=HoYYg4kcKFUro5_i7O6Ms7373zmrDQV4yK3skNWP5fU,4217
86
+ openedx_tagging/core/tagging/models/import_export.py,sha256=Aj0pleh0nh2LNS6zmdB1P4bpdgUMmvmobTkqBerORAI,4570
89
87
  openedx_tagging/core/tagging/models/system_defined.py,sha256=_6LfvUZGEltvQMtm2OXy6TOLh3C8GnVTqtZDSAZW6K4,9062
90
88
  openedx_tagging/core/tagging/models/utils.py,sha256=-A3Dj24twmTf65UB7G4WLvb_9qEvduEPIwahZ-FJDlg,1926
91
89
  openedx_tagging/core/tagging/rest_api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -98,8 +96,8 @@ openedx_tagging/core/tagging/rest_api/v1/serializers.py,sha256=gbvEBLvsmfPc3swWz
98
96
  openedx_tagging/core/tagging/rest_api/v1/urls.py,sha256=dNUKCtUCx_YzrwlbEbpDfjGVQbb2QdJ1VuJCkladj6E,752
99
97
  openedx_tagging/core/tagging/rest_api/v1/views.py,sha256=ZRkSILdb8g5k_BcuuVVfdffEdY9vFQ_YtMa3JrN0Xz8,35581
100
98
  openedx_tagging/core/tagging/rest_api/v1/views_import.py,sha256=kbHUPe5A6WaaJ3J1lFIcYCt876ecLNQfd19m7YYub6c,1470
101
- openedx_learning-0.9.3.dist-info/LICENSE.txt,sha256=QTW2QN7q3XszgUAXm9Dzgtu5LXYKbR1SGnqMa7ufEuY,35139
102
- openedx_learning-0.9.3.dist-info/METADATA,sha256=qCr7maCmqbhW_lwaYhJRsu6BdicczVsjiRlxdcgJk1g,8860
103
- openedx_learning-0.9.3.dist-info/WHEEL,sha256=DZajD4pwLWue70CAfc7YaxT1wLUciNBvN_TTcvXpltE,110
104
- openedx_learning-0.9.3.dist-info/top_level.txt,sha256=IYFbr5mgiEHd-LOtZmXj3q3a0bkGK1M9LY7GXgnfi4M,33
105
- openedx_learning-0.9.3.dist-info/RECORD,,
99
+ openedx_learning-0.10.0.dist-info/LICENSE.txt,sha256=QTW2QN7q3XszgUAXm9Dzgtu5LXYKbR1SGnqMa7ufEuY,35139
100
+ openedx_learning-0.10.0.dist-info/METADATA,sha256=B3Gg7iIer_sJiWvXtA9Sd4xdbguHC3XIqaAsjhp15I4,8829
101
+ openedx_learning-0.10.0.dist-info/WHEEL,sha256=DZajD4pwLWue70CAfc7YaxT1wLUciNBvN_TTcvXpltE,110
102
+ openedx_learning-0.10.0.dist-info/top_level.txt,sha256=IYFbr5mgiEHd-LOtZmXj3q3a0bkGK1M9LY7GXgnfi4M,33
103
+ openedx_learning-0.10.0.dist-info/RECORD,,
@@ -244,16 +244,13 @@ class CreateTag(ImportAction):
244
244
  """
245
245
  Creates a Tag
246
246
  """
247
- parent = None
248
- if self.tag.parent_id:
249
- parent = self.taxonomy.tag_set.get(external_id=self.tag.parent_id)
250
- taxonomy_tag = Tag(
247
+ Tag.objects.create(
251
248
  taxonomy=self.taxonomy,
252
- parent=parent,
249
+ parent=self.taxonomy.tag_set.get(external_id=self.tag.parent_id)
250
+ if self.tag.parent_id is not None else None,
253
251
  value=self.tag.value,
254
252
  external_id=self.tag.id,
255
253
  )
256
- taxonomy_tag.save()
257
254
 
258
255
 
259
256
  class UpdateParentTag(ImportAction):
@@ -409,8 +406,7 @@ class DeleteTag(ImportAction):
409
406
  Delete a tag
410
407
  """
411
408
  try:
412
- taxonomy_tag = self._get_tag()
413
- taxonomy_tag.delete()
409
+ self._get_tag().delete()
414
410
  except Tag.DoesNotExist:
415
411
  pass # The tag may be already cascade deleted if the parent tag was deleted
416
412
 
@@ -44,6 +44,7 @@ TODO for next versions
44
44
  """
45
45
  from __future__ import annotations
46
46
 
47
+ import time
47
48
  from typing import BinaryIO
48
49
 
49
50
  from django.utils.translation import gettext as _
@@ -77,6 +78,7 @@ def import_tags(
77
78
 
78
79
  Set `plan_only` to True to only generate the actions and not execute them.
79
80
  """
81
+ global_start_time = time.time()
80
82
  _import_validations(taxonomy)
81
83
 
82
84
  # Checks that exists only one task import in progress at a time per taxonomy
@@ -92,7 +94,10 @@ def import_tags(
92
94
  task = TagImportTask.create(taxonomy)
93
95
 
94
96
  try:
97
+ # Start of parsing
98
+
95
99
  # Get the parser and parse the file
100
+ start_time = time.time()
96
101
  task.log_parser_start()
97
102
  parser = get_parser(parser_format)
98
103
  tags, errors = parser.parse_import(file)
@@ -102,23 +107,48 @@ def import_tags(
102
107
  task.handle_parser_errors(errors)
103
108
  return False, task, None
104
109
 
105
- task.log_parser_end()
110
+ end_time = time.time()
111
+ elapsed_time = end_time - start_time
112
+ elapsed_time = round(elapsed_time, 5)
113
+ task.log_parser_end(elapsed_time)
114
+
115
+ # End of parsing
116
+
117
+ # Start of generate actions
118
+
119
+ start_time = time.time()
106
120
 
107
- # Generate actions
108
121
  task.log_start_planning()
109
122
  tag_import_plan = TagImportPlan(taxonomy)
110
123
  tag_import_plan.generate_actions(tags, replace)
111
- task.log_plan(tag_import_plan)
124
+
125
+ end_time = time.time()
126
+ elapsed_time = end_time - start_time
127
+ elapsed_time = round(elapsed_time, 5)
128
+ task.log_plan(tag_import_plan, elapsed_time)
129
+
130
+ # End of generate actions
112
131
 
113
132
  if tag_import_plan.errors:
114
133
  task.handle_plan_errors()
115
134
  return False, task, tag_import_plan
116
135
 
117
136
  if not plan_only:
137
+ # Start of execute
138
+ start_time = time.time()
118
139
  task.log_start_execute()
119
140
  tag_import_plan.execute(task)
141
+ end_time = time.time()
142
+ elapsed_time = end_time - start_time
143
+ elapsed_time = round(elapsed_time, 5)
144
+ task.log_end_execute(elapsed_time)
145
+ # End of execute
146
+
147
+ global_end_time = time.time()
148
+ global_elapsed_time = global_end_time - global_start_time
149
+ global_elapsed_time = round(global_elapsed_time, 5)
120
150
 
121
- task.end_success()
151
+ task.end_success(global_elapsed_time)
122
152
 
123
153
  return True, task, tag_import_plan
124
154
  except Exception as exception:
@@ -207,8 +207,12 @@ class TagImportPlan:
207
207
  if self.errors:
208
208
  return
209
209
  for action in self.actions:
210
+ # Avoid to save each log because it is slow and costs a lot in memory
211
+ # It is necessary to save at the end.
210
212
  if task:
211
- task.add_log(f"#{action.index}: {str(action)} [Started]")
213
+ task.add_log(f"#{action.index}: {str(action)} [Started]", save=False)
212
214
  action.execute()
213
215
  if task:
214
- task.add_log("Success")
216
+ task.add_log("Success", save=False)
217
+ if task:
218
+ task.save()
@@ -90,11 +90,11 @@ class TagImportTask(models.Model):
90
90
  """
91
91
  self.add_log(_("Starting to load data from file"))
92
92
 
93
- def log_parser_end(self):
93
+ def log_parser_end(self, elapsed_time):
94
94
  """
95
95
  Logs the parser finished event.
96
96
  """
97
- self.add_log(_("Load data finished"))
97
+ self.add_log(_("Load data finished. Time elapsed: ") + str(elapsed_time) + _(" seconds"))
98
98
 
99
99
  def handle_parser_errors(self, errors):
100
100
  """
@@ -113,11 +113,11 @@ class TagImportTask(models.Model):
113
113
  self.status = TagImportTaskState.PLANNING.value
114
114
  self.save()
115
115
 
116
- def log_plan(self, plan):
116
+ def log_plan(self, plan, elapsed_time):
117
117
  """
118
118
  Logs the task plan.
119
119
  """
120
- self.add_log(_("Plan finished"))
120
+ self.add_log(_("Plan finished. Time elapsed: ") + str(elapsed_time) + _(" seconds"))
121
121
  plan_str = plan.plan()
122
122
  self.log += f"\n{plan_str}\n"
123
123
  self.save()
@@ -138,10 +138,13 @@ class TagImportTask(models.Model):
138
138
  self.status = TagImportTaskState.EXECUTING.value
139
139
  self.save()
140
140
 
141
- def end_success(self):
141
+ def log_end_execute(self, elapsed_time):
142
+ self.add_log(_("Execute actions finished. Time elapsed: ") + str(elapsed_time) + _(" seconds"))
143
+
144
+ def end_success(self, elapsed_time):
142
145
  """
143
146
  Completes task execution with a log message, and moves the task status to SUCCESS.
144
147
  """
145
- self.add_log(_("Execution finished"), save=False)
148
+ self.add_log(_("Execution finished. Total time elapsed: ") + str(elapsed_time) + _("seconds"), save=False)
146
149
  self.status = TagImportTaskState.SUCCESS.value
147
150
  self.save()
@@ -1,14 +0,0 @@
1
- """
2
- Django metadata for the Learning Core REST API app
3
- """
4
- from django.apps import AppConfig
5
-
6
-
7
- class RESTAPIConfig(AppConfig):
8
- """
9
- Configuration for the Learning Core REST API Django app.
10
- """
11
-
12
- name = "openedx_learning.rest_api"
13
- verbose_name = "Learning Core: REST API"
14
- default_auto_field = "django.db.models.BigAutoField"
@@ -1,6 +0,0 @@
1
- """
2
- URLs for the Learning Core REST API
3
- """
4
- from django.urls import include, path
5
-
6
- urlpatterns = [path("v1/", include("openedx_learning.rest_api.v1.urls"))]
@@ -1,30 +0,0 @@
1
- """
2
- This is just an example REST API endpoint.
3
- """
4
- from rest_framework import viewsets
5
-
6
- from openedx_learning.core.components.models import Component
7
-
8
-
9
- class ComponentViewSet(viewsets.ViewSet):
10
- """
11
- Example endpoints for a Components REST API. Not implemented.
12
- """
13
- def list(self, request):
14
- _items = Component.objects.all()
15
- raise NotImplementedError
16
-
17
- def retrieve(self, request, pk=None):
18
- raise NotImplementedError
19
-
20
- def create(self, request):
21
- raise NotImplementedError
22
-
23
- def update(self, request, pk=None):
24
- raise NotImplementedError
25
-
26
- def partial_update(self, request, pk=None):
27
- raise NotImplementedError
28
-
29
- def destroy(self, request, pk=None):
30
- raise NotImplementedError
@@ -1,10 +0,0 @@
1
- """
2
- URLs for the Learning Core REST API v1
3
- """
4
- from rest_framework.routers import DefaultRouter
5
-
6
- from . import components
7
-
8
- router = DefaultRouter()
9
- router.register(r"components", components.ComponentViewSet, basename="component")
10
- urlpatterns = router.urls
File without changes
File without changes