learning-paths-plugin 0.3.2__tar.gz → 0.3.3__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 (51) hide show
  1. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/CHANGELOG.rst +8 -0
  2. {learning_paths_plugin-0.3.2/learning_paths_plugin.egg-info → learning_paths_plugin-0.3.3}/PKG-INFO +9 -1
  3. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/__init__.py +1 -1
  4. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/admin.py +4 -12
  5. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/api/v1/serializers.py +3 -9
  6. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/api/v1/urls.py +1 -3
  7. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/api/v1/utils.py +2 -6
  8. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/api/v1/views.py +7 -22
  9. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/compat.py +1 -3
  10. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/keys.py +3 -9
  11. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/models.py +8 -23
  12. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/receivers.py +3 -9
  13. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3/learning_paths_plugin.egg-info}/PKG-INFO +9 -1
  14. learning_paths_plugin-0.3.3/pyproject.toml +16 -0
  15. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/setup.py +8 -26
  16. learning_paths_plugin-0.3.2/pyproject.toml +0 -9
  17. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/LICENSE.txt +0 -0
  18. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/MANIFEST.in +0 -0
  19. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/README.rst +0 -0
  20. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/api/__init__.py +0 -0
  21. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/api/urls.py +0 -0
  22. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/api/v1/__init__.py +0 -0
  23. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/api/v1/filters.py +0 -0
  24. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/api/v1/permissions.py +0 -0
  25. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/apps.py +0 -0
  26. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/conftest.py +0 -0
  27. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/migrations/0001_initial.py +0 -0
  28. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/migrations/0002_learningpath_uuid.py +0 -0
  29. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/migrations/0003_learningpath_subtitle.py +0 -0
  30. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/migrations/0004_auto_20240207_1633.py +0 -0
  31. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/migrations/0005_learningpathstep_weight_learningpathgradingcriteria.py +0 -0
  32. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/migrations/0006_enrollment_models.py +0 -0
  33. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/migrations/0007_replace_uuid_with_learningpathkey.py +0 -0
  34. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/migrations/0008_remove_learningpathstep_relative_due_date_in_days.py +0 -0
  35. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/migrations/0009_remove_learningpath_slug.py +0 -0
  36. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/migrations/0010_learningpath_invite_only.py +0 -0
  37. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/migrations/0011_replace_learningpath_image_url_with_image.py +0 -0
  38. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/migrations/0012_alter_learningpath_subtitle.py +0 -0
  39. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/migrations/__init__.py +0 -0
  40. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/settings.py +0 -0
  41. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/templates/learning_paths/base.html +0 -0
  42. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/urls.py +0 -0
  43. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths_plugin.egg-info/SOURCES.txt +0 -0
  44. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths_plugin.egg-info/dependency_links.txt +0 -0
  45. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths_plugin.egg-info/entry_points.txt +0 -0
  46. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths_plugin.egg-info/not-zip-safe +0 -0
  47. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths_plugin.egg-info/requires.txt +0 -0
  48. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths_plugin.egg-info/top_level.txt +0 -0
  49. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/requirements/base.in +0 -0
  50. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/requirements/constraints.txt +0 -0
  51. {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/setup.cfg +0 -0
@@ -16,6 +16,14 @@ Unreleased
16
16
 
17
17
  *
18
18
 
19
+ 0.3.3 - 2025-05-23
20
+ ******************
21
+
22
+ Changed
23
+ =======
24
+
25
+ * Changed line length from 80 to 120 characters.
26
+
19
27
  0.3.2 - 2025-05-02
20
28
  ******************
21
29
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: learning-paths-plugin
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: Learning Paths plugin
5
5
  Home-page: https://github.com/open-craft/learning-paths-plugin
6
6
  Author: OpenCraft
@@ -121,6 +121,14 @@ Unreleased
121
121
 
122
122
  *
123
123
 
124
+ 0.3.3 - 2025-05-23
125
+ ******************
126
+
127
+ Changed
128
+ =======
129
+
130
+ * Changed line length from 80 to 120 characters.
131
+
124
132
  0.3.2 - 2025-05-02
125
133
  ******************
126
134
 
@@ -2,4 +2,4 @@
2
2
  Learning Paths plugin.
3
3
  """
4
4
 
5
- __version__ = "0.3.2"
5
+ __version__ = "0.3.3"
@@ -65,9 +65,7 @@ class LearningPathStepForm(forms.ModelForm):
65
65
  """Lazily fetch course keys to avoid calling compat code in all environments."""
66
66
  super().__init__(*args, **kwargs)
67
67
  self._course_keys = get_course_keys_with_outlines()
68
- self.fields["course_key"].widget = CourseKeyDatalistWidget(
69
- choices=self._course_keys
70
- )
68
+ self.fields["course_key"].widget = CourseKeyDatalistWidget(choices=self._course_keys)
71
69
 
72
70
  course_key = forms.CharField(label=_("Course"))
73
71
 
@@ -77,9 +75,7 @@ class LearningPathStepForm(forms.ModelForm):
77
75
  valid_keys = {str(key).strip() for key in self._course_keys}
78
76
 
79
77
  if course_key not in valid_keys:
80
- raise ValidationError(
81
- _("Invalid course key. Please select a course from the suggestions.")
82
- )
78
+ raise ValidationError(_("Invalid course key. Please select a course from the suggestions."))
83
79
 
84
80
  return course_key
85
81
 
@@ -135,9 +131,7 @@ class BulkEnrollUsersForm(forms.ModelForm):
135
131
  found_usernames = list(users.values_list("username", flat=True))
136
132
  invalid_usernames = set(usernames) - set(found_usernames)
137
133
  if invalid_usernames:
138
- raise ValidationError(
139
- f"The following usernames are not valid: {', '.join(invalid_usernames)}"
140
- )
134
+ raise ValidationError(f"The following usernames are not valid: {', '.join(invalid_usernames)}")
141
135
  return users
142
136
 
143
137
 
@@ -179,9 +173,7 @@ class LearningPathAdmin(admin.ModelAdmin):
179
173
  super().save_related(request, form, formsets, change)
180
174
  with transaction.atomic():
181
175
  for user in form.cleaned_data["usernames"]:
182
- LearningPathEnrollment.objects.get_or_create(
183
- user=user, learning_path=form.instance
184
- )
176
+ LearningPathEnrollment.objects.get_or_create(user=user, learning_path=form.instance)
185
177
 
186
178
 
187
179
  class SkillAdmin(admin.ModelAdmin):
@@ -104,9 +104,7 @@ class LearningPathListSerializer(serializers.ModelSerializer):
104
104
  """Serializer for the learning path list."""
105
105
 
106
106
  steps = LearningPathStepSerializer(many=True, read_only=True)
107
- required_completion = serializers.FloatField(
108
- source="grading_criteria.required_completion", read_only=True
109
- )
107
+ required_completion = serializers.FloatField(source="grading_criteria.required_completion", read_only=True)
110
108
  is_enrolled = serializers.SerializerMethodField()
111
109
  invite_only = serializers.BooleanField()
112
110
  image = serializers.ImageField(read_only=True)
@@ -168,12 +166,8 @@ class LearningPathDetailSerializer(LearningPathListSerializer):
168
166
  Serializer for learning path details.
169
167
  """
170
168
 
171
- required_skills = RequiredSkillSerializer(
172
- source="requiredskill_set", many=True, read_only=True
173
- )
174
- acquired_skills = AcquiredSkillSerializer(
175
- source="acquiredskill_set", many=True, read_only=True
176
- )
169
+ required_skills = RequiredSkillSerializer(source="requiredskill_set", many=True, read_only=True)
170
+ acquired_skills = AcquiredSkillSerializer(source="acquiredskill_set", many=True, read_only=True)
177
171
 
178
172
  class Meta(LearningPathListSerializer.Meta):
179
173
  fields = LearningPathListSerializer.Meta.fields + [
@@ -16,9 +16,7 @@ from learning_paths.api.v1.views import (
16
16
  from learning_paths.keys import COURSE_KEY_URL_PATTERN, LEARNING_PATH_URL_PATTERN
17
17
 
18
18
  router = routers.SimpleRouter()
19
- router.register(
20
- r"programs", LearningPathAsProgramViewSet, basename="learning-path-as-program"
21
- )
19
+ router.register(r"programs", LearningPathAsProgramViewSet, basename="learning-path-as-program")
22
20
  router.register(r"learning-paths", LearningPathViewSet, basename="learning-path")
23
21
 
24
22
  urlpatterns = router.urls + [
@@ -29,9 +29,7 @@ def get_course_completion(username: str, course_key: CourseKey, client: Any) ->
29
29
  if err.response.status_code == 404:
30
30
  return 0.0
31
31
  else:
32
- raise APIException(
33
- f"Error fetching completion for course {course_id}: {err}"
34
- ) from err
32
+ raise APIException(f"Error fetching completion for course {course_id}: {err}") from err
35
33
 
36
34
  if data and data.get("results"):
37
35
  return data["results"][0]["completion"]["percent"]
@@ -51,9 +49,7 @@ def get_aggregate_progress(user, learning_path):
51
49
  total_completion = 0.0
52
50
 
53
51
  for step in steps:
54
- course_completion = get_course_completion(
55
- user.username, step.course_key, client
56
- )
52
+ course_completion = get_course_completion(user.username, step.course_key, client)
57
53
  total_completion += course_completion
58
54
 
59
55
  total_courses = len(steps)
@@ -152,9 +152,7 @@ class LearningPathViewSet(viewsets.ReadOnlyModelViewSet):
152
152
  Get all learning paths and prefetch the related data.
153
153
  """
154
154
  user = self.request.user
155
- queryset = LearningPath.objects.get_paths_visible_to_user(
156
- user
157
- ).prefetch_related(
155
+ queryset = LearningPath.objects.get_paths_visible_to_user(user).prefetch_related(
158
156
  "steps",
159
157
  "grading_criteria",
160
158
  )
@@ -203,9 +201,7 @@ class LearningPathEnrollmentView(APIView):
203
201
  """
204
202
  learning_path = self._get_learning_path(learning_path_key_str)
205
203
 
206
- enrollments = LearningPathEnrollment.objects.filter(
207
- learning_path=learning_path, is_active=True
208
- )
204
+ enrollments = LearningPathEnrollment.objects.filter(learning_path=learning_path, is_active=True)
209
205
 
210
206
  if request.user.is_staff:
211
207
  if username := request.query_params.get("username"):
@@ -233,18 +229,14 @@ class LearningPathEnrollmentView(APIView):
233
229
  username = request.data.get("username")
234
230
  user = get_object_or_404(User, username=username) if username else request.user
235
231
 
236
- enrollment, created = LearningPathEnrollment.objects.get_or_create(
237
- learning_path=learning_path, user=user
238
- )
232
+ enrollment, created = LearningPathEnrollment.objects.get_or_create(learning_path=learning_path, user=user)
239
233
  if created:
240
234
  return Response(
241
235
  LearningPathEnrollmentSerializer(enrollment).data,
242
236
  status=status.HTTP_201_CREATED,
243
237
  )
244
238
  if enrollment.is_active:
245
- return Response(
246
- {"detail": "Enrollment exists."}, status=status.HTTP_409_CONFLICT
247
- )
239
+ return Response({"detail": "Enrollment exists."}, status=status.HTTP_409_CONFLICT)
248
240
 
249
241
  enrollment.is_active = True
250
242
  enrollment.enrolled_at = datetime.now(timezone.utc)
@@ -276,10 +268,7 @@ class LearningPathEnrollmentView(APIView):
276
268
  user=user,
277
269
  )
278
270
 
279
- if (
280
- not request.user.is_staff
281
- and not settings.LEARNING_PATHS_ALLOW_SELF_UNENROLLMENT
282
- ):
271
+ if not request.user.is_staff and not settings.LEARNING_PATHS_ALLOW_SELF_UNENROLLMENT:
283
272
  raise PermissionDenied
284
273
 
285
274
  enrollment.is_active = False
@@ -360,9 +349,7 @@ class BulkEnrollView(APIView):
360
349
 
361
350
  # Create LearningPathEnrollment for existing users
362
351
  for user in existing_users:
363
- enrollment = LearningPathEnrollment.objects.filter(
364
- user=user, learning_path=learning_path
365
- ).first()
352
+ enrollment = LearningPathEnrollment.objects.filter(user=user, learning_path=learning_path).first()
366
353
  enrolled_now = False
367
354
  if not enrollment:
368
355
  enrollment = LearningPathEnrollment(
@@ -412,9 +399,7 @@ class LearningPathCourseEnrollmentView(APIView):
412
399
  :raises: Http404 if the learning path is not found or the user does not have access.
413
400
  """
414
401
  return get_object_or_404(
415
- LearningPath.objects.get_paths_visible_to_user(self.request.user).filter(
416
- is_enrolled=True
417
- ),
402
+ LearningPath.objects.get_paths_visible_to_user(self.request.user).filter(is_enrolled=True),
418
403
  key=learning_path_key_str,
419
404
  )
420
405
 
@@ -72,7 +72,5 @@ def enroll_user_in_course(user: AbstractBaseUser, course_key: CourseKey) -> bool
72
72
  CourseEnrollment.enroll(user, course_key)
73
73
  return True
74
74
  except CourseEnrollmentException as exc:
75
- log.exception(
76
- "Failed to enroll user %s in course %s: %s", user, course_key, exc
77
- )
75
+ log.exception("Failed to enroll user %s in course %s: %s", user, course_key, exc)
78
76
  return False
@@ -12,15 +12,11 @@ from opaque_keys.edx.keys import LearningContextKey
12
12
 
13
13
  COURSE_KEY_NAMESPACE = "course-v1"
14
14
  COURSE_KEY_PATTERN = r"([^+]+)\+([^+]+)\+([^+]+)"
15
- COURSE_KEY_URL_PATTERN = (
16
- rf"(?P<course_key_str>{COURSE_KEY_NAMESPACE}:{COURSE_KEY_PATTERN})"
17
- )
15
+ COURSE_KEY_URL_PATTERN = rf"(?P<course_key_str>{COURSE_KEY_NAMESPACE}:{COURSE_KEY_PATTERN})"
18
16
 
19
17
  LEARNING_PATH_NAMESPACE = "path-v1"
20
18
  LEARNING_PATH_PATTERN = r"([^+]+)\+([^+]+)\+([^+]+)\+([^+]+)"
21
- LEARNING_PATH_URL_PATTERN = (
22
- rf"(?P<learning_path_key_str>{LEARNING_PATH_NAMESPACE}:{LEARNING_PATH_PATTERN})"
23
- )
19
+ LEARNING_PATH_URL_PATTERN = rf"(?P<learning_path_key_str>{LEARNING_PATH_NAMESPACE}:{LEARNING_PATH_PATTERN})"
24
20
 
25
21
 
26
22
  class LearningPathKey(LearningContextKey):
@@ -52,9 +48,7 @@ class LearningPathKey(LearningContextKey):
52
48
 
53
49
  def _to_string(self) -> str:
54
50
  """Return a string representing this key."""
55
- return "+".join(
56
- [self.org, self.number, self.run, self.group] # pylint: disable=no-member
57
- )
51
+ return "+".join([self.org, self.number, self.run, self.group]) # pylint: disable=no-member
58
52
 
59
53
 
60
54
  class LearningPathKeyField(LearningContextKeyField):
@@ -114,16 +114,12 @@ class LearningPath(TimeStampedModel):
114
114
  blank=True,
115
115
  null=True,
116
116
  verbose_name=_("Duration (days)"),
117
- help_text=_(
118
- "Approximate time (in days) it should take to complete this Learning Path."
119
- ),
117
+ help_text=_("Approximate time (in days) it should take to complete this Learning Path."),
120
118
  )
121
119
  sequential = models.BooleanField(
122
120
  default=False,
123
121
  verbose_name=_("Is sequential"),
124
- help_text=_(
125
- "Whether the courses in this Learning Path are meant to be taken sequentially."
126
- ),
122
+ help_text=_("Whether the courses in this Learning Path are meant to be taken sequentially."),
127
123
  )
128
124
  # Note: the enrolled learners will be able to self-enroll in all courses
129
125
  # (steps) of the learning path. To avoid mistakes of making the courses
@@ -132,9 +128,7 @@ class LearningPath(TimeStampedModel):
132
128
  invite_only = models.BooleanField(
133
129
  default=True,
134
130
  verbose_name=_("Invite only"),
135
- help_text=_(
136
- "If enabled, only staff can enroll users and only enrolled users can see the learning path."
137
- ),
131
+ help_text=_("If enabled, only staff can enroll users and only enrolled users can see the learning path."),
138
132
  )
139
133
  enrolled_users = models.ManyToManyField(User, through="LearningPathEnrollment")
140
134
  tracker = FieldTracker(fields=["image"])
@@ -193,16 +187,12 @@ class LearningPathStep(TimeStampedModel):
193
187
  unique_together = ("learning_path", "course_key")
194
188
 
195
189
  course_key = CourseKeyField(max_length=255)
196
- learning_path = models.ForeignKey(
197
- LearningPath, related_name="steps", on_delete=models.CASCADE
198
- )
190
+ learning_path = models.ForeignKey(LearningPath, related_name="steps", on_delete=models.CASCADE)
199
191
  order = models.PositiveIntegerField(
200
192
  blank=True,
201
193
  null=True,
202
194
  verbose_name=_("Sequential order"),
203
- help_text=_(
204
- "Ordinal position of this step in the sequence of the Learning Path, if applicable."
205
- ),
195
+ help_text=_("Ordinal position of this step in the sequence of the Learning Path, if applicable."),
206
196
  )
207
197
  weight = models.FloatField(
208
198
  default=1.0,
@@ -259,9 +249,7 @@ class LearningPathSkill(TimeStampedModel):
259
249
 
260
250
  learning_path = models.ForeignKey(LearningPath, on_delete=models.CASCADE)
261
251
  skill = models.ForeignKey(Skill, on_delete=models.CASCADE)
262
- level = models.PositiveIntegerField(
263
- help_text=_("The skill level associated with this course.")
264
- )
252
+ level = models.PositiveIntegerField(help_text=_("The skill level associated with this course."))
265
253
 
266
254
  def __str__(self):
267
255
  """User-friendly string representation of this model."""
@@ -305,8 +293,7 @@ class LearningPathEnrollment(TimeStampedModel):
305
293
  enrolled_at = models.DateTimeField(
306
294
  auto_now_add=True,
307
295
  help_text=_(
308
- "Timestamp of enrollment or un-enrollment. To be explicitly set when performing"
309
- " a learner enrollment."
296
+ "Timestamp of enrollment or un-enrollment. To be explicitly set when performing a learner enrollment."
310
297
  ),
311
298
  )
312
299
 
@@ -331,9 +318,7 @@ class LearningPathGradingCriteria(models.Model):
331
318
  .. no_pii:
332
319
  """
333
320
 
334
- learning_path = models.OneToOneField(
335
- LearningPath, related_name="grading_criteria", on_delete=models.CASCADE
336
- )
321
+ learning_path = models.OneToOneField(LearningPath, related_name="grading_criteria", on_delete=models.CASCADE)
337
322
  required_completion = models.FloatField(
338
323
  default=0.80,
339
324
  help_text=(
@@ -7,9 +7,7 @@ from learning_paths.models import LearningPathEnrollment, LearningPathEnrollment
7
7
  logger = logging.getLogger(__name__)
8
8
 
9
9
 
10
- def process_pending_enrollments(
11
- sender, instance, created, **kwargs
12
- ): # pylint: disable=unused-argument
10
+ def process_pending_enrollments(sender, instance, created, **kwargs): # pylint: disable=unused-argument
13
11
  """
14
12
  Process pending enrollments after a user instance has been created.
15
13
 
@@ -31,9 +29,7 @@ def process_pending_enrollments(
31
29
  return
32
30
 
33
31
  logger.info("[LearningPaths] Processing pending enrollments for user %s", instance)
34
- pending_enrollments = LearningPathEnrollmentAllowed.objects.filter(
35
- email=instance.email
36
- ).all()
32
+ pending_enrollments = LearningPathEnrollmentAllowed.objects.filter(email=instance.email).all()
37
33
 
38
34
  enrollments = []
39
35
 
@@ -41,9 +37,7 @@ def process_pending_enrollments(
41
37
  entry.user = instance
42
38
  entry.save()
43
39
 
44
- enrollments.append(
45
- LearningPathEnrollment(learning_path=entry.learning_path, user=instance)
46
- )
40
+ enrollments.append(LearningPathEnrollment(learning_path=entry.learning_path, user=instance))
47
41
  new_enrollments = LearningPathEnrollment.objects.bulk_create(enrollments)
48
42
  logger.info(
49
43
  "[LearningPaths] Processed %d pending Learning Path enrollments for user %s.",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: learning-paths-plugin
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: Learning Paths plugin
5
5
  Home-page: https://github.com/open-craft/learning-paths-plugin
6
6
  Author: OpenCraft
@@ -121,6 +121,14 @@ Unreleased
121
121
 
122
122
  *
123
123
 
124
+ 0.3.3 - 2025-05-23
125
+ ******************
126
+
127
+ Changed
128
+ =======
129
+
130
+ * Changed line length from 80 to 120 characters.
131
+
124
132
  0.3.2 - 2025-05-02
125
133
  ******************
126
134
 
@@ -0,0 +1,16 @@
1
+ [tool.black]
2
+ line-length = 120
3
+ exclude = '''
4
+ /(
5
+ \.git
6
+ | \.hg
7
+ | \.mypy_cache
8
+ | \.tox
9
+ | \.venv
10
+ | _build
11
+ | buck-out
12
+ | build
13
+ | dist
14
+ | migrations
15
+ )/
16
+ '''
@@ -62,13 +62,10 @@ def load_requirements(*requirements_paths):
62
62
  re_package_name_base_chars = r"a-zA-Z0-9\-_." # chars allowed in base package name
63
63
  # Two groups: name[maybe,extras], and optionally a constraint
64
64
  requirement_line_regex = re.compile(
65
- r"([%s]+(?:\[[%s,\s]+\])?)([<>=][^#\s]+)?"
66
- % (re_package_name_base_chars, re_package_name_base_chars)
65
+ r"([%s]+(?:\[[%s,\s]+\])?)([<>=][^#\s]+)?" % (re_package_name_base_chars, re_package_name_base_chars)
67
66
  )
68
67
 
69
- def add_version_constraint_or_raise(
70
- current_line, current_requirements, add_if_not_present
71
- ):
68
+ def add_version_constraint_or_raise(current_line, current_requirements, add_if_not_present):
72
69
  regex_match = requirement_line_regex.match(current_line)
73
70
  if regex_match:
74
71
  package = regex_match.group(1)
@@ -77,10 +74,7 @@ def load_requirements(*requirements_paths):
77
74
  existing_version_constraints = current_requirements.get(package, None)
78
75
  # It's fine to add constraints to an unconstrained package,
79
76
  # but raise an error if there are already constraints in place.
80
- if (
81
- existing_version_constraints
82
- and existing_version_constraints != version_constraints
83
- ):
77
+ if existing_version_constraints and existing_version_constraints != version_constraints:
84
78
  raise BaseException(
85
79
  f"Multiple constraint definitions found for {package}:"
86
80
  f' "{existing_version_constraints}" and "{version_constraints}".'
@@ -98,11 +92,7 @@ def load_requirements(*requirements_paths):
98
92
  if is_requirement(line):
99
93
  add_version_constraint_or_raise(line, requirements, True)
100
94
  if line and line.startswith("-c") and not line.startswith("-c http"):
101
- constraint_files.add(
102
- os.path.dirname(path)
103
- + "/"
104
- + line.split("#")[0].replace("-c", "").strip()
105
- )
95
+ constraint_files.add(os.path.dirname(path) + "/" + line.split("#")[0].replace("-c", "").strip())
106
96
 
107
97
  # process constraint files: add constraints to existing requirements
108
98
  for constraint_file in constraint_files:
@@ -112,9 +102,7 @@ def load_requirements(*requirements_paths):
112
102
  add_version_constraint_or_raise(line, requirements, False)
113
103
 
114
104
  # process back into list of pkg><=constraints strings
115
- constrained_requirements = [
116
- f'{pkg}{version or ""}' for (pkg, version) in sorted(requirements.items())
117
- ]
105
+ constrained_requirements = [f'{pkg}{version or ""}' for (pkg, version) in sorted(requirements.items())]
118
106
  return constrained_requirements
119
107
 
120
108
 
@@ -126,9 +114,7 @@ def is_requirement(line):
126
114
  bool: True if the line is not blank, a comment,
127
115
  a URL, or an included file
128
116
  """
129
- return (
130
- line and line.strip() and not line.startswith(("-r", "#", "-e", "git+", "-c"))
131
- )
117
+ return line and line.strip() and not line.startswith(("-r", "#", "-e", "git+", "-c"))
132
118
 
133
119
 
134
120
  VERSION = get_version("learning_paths", "__init__.py")
@@ -139,12 +125,8 @@ if sys.argv[-1] == "tag":
139
125
  os.system("git push --tags")
140
126
  sys.exit()
141
127
 
142
- README = open(
143
- os.path.join(os.path.dirname(__file__), "README.rst"), encoding="utf8"
144
- ).read()
145
- CHANGELOG = open(
146
- os.path.join(os.path.dirname(__file__), "CHANGELOG.rst"), encoding="utf8"
147
- ).read()
128
+ README = open(os.path.join(os.path.dirname(__file__), "README.rst"), encoding="utf8").read()
129
+ CHANGELOG = open(os.path.join(os.path.dirname(__file__), "CHANGELOG.rst"), encoding="utf8").read()
148
130
 
149
131
  setup(
150
132
  name="learning-paths-plugin",
@@ -1,9 +0,0 @@
1
- [tool.black]
2
- exclude = '''
3
- /(
4
- \.git
5
- | \.tox
6
- | _build
7
- | migrations
8
- )/
9
- '''