openedx-learning 0.16.3__py2.py3-none-any.whl → 0.18.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.16.3"
5
+ __version__ = "0.18.0"
@@ -71,7 +71,6 @@ class ContentInline(admin.TabularInline):
71
71
  fields = [
72
72
  "key",
73
73
  "format_size",
74
- "learner_downloadable",
75
74
  "rendered_data",
76
75
  ]
77
76
  readonly_fields = [
@@ -215,7 +215,6 @@ def create_next_component_version(
215
215
  content_id=content_pk,
216
216
  component_version=component_version,
217
217
  key=key,
218
- learner_downloadable=False,
219
218
  )
220
219
  # Now copy any old associations that existed, as long as they aren't
221
220
  # in conflict with the new stuff or marked for deletion.
@@ -227,7 +226,6 @@ def create_next_component_version(
227
226
  content_id=cvrc.content_id,
228
227
  component_version=component_version,
229
228
  key=cvrc.key,
230
- learner_downloadable=cvrc.learner_downloadable,
231
229
  )
232
230
 
233
231
  return component_version
@@ -422,7 +420,6 @@ def create_component_version_content(
422
420
  content_id: int,
423
421
  /,
424
422
  key: str,
425
- learner_downloadable: bool = False,
426
423
  ) -> ComponentVersionContent:
427
424
  """
428
425
  Add a Content to the given ComponentVersion
@@ -445,7 +442,6 @@ def create_component_version_content(
445
442
  component_version_id=component_version_id,
446
443
  content_id=content_id,
447
444
  key=key,
448
- learner_downloadable=learner_downloadable,
449
445
  )
450
446
  return cvrc
451
447
 
@@ -453,7 +449,6 @@ def create_component_version_content(
453
449
  class AssetError(StrEnum):
454
450
  """Error codes related to fetching ComponentVersion assets."""
455
451
  ASSET_PATH_NOT_FOUND_FOR_COMPONENT_VERSION = auto()
456
- ASSET_NOT_LEARNER_DOWNLOADABLE = auto()
457
452
  ASSET_HAS_NO_DOWNLOAD_FILE = auto()
458
453
 
459
454
 
@@ -484,7 +479,6 @@ def get_redirect_response_for_component_asset(
484
479
  component_version_uuid: UUID,
485
480
  asset_path: Path,
486
481
  public: bool = False,
487
- learner_downloadable_only: bool = True,
488
482
  ) -> HttpResponse:
489
483
  """
490
484
  ``HttpResponse`` for a reverse-proxy to serve a ``ComponentVersion`` asset.
@@ -498,11 +492,6 @@ def get_redirect_response_for_component_asset(
498
492
  If ``True``, this will return an ``HttpResponse`` that can be cached in
499
493
  a CDN and shared across many clients.
500
494
 
501
- :param learner_downloadable_only: Only return assets that are meant to be
502
- downloadable by Learners, i.e. in the LMS experience. If this is
503
- ``True``, then requests for assets that are not meant for student
504
- download will return a ``404`` error response.
505
-
506
495
  **Response Codes**
507
496
 
508
497
  If the asset exists for this ``ComponentVersion``, this function will return
@@ -512,15 +501,10 @@ def get_redirect_response_for_component_asset(
512
501
  the ``ComponentVersion`` itself does not exist, the response code will be
513
502
  ``404``.
514
503
 
515
- Other than checking the coarse-grained ``learner_downloadable_only`` flag,
516
- *this function does not do auth checking of any sort*–it will never return
504
+ This function does not do auth checking of any sort. It will never return
517
505
  a ``401`` or ``403`` response code. That is by design. Figuring out who is
518
506
  making the request and whether they have permission to do so is the
519
- responsiblity of whatever is calling this function. The
520
- ``learner_downloadable_only`` flag is intended to be a filter for the entire
521
- view. When it's True, not even staff can download component-internal assets.
522
- This is intended to protect us from accidentally allowing sensitive grading
523
- code to get leaked out.
507
+ responsiblity of whatever is calling this function.
524
508
 
525
509
  **Metadata Headers**
526
510
 
@@ -596,24 +580,6 @@ def get_redirect_response_for_component_asset(
596
580
  )
597
581
  return HttpResponseNotFound(headers=info_headers)
598
582
 
599
- # Check: If we're asking only for Learner Downloadable assets, and the asset
600
- # in question is not supposed to be downloadable by learners, then we give a
601
- # 404 error. Even staff members are not expected to be able to download
602
- # these assets via the LMS endpoint that serves students. Studio would be
603
- # expected to have an entirely different view to serve these assets in that
604
- # context (along with different timeouts, auth, and cache settings). So in
605
- # that sense, the asset doesn't exist for that particular endpoint.
606
- if learner_downloadable_only and (not cv_content.learner_downloadable):
607
- logger.error(
608
- f"ComponentVersion {component_version_uuid} has asset {asset_path}, "
609
- "but it is not meant to be downloadable by learners "
610
- "(ComponentVersionContent.learner_downloadable=False)."
611
- )
612
- info_headers.update(
613
- _error_header(AssetError.ASSET_NOT_LEARNER_DOWNLOADABLE)
614
- )
615
- return HttpResponseNotFound(headers=info_headers)
616
-
617
583
  # At this point, we know that there is valid Content that we want to send.
618
584
  # This adds Content-level headers, like the hash/etag and content type.
619
585
  info_headers.update(contents_api.get_content_info_headers(content))
@@ -0,0 +1,17 @@
1
+ # Generated by Django 4.2.16 on 2024-11-06 17:14
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('oel_components', '0002_alter_componentversioncontent_key'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.RemoveField(
14
+ model_name='componentversioncontent',
15
+ name='learner_downloadable',
16
+ ),
17
+ ]
@@ -254,43 +254,6 @@ class ComponentVersionContent(models.Model):
254
254
  # identifiers that don't map as cleanly to file paths at some point.
255
255
  key = key_field(db_column="_key")
256
256
 
257
- # Long explanation for the ``learner_downloadable`` field:
258
- #
259
- # Is this Content downloadable during the learning experience? This is
260
- # NOT about public vs. private permissions on course assets, as that will be
261
- # a policy that can be changed independently of new versions of the content.
262
- # For instance, a course team could decide to flip their course assets from
263
- # private to public for CDN caching reasons, and that should not require
264
- # new ComponentVersions to be created.
265
- #
266
- # What the ``learner_downloadable`` field refers to is whether this asset is
267
- # supposed to *ever* be directly downloadable by browsers during the
268
- # learning experience. This will be True for things like images, PDFs, and
269
- # video transcript files. This field will be False for things like:
270
- #
271
- # * Problem Block OLX will contain the answers to the problem. The XBlock
272
- # runtime and ProblemBlock will use this information to generate HTML and
273
- # grade responses, but the the user's browser is never permitted to
274
- # actually download the raw OLX itself.
275
- # * Many courses include a python_lib.zip file holding custom Python code
276
- # to be used by codejail to assess student answers. This code will also
277
- # potentially reveal answers, and is never intended to be downloadable by
278
- # the student's browser.
279
- # * Some course teams will upload other file formats that their OLX is
280
- # derived from (e.g. specially formatted LaTeX files). These files will
281
- # likewise contain answers and should never be downloadable by the
282
- # student.
283
- # * Other custom metadata may be attached as files in the import, such as
284
- # custom identifiers, author information, etc.
285
- #
286
- # Even if ``learner_downloadble`` is True, the LMS may decide that this
287
- # particular student isn't allowed to see this particular piece of content
288
- # yet–e.g. because they are not enrolled, or because the exam this Component
289
- # is a part of hasn't started yet. That's a matter of LMS permissions and
290
- # policy that is not intrinsic to the content itself, and exists at a layer
291
- # above this.
292
- learner_downloadable = models.BooleanField(default=False)
293
-
294
257
  class Meta:
295
258
  constraints = [
296
259
  # Uniqueness is only by ComponentVersion and key. If for some reason
@@ -50,6 +50,7 @@ __all__ = [
50
50
  "soft_delete_draft",
51
51
  "reset_drafts_to_published",
52
52
  "register_content_models",
53
+ "filter_publishable_entities",
53
54
  ]
54
55
 
55
56
 
@@ -493,3 +494,22 @@ def register_content_models(
493
494
  return PublishableContentModelRegistry.register(
494
495
  content_model_cls, content_version_model_cls
495
496
  )
497
+
498
+
499
+ def filter_publishable_entities(
500
+ entities: QuerySet[PublishableEntity],
501
+ has_draft=None,
502
+ has_published=None
503
+ ) -> QuerySet[PublishableEntity]:
504
+ """
505
+ Filter an entities query set.
506
+
507
+ has_draft: You can filter by entities that has a draft or not.
508
+ has_published: You can filter by entities that has a published version or not.
509
+ """
510
+ if has_draft is not None:
511
+ entities = entities.filter(draft__version__isnull=not has_draft)
512
+ if has_published is not None:
513
+ entities = entities.filter(published__version__isnull=not has_published)
514
+
515
+ return entities
@@ -5,7 +5,7 @@ Views for the media server application
5
5
  """
6
6
  from pathlib import Path
7
7
 
8
- from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
8
+ from django.core.exceptions import ObjectDoesNotExist
9
9
  from django.http import FileResponse, Http404
10
10
 
11
11
  from openedx_learning.apps.authoring.components.api import look_up_component_version_content
@@ -34,11 +34,6 @@ def component_asset(
34
34
  except ObjectDoesNotExist:
35
35
  raise Http404("File not found") # pylint: disable=raise-missing-from
36
36
 
37
- if not cvc.learner_downloadable and not (
38
- request.user and request.user.is_superuser
39
- ):
40
- raise PermissionDenied("This file is not publicly downloadable.")
41
-
42
37
  response = FileResponse(cvc.raw_content.file, filename=Path(asset_path).name)
43
38
  response["Content-Type"] = cvc.raw_content.mime_type
44
39
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: openedx-learning
3
- Version: 0.16.3
3
+ Version: 0.18.0
4
4
  Summary: Open edX Learning Core and Tagging.
5
5
  Home-page: https://github.com/openedx/openedx-learning
6
6
  Author: David Ormsbee
@@ -18,12 +18,12 @@ Classifier: Programming Language :: Python :: 3.11
18
18
  Classifier: Programming Language :: Python :: 3.12
19
19
  Requires-Python: >=3.11
20
20
  License-File: LICENSE.txt
21
- Requires-Dist: edx-drf-extensions
22
- Requires-Dist: Django<5.0
23
- Requires-Dist: celery
24
- Requires-Dist: djangorestframework<4.0
25
21
  Requires-Dist: attrs
26
22
  Requires-Dist: rules<4.0
23
+ Requires-Dist: Django<5.0
24
+ Requires-Dist: djangorestframework<4.0
25
+ Requires-Dist: edx-drf-extensions
26
+ Requires-Dist: celery
27
27
 
28
28
  Open edX Learning Core (and Tagging)
29
29
  ====================================
@@ -1,4 +1,4 @@
1
- openedx_learning/__init__.py,sha256=bkrow8r93asdRsP0nSEgewxu6wo1JajZgTv--0P_RKk,69
1
+ openedx_learning/__init__.py,sha256=WQCJfrV8nUpwAaGx2NCUAw0d22YgqZqryjy0wIiV01M,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=vbRpiQ2wOfN3oR2bbN0-bI2ra0QRtha9tVixKW1ENis,929
@@ -17,15 +17,16 @@ openedx_learning/apps/authoring/collections/migrations/0004_collection_key.py,sh
17
17
  openedx_learning/apps/authoring/collections/migrations/0005_alter_collection_options_alter_collection_enabled.py,sha256=HdU_3zxN32nzzvOFpiVpQXleHleJhnq2d8k7jAxhUTM,504
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
- openedx_learning/apps/authoring/components/admin.py,sha256=3kFu_PR0BFb8U0zVv3WWhi27i__TDuJG0pFlwr3tKAw,4614
21
- openedx_learning/apps/authoring/components/api.py,sha256=BhLlEEBzxFNDHA1dBxoNvIFj92t-vaDF44VExWBbWrc,26861
20
+ openedx_learning/apps/authoring/components/admin.py,sha256=zfEpuBEySMYpUZzygaE2MDoI8SH-2H3xIL20YCSCMLo,4582
21
+ openedx_learning/apps/authoring/components/api.py,sha256=Kd8dscUAB5HWX84upmpfK-HjB_1olR7QBP7Ybxn0FVM,24894
22
22
  openedx_learning/apps/authoring/components/apps.py,sha256=YoYPsI9gcleA3uEs8CiLIrjUncRMo2DKbYt4mDfzePg,770
23
- openedx_learning/apps/authoring/components/models.py,sha256=T-wc7vxaWMlulQmMsVH7m6Pd857P3Eguo0vtflTLURI,13415
23
+ openedx_learning/apps/authoring/components/models.py,sha256=VX-pi4ZPJkCP3UtyTcVihgBdBj2pmenwdVC_xx8wCi0,11190
24
24
  openedx_learning/apps/authoring/components/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
25
  openedx_learning/apps/authoring/components/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
26
  openedx_learning/apps/authoring/components/management/commands/add_assets_to_component.py,sha256=0dJ77NZZoNzYheOdFPXtJrjdL_Z-pCNg3l1rbEGnMCY,3175
27
27
  openedx_learning/apps/authoring/components/migrations/0001_initial.py,sha256=446LkJSFeK8J_-l-bxakZ_BVx_CiJIllGcBYqWcEenA,4664
28
28
  openedx_learning/apps/authoring/components/migrations/0002_alter_componentversioncontent_key.py,sha256=98724dtucRjJCRyLt5p45qXYb2d6-ouVGp7PB6zTG6E,539
29
+ openedx_learning/apps/authoring/components/migrations/0003_remove_componentversioncontent_learner_downloadable.py,sha256=hDAkKdBvKULepML9pVMqkZg31nAyCeszQHJsFJ4qGws,382
29
30
  openedx_learning/apps/authoring/components/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
31
  openedx_learning/apps/authoring/contents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
32
  openedx_learning/apps/authoring/contents/admin.py,sha256=9Njd_lje1emcd168KBWUTGf0mVJ6K-dMYMcqHNjRU4k,1761
@@ -36,7 +37,7 @@ openedx_learning/apps/authoring/contents/migrations/0001_initial.py,sha256=FtOTm
36
37
  openedx_learning/apps/authoring/contents/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
38
  openedx_learning/apps/authoring/publishing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
39
  openedx_learning/apps/authoring/publishing/admin.py,sha256=F-0QlVQmuovLIF258XK_vKJdOnn7lLa_0A5veE72TKc,4830
39
- openedx_learning/apps/authoring/publishing/api.py,sha256=CbJ8bvUGbN879JMX29auLfHeTQ_CotA6n868qglyaPM,16845
40
+ openedx_learning/apps/authoring/publishing/api.py,sha256=dawwRDbqbA_02xtPAwFIukWShrkO6MoQptmEKdAN7uA,17475
40
41
  openedx_learning/apps/authoring/publishing/apps.py,sha256=jUfd78xvXaZg3dwkqXihatbeajJGm3Uz1rJpuyd-3g0,402
41
42
  openedx_learning/apps/authoring/publishing/model_mixins.py,sha256=DGM6cZSNX4KZHy5xWtNy6yO4bRT04VDus8yZJ4v3vI0,14447
42
43
  openedx_learning/apps/authoring/publishing/models.py,sha256=ImMAujPDc2-CECZw_yvVlUOdtGYwmt99TJ2r1HJkkV8,20488
@@ -47,7 +48,7 @@ openedx_learning/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZ
47
48
  openedx_learning/contrib/media_server/__init__.py,sha256=iYijWFCl5RNR9omSu22kMl49EfponoqXBqXr0HMp4QI,56
48
49
  openedx_learning/contrib/media_server/apps.py,sha256=FPT0rsUFtPyhFpWKjSI1e_s58wU0IbDyaAW_66V6sY4,816
49
50
  openedx_learning/contrib/media_server/urls.py,sha256=newNjV41sM9A9Oy_rgnZSXdkTFxSHiupIiAsVIGE2CE,365
50
- openedx_learning/contrib/media_server/views.py,sha256=A4umcQr9xU5l-7dCSUX9jP9-gtwf4LGjoMn7NlsdLsk,1547
51
+ openedx_learning/contrib/media_server/views.py,sha256=qZPhdEW_oYj1MEdgLVP6Cq3tRiZtp7dTb7ASaSKZ2HY,1350
51
52
  openedx_learning/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
53
  openedx_learning/lib/admin_utils.py,sha256=5z9NrXxmT5j8azx9u1t0AgxV5PIDTc2jPyM5z5yW8cw,4021
53
54
  openedx_learning/lib/cache.py,sha256=ppT36KiPLdsAF3GfZCF0IdiHodckd2gLiF1sNhjSJuk,958
@@ -111,8 +112,8 @@ openedx_tagging/core/tagging/rest_api/v1/serializers.py,sha256=D7brBbgmU7MnbU7Ln
111
112
  openedx_tagging/core/tagging/rest_api/v1/urls.py,sha256=dNUKCtUCx_YzrwlbEbpDfjGVQbb2QdJ1VuJCkladj6E,752
112
113
  openedx_tagging/core/tagging/rest_api/v1/views.py,sha256=LA0EF7-p91JgVrLZpZaG474elKD1dswODGvoPIw47Mg,35837
113
114
  openedx_tagging/core/tagging/rest_api/v1/views_import.py,sha256=kbHUPe5A6WaaJ3J1lFIcYCt876ecLNQfd19m7YYub6c,1470
114
- openedx_learning-0.16.3.dist-info/LICENSE.txt,sha256=QTW2QN7q3XszgUAXm9Dzgtu5LXYKbR1SGnqMa7ufEuY,35139
115
- openedx_learning-0.16.3.dist-info/METADATA,sha256=DmGqGCSBExCF06ztJhI0YSnT_IFUnbLt__WYV9He0qU,8777
116
- openedx_learning-0.16.3.dist-info/WHEEL,sha256=TJ49d73sNs10F0aze1W_bTW2P_X7-F4YXOlBqoqA-jY,109
117
- openedx_learning-0.16.3.dist-info/top_level.txt,sha256=IYFbr5mgiEHd-LOtZmXj3q3a0bkGK1M9LY7GXgnfi4M,33
118
- openedx_learning-0.16.3.dist-info/RECORD,,
115
+ openedx_learning-0.18.0.dist-info/LICENSE.txt,sha256=QTW2QN7q3XszgUAXm9Dzgtu5LXYKbR1SGnqMa7ufEuY,35139
116
+ openedx_learning-0.18.0.dist-info/METADATA,sha256=0TzunzFQsdhZ0vYpqBy0XCsW02pFP0mACyskAekcA4w,8777
117
+ openedx_learning-0.18.0.dist-info/WHEEL,sha256=OpXWERl2xLPRHTvd2ZXo_iluPEQd8uSbYkJ53NAER_Y,109
118
+ openedx_learning-0.18.0.dist-info/top_level.txt,sha256=IYFbr5mgiEHd-LOtZmXj3q3a0bkGK1M9LY7GXgnfi4M,33
119
+ openedx_learning-0.18.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.2.0)
2
+ Generator: setuptools (75.3.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py2-none-any
5
5
  Tag: py3-none-any