openedx-learning 0.5.1__py2.py3-none-any.whl → 0.6.1__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 (24) hide show
  1. openedx_learning/__init__.py +1 -1
  2. openedx_learning/contrib/media_server/views.py +2 -2
  3. openedx_learning/core/components/admin.py +22 -31
  4. openedx_learning/core/components/api.py +51 -47
  5. openedx_learning/core/components/migrations/0001_initial.py +12 -12
  6. openedx_learning/core/components/migrations/0002_alter_componentversioncontent_key.py +20 -0
  7. openedx_learning/core/components/models.py +37 -30
  8. openedx_learning/core/contents/admin.py +13 -20
  9. openedx_learning/core/contents/api.py +104 -94
  10. openedx_learning/core/contents/migrations/0001_initial.py +23 -30
  11. openedx_learning/core/contents/models.py +230 -149
  12. openedx_learning/core/publishing/migrations/0001_initial.py +2 -2
  13. openedx_learning/core/publishing/migrations/0002_alter_learningpackage_key_and_more.py +25 -0
  14. openedx_learning/core/publishing/models.py +41 -2
  15. openedx_learning/lib/fields.py +14 -2
  16. openedx_learning/lib/managers.py +6 -2
  17. {openedx_learning-0.5.1.dist-info → openedx_learning-0.6.1.dist-info}/METADATA +4 -4
  18. {openedx_learning-0.5.1.dist-info → openedx_learning-0.6.1.dist-info}/RECORD +24 -22
  19. openedx_tagging/core/tagging/data.py +1 -0
  20. openedx_tagging/core/tagging/models/base.py +36 -5
  21. openedx_tagging/core/tagging/rest_api/v1/serializers.py +1 -0
  22. {openedx_learning-0.5.1.dist-info → openedx_learning-0.6.1.dist-info}/LICENSE.txt +0 -0
  23. {openedx_learning-0.5.1.dist-info → openedx_learning-0.6.1.dist-info}/WHEEL +0 -0
  24. {openedx_learning-0.5.1.dist-info → openedx_learning-0.6.1.dist-info}/top_level.txt +0 -0
@@ -1,38 +1,40 @@
1
- openedx_learning/__init__.py,sha256=gvkyGVExc4QdwZc86bIh3XQTh8uSOIhf0bVJTMxPrbg,67
1
+ openedx_learning/__init__.py,sha256=4Akvu3WuVTtV-T_BQb8iO3zg_KBv_PDEc1owlfudTlQ,67
2
2
  openedx_learning/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  openedx_learning/contrib/media_server/__init__.py,sha256=iYijWFCl5RNR9omSu22kMl49EfponoqXBqXr0HMp4QI,56
4
4
  openedx_learning/contrib/media_server/apps.py,sha256=FPT0rsUFtPyhFpWKjSI1e_s58wU0IbDyaAW_66V6sY4,816
5
5
  openedx_learning/contrib/media_server/urls.py,sha256=newNjV41sM9A9Oy_rgnZSXdkTFxSHiupIiAsVIGE2CE,365
6
- openedx_learning/contrib/media_server/views.py,sha256=ZwEZV1yffU-JoFxinJU3k1kAfEMEY0sNOn5tV38ouhU,1529
6
+ openedx_learning/contrib/media_server/views.py,sha256=V-eq0SFi5815lkGdRz8dye4fF3dHzO5lVfm78gfG3mg,1537
7
7
  openedx_learning/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  openedx_learning/core/components/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- openedx_learning/core/components/admin.py,sha256=sQ3zNyxJtPssG9agZXPm52e1QmJ_IwZlyzLtoOhnmbM,5174
10
- openedx_learning/core/components/api.py,sha256=HfH9kZ1zTEyi0q8KDKtW1b3LiTabYStj-bW7AgjOUuw,12088
9
+ openedx_learning/core/components/admin.py,sha256=QE7e76C6X2V1AQPxQe6SayQPfCMmbs4RZPEPIGcvTWw,4672
10
+ openedx_learning/core/components/api.py,sha256=2ux1s9Bpfvs5sgTViHLagMb1IHpMo_vRR-MbrbXh0_U,12345
11
11
  openedx_learning/core/components/apps.py,sha256=invLeAY0B-d_Ac9xu_b5ZWeb7v5NJe_-938BO9U2-jo,747
12
- openedx_learning/core/components/models.py,sha256=l1K6xzsgpYhD4f4KOOJNzrrBGkpGb8lAAtc54OxLEFQ,12669
13
- openedx_learning/core/components/migrations/0001_initial.py,sha256=LzGWn--dwIzhBQ9t3V6tPgBDn0CcyvNkvBjNyXbQlN4,4710
12
+ openedx_learning/core/components/models.py,sha256=duVLdkTFnM7ixsqXQPlvmqmgf32hYyXdeRw1W7MdOpg,13170
13
+ openedx_learning/core/components/migrations/0001_initial.py,sha256=446LkJSFeK8J_-l-bxakZ_BVx_CiJIllGcBYqWcEenA,4664
14
+ openedx_learning/core/components/migrations/0002_alter_componentversioncontent_key.py,sha256=98724dtucRjJCRyLt5p45qXYb2d6-ouVGp7PB6zTG6E,539
14
15
  openedx_learning/core/components/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
16
  openedx_learning/core/contents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- openedx_learning/core/contents/admin.py,sha256=iYNa6jIdVL3Ce1VYKITzO0FMZ8pQKr0NMqUU3y4KwvM,1373
17
- openedx_learning/core/contents/api.py,sha256=tQzTQL_vSiPvSeSQimKLtp4c-OqOdPVC-3iq9qW_eiY,4532
17
+ openedx_learning/core/contents/admin.py,sha256=4ILH_cEiAXKUlfPVwJJZeh5yupgF3v7kf-xJ6ZTTFDE,1174
18
+ openedx_learning/core/contents/api.py,sha256=09159G0e_r7glDvi-G8c5k6ads2LmhmDLHnHeTSpotI,5553
18
19
  openedx_learning/core/contents/apps.py,sha256=6O04ikajbc4FsSdzx7yEl0cdQpEHpK1LdE471vygdII,375
19
- openedx_learning/core/contents/models.py,sha256=p30Mm0VLUQ6Xz0VslkOHYQibQJ2IlZ3J9JUT-tS8IUE,11250
20
- openedx_learning/core/contents/migrations/0001_initial.py,sha256=KupLbOjnWhp2EysROZcCDWn5n92xbkE7hgxWEXMUbM0,3641
20
+ openedx_learning/core/contents/models.py,sha256=HInsxr3Bnl48_0fbdoDKnGmrpIxTFTpoQTUg6_opdB4,15143
21
+ openedx_learning/core/contents/migrations/0001_initial.py,sha256=FtOTmIGX2KHpjw-PHbfRjxkFEomI5CEDhNKCZ7IpFeE,3060
21
22
  openedx_learning/core/contents/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
23
  openedx_learning/core/publishing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
24
  openedx_learning/core/publishing/admin.py,sha256=F-0QlVQmuovLIF258XK_vKJdOnn7lLa_0A5veE72TKc,4830
24
25
  openedx_learning/core/publishing/api.py,sha256=pfBePrmP0jnU5YTt4x7qhT37RN02P95gw2-xQNA_e9A,14254
25
26
  openedx_learning/core/publishing/apps.py,sha256=skY84raPW6gvrb5VPzhrLR2_gIHTUG67tMhSO7umVN4,379
26
27
  openedx_learning/core/publishing/model_mixins.py,sha256=Y6hGh0MKVOx0dUyNL0YkLL9ErUnYI2Nv2ZGMhc3CMng,13412
27
- openedx_learning/core/publishing/models.py,sha256=ZaAfUWZnurY_0HDCScGNjSo5VoEfBuogwbGucrwLxsk,18167
28
- openedx_learning/core/publishing/migrations/0001_initial.py,sha256=RAao1TdTV9xDN_1-iXfpWAmiR65r7pNPTsxKBaJ1gag,9313
28
+ openedx_learning/core/publishing/models.py,sha256=MGezxLoKFqHYnZccknqWyuNYrgiqu9eGDc4H9yIYzrg,20321
29
+ openedx_learning/core/publishing/migrations/0001_initial.py,sha256=wvekNV19YRSdxRmQaFnLSn_nCsQlHIucPDVMmgKf_OE,9272
30
+ openedx_learning/core/publishing/migrations/0002_alter_learningpackage_key_and_more.py,sha256=toI7qJhNukk6hirKfFx9EpqTpzF2O2Yq1VpFJusDn2M,806
29
31
  openedx_learning/core/publishing/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
32
  openedx_learning/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
33
  openedx_learning/lib/admin_utils.py,sha256=5z9NrXxmT5j8azx9u1t0AgxV5PIDTc2jPyM5z5yW8cw,4021
32
34
  openedx_learning/lib/cache.py,sha256=ppT36KiPLdsAF3GfZCF0IdiHodckd2gLiF1sNhjSJuk,958
33
35
  openedx_learning/lib/collations.py,sha256=mMUXt6rnKoYOKTIVAVwiIQtWhaNsxRCfdeR25TPeK3U,4325
34
- openedx_learning/lib/fields.py,sha256=u14v9G4gTdwtgiiERMiXygUgKXUoW7qdZH7z0MivX0k,6982
35
- openedx_learning/lib/managers.py,sha256=IGgCfA19vpgZ_rydZpsduUShQcNRYUFO3aKn0L2pST4,1269
36
+ openedx_learning/lib/fields.py,sha256=eiGoXMPhRuq25EH2qf6BAODshAQE3DBVdIYAMIUAXW0,7522
37
+ openedx_learning/lib/managers.py,sha256=ofcUxHS2wsJSFP4CB7OCkN6JiWFxNlIyNh_VDgFtgug,1538
36
38
  openedx_learning/lib/test_utils.py,sha256=g3KLuepIZbaDBCsaj9711YuqyUx7LD4gXDcfNC-mWdc,527
37
39
  openedx_learning/lib/validators.py,sha256=98UldFhFxd77zF1VxzQcOPt0blh64gMZDv-d_uQ67mg,396
38
40
  openedx_learning/rest_api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -47,7 +49,7 @@ openedx_tagging/core/tagging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
47
49
  openedx_tagging/core/tagging/admin.py,sha256=5mSTxlftMq5MVGzdE8xl3AcxgtfpnrKpXPNdvw7hAno,1075
48
50
  openedx_tagging/core/tagging/api.py,sha256=jXmu2WPFDEiLKpz0OqVR8yo7NyKTo3omlRkytwa__L8,15758
49
51
  openedx_tagging/core/tagging/apps.py,sha256=-gp0VYqX4XQzwjjd-G68Ev2Op0INLh9Byz5UOqF5_7k,345
50
- openedx_tagging/core/tagging/data.py,sha256=C5uRsR3she-iB3n1OyPdQm6PsIc1Lg-XOI7HT0yNLPI,1327
52
+ openedx_tagging/core/tagging/data.py,sha256=421EvmDzdM7H523dBVQk4J0W_UwTT4U5syqPRXUYK4g,1353
51
53
  openedx_tagging/core/tagging/rules.py,sha256=UqIPPbOVA6FFF6uqLk0s5ORUczSYQt-a-S6Q_dB-RiE,6286
52
54
  openedx_tagging/core/tagging/urls.py,sha256=-0Nzmh0mlF9-GgEuocwBdSJn6n8EINnxR4m4pmPou44,159
53
55
  openedx_tagging/core/tagging/import_export/__init__.py,sha256=q5K4JalFQlJxAFUFyqhLY5zQtAskDnRM1H_aVuP_E3Q,83
@@ -78,7 +80,7 @@ openedx_tagging/core/tagging/migrations/0014_minor_fixes.py,sha256=46_F-el1UylSR
78
80
  openedx_tagging/core/tagging/migrations/0015_taxonomy_export_id.py,sha256=jhS-T8o2mwu61E7hPbjjE_6MPLKRPQFAVu7pJHZNRz4,1454
79
81
  openedx_tagging/core/tagging/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
80
82
  openedx_tagging/core/tagging/models/__init__.py,sha256=yYdOnthuc7EUdfEULtZgqRwn5Y4bbYQmJCjVZqR5GTM,236
81
- openedx_tagging/core/tagging/models/base.py,sha256=hlcb4Sk6UxTQGn2XcENsOE3EjMfu2Y1tcf3-_tro7YI,36586
83
+ openedx_tagging/core/tagging/models/base.py,sha256=7I6pIbBOxev9abAX7bDvQ0Ac-DQMLbth0DJ7Kj6fTak,38291
82
84
  openedx_tagging/core/tagging/models/import_export.py,sha256=cXsTk44CO6RZvmiMI5ERsyjuM-wYnJoORwCsGEj6OEc,4125
83
85
  openedx_tagging/core/tagging/models/system_defined.py,sha256=_6LfvUZGEltvQMtm2OXy6TOLh3C8GnVTqtZDSAZW6K4,9062
84
86
  openedx_tagging/core/tagging/models/utils.py,sha256=JD3ypABgEdx5lQBttFm0ByfgHJPuOQiJ8HYvUgAMA8g,1417
@@ -88,12 +90,12 @@ openedx_tagging/core/tagging/rest_api/urls.py,sha256=egXaRQv1EAgF04ThgVZBQuvLK1L
88
90
  openedx_tagging/core/tagging/rest_api/utils.py,sha256=XZXixZ44vpNlxiyFplW8Lktyh_m1EfR3Y-tnyvA7acc,3620
89
91
  openedx_tagging/core/tagging/rest_api/v1/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
90
92
  openedx_tagging/core/tagging/rest_api/v1/permissions.py,sha256=FeSulmsFD7wAAuYpxFeGMZwgJW51d8K6bf6SQm6vbgA,2131
91
- openedx_tagging/core/tagging/rest_api/v1/serializers.py,sha256=kOnlTFZNk3ED_hL0Xt4wkokDmHOXkiCDV7mxrYQm35Q,13038
93
+ openedx_tagging/core/tagging/rest_api/v1/serializers.py,sha256=I9jk3x3wPIeV_XCyC4uNTeqllumXpcQLVD0rdy_YNH8,13088
92
94
  openedx_tagging/core/tagging/rest_api/v1/urls.py,sha256=dNUKCtUCx_YzrwlbEbpDfjGVQbb2QdJ1VuJCkladj6E,752
93
95
  openedx_tagging/core/tagging/rest_api/v1/views.py,sha256=opPY8xwbOupUaLnX0ovCV-oJ06l-d_qV8CgT47-m-Dc,34358
94
96
  openedx_tagging/core/tagging/rest_api/v1/views_import.py,sha256=kbHUPe5A6WaaJ3J1lFIcYCt876ecLNQfd19m7YYub6c,1470
95
- openedx_learning-0.5.1.dist-info/LICENSE.txt,sha256=QTW2QN7q3XszgUAXm9Dzgtu5LXYKbR1SGnqMa7ufEuY,35139
96
- openedx_learning-0.5.1.dist-info/METADATA,sha256=iKPxIiJ232B9pOWaYvFPK4lwA5rTEf7Sky5OOrgaItI,8758
97
- openedx_learning-0.5.1.dist-info/WHEEL,sha256=Z-nyYpwrcSqxfdux5Mbn_DQ525iP7J2DG3JgGvOYyTQ,110
98
- openedx_learning-0.5.1.dist-info/top_level.txt,sha256=IYFbr5mgiEHd-LOtZmXj3q3a0bkGK1M9LY7GXgnfi4M,33
99
- openedx_learning-0.5.1.dist-info/RECORD,,
97
+ openedx_learning-0.6.1.dist-info/LICENSE.txt,sha256=QTW2QN7q3XszgUAXm9Dzgtu5LXYKbR1SGnqMa7ufEuY,35139
98
+ openedx_learning-0.6.1.dist-info/METADATA,sha256=yw2xbOghDZ_i09kmUY-TqCnVX3gTYc0KP164ZDzis0E,8758
99
+ openedx_learning-0.6.1.dist-info/WHEEL,sha256=Z-nyYpwrcSqxfdux5Mbn_DQ525iP7J2DG3JgGvOYyTQ,110
100
+ openedx_learning-0.6.1.dist-info/top_level.txt,sha256=IYFbr5mgiEHd-LOtZmXj3q3a0bkGK1M9LY7GXgnfi4M,33
101
+ openedx_learning-0.6.1.dist-info/RECORD,,
@@ -21,6 +21,7 @@ class TagData(TypedDict):
21
21
  value: str
22
22
  external_id: str | None
23
23
  child_count: int
24
+ descendant_count: int
24
25
  depth: int
25
26
  parent_value: str | None
26
27
  # Note: usage_count may or may not be present, depending on the request.
@@ -171,6 +171,18 @@ class Tag(models.Model):
171
171
  return self.taxonomy.tag_set.filter(parent=self).count()
172
172
  return 0
173
173
 
174
+ @cached_property
175
+ def descendant_count(self) -> int:
176
+ """
177
+ How many descendant tags this tag has in the taxonomy.
178
+ """
179
+ if self.taxonomy and not self.taxonomy.allow_free_text:
180
+ return self.taxonomy.tag_set.filter(
181
+ Q(parent__parent=self) |
182
+ Q(parent__parent__parent=self)
183
+ ).count() + self.child_count
184
+ return 0
185
+
174
186
  def clean(self):
175
187
  """
176
188
  Validate this tag before saving
@@ -436,6 +448,7 @@ class Taxonomy(models.Model):
436
448
  qs = qs.annotate(
437
449
  depth=Value(0),
438
450
  child_count=Value(0),
451
+ descendant_count=Value(0),
439
452
  external_id=Value(None, output_field=models.CharField()),
440
453
  parent_value=Value(None, output_field=models.CharField()),
441
454
  _id=Value(None, output_field=models.CharField()),
@@ -467,12 +480,16 @@ class Taxonomy(models.Model):
467
480
  else:
468
481
  qs = self.tag_set.filter(parent=None).annotate(depth=Value(0))
469
482
  qs = qs.annotate(parent_value=Value(None, output_field=models.CharField()))
470
- qs = qs.annotate(child_count=models.Count("children"))
483
+ qs = qs.annotate(child_count=models.Count("children", distinct=True))
484
+ qs = qs.annotate(grandchild_count=models.Count("children__children", distinct=True))
485
+ qs = qs.annotate(great_grandchild_count=models.Count("children__children__children"))
486
+ qs = qs.annotate(descendant_count=F("child_count") + F("grandchild_count") + F("great_grandchild_count"))
471
487
  # Filter by search term:
472
488
  if search_term:
473
489
  qs = qs.filter(value__icontains=search_term)
474
490
  qs = qs.annotate(_id=F("id")) # ID has an underscore to encourage use of 'value' rather than this internal ID
475
- qs = qs.values("value", "child_count", "depth", "parent_value", "external_id", "_id").order_by("value")
491
+ qs = qs.values("value", "child_count", "descendant_count", "depth", "parent_value", "external_id", "_id")
492
+ qs = qs.order_by("value")
476
493
  if include_counts:
477
494
  # We need to include the count of how many times this tag is used to tag objects.
478
495
  # You'd think we could just use:
@@ -524,14 +541,27 @@ class Taxonomy(models.Model):
524
541
  if pk is not None:
525
542
  matching_ids.append(pk)
526
543
  qs = qs.filter(pk__in=matching_ids)
527
- qs = qs.annotate(child_count=models.Count("children", filter=Q(children__pk__in=matching_ids)))
544
+ qs = qs.annotate(
545
+ child_count=models.Count("children", filter=Q(children__pk__in=matching_ids), distinct=True),
546
+ grandchild_count=models.Count(
547
+ "children__children", filter=Q(children__children__pk__in=matching_ids), distinct=True,
548
+ ),
549
+ great_grandchild_count=models.Count(
550
+ "children__children__children",
551
+ filter=Q(children__children__children__pk__in=matching_ids),
552
+ ),
553
+ )
554
+ qs = qs.annotate(descendant_count=F("child_count") + F("grandchild_count") + F("great_grandchild_count"))
528
555
  elif excluded_values:
529
556
  raise NotImplementedError("Using excluded_values without search_term is not currently supported.")
530
557
  # We could implement this in the future but I'd prefer to get rid of the "excluded_values" API altogether.
531
558
  # It remains to be seen if it's useful to do that on the backend, or if we can do it better/simpler on the
532
559
  # frontend.
533
560
  else:
534
- qs = qs.annotate(child_count=models.Count("children"))
561
+ qs = qs.annotate(child_count=models.Count("children", distinct=True))
562
+ qs = qs.annotate(grandchild_count=models.Count("children__children", distinct=True))
563
+ qs = qs.annotate(great_grandchild_count=models.Count("children__children__children"))
564
+ qs = qs.annotate(descendant_count=F("child_count") + F("grandchild_count") + F("great_grandchild_count"))
535
565
 
536
566
  # Add the "depth" to each tag:
537
567
  qs = Tag.annotate_depth(qs)
@@ -550,7 +580,8 @@ class Taxonomy(models.Model):
550
580
  # Add the parent value
551
581
  qs = qs.annotate(parent_value=F("parent__value"))
552
582
  qs = qs.annotate(_id=F("id")) # ID has an underscore to encourage use of 'value' rather than this internal ID
553
- qs = qs.values("value", "child_count", "depth", "parent_value", "external_id", "_id").order_by("sort_key")
583
+ qs = qs.values("value", "child_count", "descendant_count", "depth", "parent_value", "external_id", "_id")
584
+ qs = qs.order_by("sort_key")
554
585
  if include_counts:
555
586
  # Including the counts is a bit tricky; see the comment above in _get_filtered_tags_one_level()
556
587
  obj_tags = ObjectTag.objects.filter(tag_id=models.OuterRef("pk")).order_by().annotate(
@@ -210,6 +210,7 @@ class TagDataSerializer(UserPermissionsSerializerMixin, serializers.Serializer):
210
210
  value = serializers.CharField()
211
211
  external_id = serializers.CharField(allow_null=True)
212
212
  child_count = serializers.IntegerField()
213
+ descendant_count = serializers.IntegerField()
213
214
  depth = serializers.IntegerField()
214
215
  parent_value = serializers.CharField(allow_null=True)
215
216
  usage_count = serializers.IntegerField(required=False)