learning-paths-plugin 0.5.0__tar.gz → 0.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/CHANGELOG.rst +10 -0
  2. {learning_paths_plugin-0.5.0/learning_paths_plugin.egg-info → learning_paths_plugin-0.6.0}/PKG-INFO +11 -1
  3. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/__init__.py +1 -1
  4. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/receivers.py +39 -21
  5. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0/learning_paths_plugin.egg-info}/PKG-INFO +11 -1
  6. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/LICENSE.txt +0 -0
  7. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/MANIFEST.in +0 -0
  8. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/README.rst +0 -0
  9. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/admin.py +0 -0
  10. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/api/__init__.py +0 -0
  11. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/api/urls.py +0 -0
  12. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/api/v1/__init__.py +0 -0
  13. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/api/v1/filters.py +0 -0
  14. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/api/v1/permissions.py +0 -0
  15. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/api/v1/serializers.py +0 -0
  16. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/api/v1/urls.py +0 -0
  17. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/api/v1/utils.py +0 -0
  18. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/api/v1/views.py +0 -0
  19. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/apps.py +0 -0
  20. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/compat.py +0 -0
  21. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/conftest.py +0 -0
  22. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/keys.py +0 -0
  23. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/migrations/0001_initial.py +0 -0
  24. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/migrations/0002_learningpath_uuid.py +0 -0
  25. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/migrations/0003_learningpath_subtitle.py +0 -0
  26. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/migrations/0004_auto_20240207_1633.py +0 -0
  27. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/migrations/0005_learningpathstep_weight_learningpathgradingcriteria.py +0 -0
  28. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/migrations/0006_enrollment_models.py +0 -0
  29. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/migrations/0007_replace_uuid_with_learningpathkey.py +0 -0
  30. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/migrations/0008_remove_learningpathstep_relative_due_date_in_days.py +0 -0
  31. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/migrations/0009_remove_learningpath_slug.py +0 -0
  32. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/migrations/0010_learningpath_invite_only.py +0 -0
  33. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/migrations/0011_replace_learningpath_image_url_with_image.py +0 -0
  34. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/migrations/0012_alter_learningpath_subtitle.py +0 -0
  35. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/migrations/0013_enrollment_audit.py +0 -0
  36. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/migrations/0014_remove_learningpath_duration_in_days_and_more.py +0 -0
  37. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/migrations/0015_make_skill_level_optional.py +0 -0
  38. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/migrations/0016_alter_learningpathstep_options.py +0 -0
  39. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/migrations/__init__.py +0 -0
  40. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/models.py +0 -0
  41. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/settings.py +0 -0
  42. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/templates/learning_paths/base.html +0 -0
  43. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths/urls.py +0 -0
  44. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths_plugin.egg-info/SOURCES.txt +0 -0
  45. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths_plugin.egg-info/dependency_links.txt +0 -0
  46. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths_plugin.egg-info/entry_points.txt +0 -0
  47. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths_plugin.egg-info/not-zip-safe +0 -0
  48. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths_plugin.egg-info/requires.txt +0 -0
  49. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/learning_paths_plugin.egg-info/top_level.txt +0 -0
  50. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/pyproject.toml +0 -0
  51. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/requirements/base.in +0 -0
  52. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/requirements/constraints.txt +0 -0
  53. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/setup.cfg +0 -0
  54. {learning_paths_plugin-0.5.0 → learning_paths_plugin-0.6.0}/setup.py +0 -0
@@ -16,6 +16,16 @@ Unreleased
16
16
 
17
17
  *
18
18
 
19
+ 0.6.0 - 2026-10-22
20
+ ******************
21
+
22
+ Fixed
23
+ =====
24
+
25
+ * Process pending Learning Path enrollments when a user activates the account or changes the email address, instead
26
+ of only when the account is created. Enrollments are no longer created for accounts that have not been activated.
27
+ * Link all unclaimed enrollment allowed records to the user, including the inactive ones.
28
+
19
29
  0.5.0 - 2026-08-11
20
30
  ******************
21
31
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: learning-paths-plugin
3
- Version: 0.5.0
3
+ Version: 0.6.0
4
4
  Summary: Learning Paths plugin
5
5
  Home-page: https://github.com/open-craft/learning-paths-plugin
6
6
  Author: OpenCraft
@@ -120,6 +120,16 @@ Unreleased
120
120
 
121
121
  *
122
122
 
123
+ 0.6.0 - 2026-10-22
124
+ ******************
125
+
126
+ Fixed
127
+ =====
128
+
129
+ * Process pending Learning Path enrollments when a user activates the account or changes the email address, instead
130
+ of only when the account is created. Enrollments are no longer created for accounts that have not been activated.
131
+ * Link all unclaimed enrollment allowed records to the user, including the inactive ones.
132
+
123
133
  0.5.0 - 2026-08-11
124
134
  ******************
125
135
 
@@ -2,4 +2,4 @@
2
2
  Learning Paths plugin.
3
3
  """
4
4
 
5
- __version__ = "0.5.0"
5
+ __version__ = "0.6.0"
@@ -4,7 +4,6 @@
4
4
 
5
5
  import logging
6
6
 
7
- from django.db import IntegrityError
8
7
  from django.db.models.signals import post_save
9
8
  from django.dispatch import receiver
10
9
 
@@ -19,63 +18,82 @@ logger = logging.getLogger(__name__)
19
18
 
20
19
  def process_pending_enrollments(sender, instance, created, **kwargs):
21
20
  """
22
- Process pending enrollments after a user instance has been created.
21
+ Process pending enrollments when a user becomes able to claim them.
23
22
 
24
23
  Bulk enrollment API allows enrolling users with just the email. So learners who
25
24
  do not have an account yet would also be enrolled. This information is stored
26
25
  in the LearningPathEnrollmentAllowed model. This signal handler processes such
27
- instances and created the corresponding LearningPathEnrollment objects.
26
+ instances and creates the corresponding LearningPathEnrollment objects.
27
+
28
+ The entries are processed only for active users (i.e., users who confirmed their email address) when:
29
+ 1. The user is created (e.g., when the account is created already activated).
30
+ 2. The user activates the account.
31
+ 3. The user changes the email address (the change is applied after confirming the new email address).
28
32
 
29
33
  Args:
30
34
  sender: User model class.
31
35
  instance: The actual instance being saved.
32
36
  created: A boolean indicating whether this is a creation and not an update.
33
37
  """
34
- if not created:
35
- logger.debug(
36
- "[LearningPaths] Skipping processing of pending enrollments for user %s.",
37
- instance,
38
- )
38
+ # `_changed_fields` is set by the `pre_save` receiver of the User model in edx-platform.
39
+ changed_fields = getattr(instance, "_changed_fields", {})
40
+ if not instance.is_active or not (created or {"email", "is_active"} & changed_fields.keys()):
39
41
  return
40
42
 
41
- logger.info("[LearningPaths] Processing pending enrollments for user %s", instance)
42
- pending_enrollments = LearningPathEnrollmentAllowed.objects.filter(email=instance.email, is_active=True)
43
+ process_pending_enrollments_for_user(instance)
44
+
45
+
46
+ def process_pending_enrollments_for_user(user) -> int:
47
+ """
48
+ Process the LearningPathEnrollmentAllowed entries matching the user's email.
49
+
50
+ Active entries are converted into enrollments, unless the user already has an enrollment (active or not) in the
51
+ learning path. All entries that are not linked to any user yet are deactivated and linked to the user, so they
52
+ cannot be claimed by another account that changes its email to the same address.
53
+
54
+ Returns:
55
+ The number of created enrollments.
56
+ """
57
+ logger.info("[LearningPaths] Processing pending enrollments for user %s", user)
58
+ pending_enrollments = LearningPathEnrollmentAllowed.objects.filter(email=user.email, user__isnull=True)
43
59
  enrollments_created = 0
44
60
 
45
61
  for entry in pending_enrollments:
46
- try:
62
+ enrollment_exists = LearningPathEnrollment.objects.filter(learning_path=entry.learning_path, user=user).exists()
63
+
64
+ if entry.is_active and not enrollment_exists:
47
65
  audit_data = {
48
- "enrolled_by": instance,
66
+ "enrolled_by": user,
49
67
  "state_transition": LearningPathEnrollmentAudit.ALLOWEDTOENROLL_TO_ENROLLED,
50
68
  }
51
69
  if last_allowed_audit := entry.audit.order_by("-created").first():
52
70
  for field in ["reason", "org", "role"]:
53
71
  audit_data[field] = getattr(last_allowed_audit, field, "")
54
72
 
55
- enrollment = LearningPathEnrollment(learning_path=entry.learning_path, user=instance)
73
+ enrollment = LearningPathEnrollment(learning_path=entry.learning_path, user=user)
56
74
  enrollment._audit = audit_data # pylint: disable=protected-access
57
75
  enrollment.save()
58
76
  enrollments_created += 1
59
77
 
60
78
  # Link existing audits from the "allowed to enroll" entry to the new enrollment.
61
79
  entry.audit.update(enrollment=enrollment)
62
-
63
- except IntegrityError: # pragma: no cover
80
+ elif entry.is_active:
64
81
  logger.info(
65
82
  "[LearningPaths] Enrollment already exists for user %s in the learning path %s",
66
- instance,
83
+ user,
67
84
  entry.learning_path.key,
68
85
  )
69
- finally:
70
- entry.is_active = False
71
- entry.user = instance
72
- entry.save()
86
+
87
+ entry.is_active = False
88
+ entry.user = user
89
+ entry.save()
73
90
 
74
91
  logger.info(
75
92
  "[LearningPaths] Processed %d pending Learning Path enrollments for user %s.",
76
93
  enrollments_created,
77
- instance,
94
+ user,
78
95
  )
96
+ return enrollments_created
79
97
 
80
98
 
81
99
  def _create_enrollment_audit(instance: LearningPathEnrollment | LearningPathEnrollmentAllowed, audit_data: dict):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: learning-paths-plugin
3
- Version: 0.5.0
3
+ Version: 0.6.0
4
4
  Summary: Learning Paths plugin
5
5
  Home-page: https://github.com/open-craft/learning-paths-plugin
6
6
  Author: OpenCraft
@@ -120,6 +120,16 @@ Unreleased
120
120
 
121
121
  *
122
122
 
123
+ 0.6.0 - 2026-10-22
124
+ ******************
125
+
126
+ Fixed
127
+ =====
128
+
129
+ * Process pending Learning Path enrollments when a user activates the account or changes the email address, instead
130
+ of only when the account is created. Enrollments are no longer created for accounts that have not been activated.
131
+ * Link all unclaimed enrollment allowed records to the user, including the inactive ones.
132
+
123
133
  0.5.0 - 2026-08-11
124
134
  ******************
125
135