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.
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/CHANGELOG.rst +8 -0
- {learning_paths_plugin-0.3.2/learning_paths_plugin.egg-info → learning_paths_plugin-0.3.3}/PKG-INFO +9 -1
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/__init__.py +1 -1
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/admin.py +4 -12
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/api/v1/serializers.py +3 -9
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/api/v1/urls.py +1 -3
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/api/v1/utils.py +2 -6
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/api/v1/views.py +7 -22
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/compat.py +1 -3
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/keys.py +3 -9
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/models.py +8 -23
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/receivers.py +3 -9
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3/learning_paths_plugin.egg-info}/PKG-INFO +9 -1
- learning_paths_plugin-0.3.3/pyproject.toml +16 -0
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/setup.py +8 -26
- learning_paths_plugin-0.3.2/pyproject.toml +0 -9
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/LICENSE.txt +0 -0
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/MANIFEST.in +0 -0
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/README.rst +0 -0
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/api/__init__.py +0 -0
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/api/urls.py +0 -0
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/api/v1/__init__.py +0 -0
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/api/v1/filters.py +0 -0
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/api/v1/permissions.py +0 -0
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/apps.py +0 -0
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/conftest.py +0 -0
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/migrations/0001_initial.py +0 -0
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/migrations/0002_learningpath_uuid.py +0 -0
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/migrations/0003_learningpath_subtitle.py +0 -0
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/migrations/0004_auto_20240207_1633.py +0 -0
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/migrations/0005_learningpathstep_weight_learningpathgradingcriteria.py +0 -0
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/migrations/0006_enrollment_models.py +0 -0
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/migrations/0007_replace_uuid_with_learningpathkey.py +0 -0
- {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
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/migrations/0009_remove_learningpath_slug.py +0 -0
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/migrations/0010_learningpath_invite_only.py +0 -0
- {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
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/migrations/0012_alter_learningpath_subtitle.py +0 -0
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/migrations/__init__.py +0 -0
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/settings.py +0 -0
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/templates/learning_paths/base.html +0 -0
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/urls.py +0 -0
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths_plugin.egg-info/SOURCES.txt +0 -0
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths_plugin.egg-info/dependency_links.txt +0 -0
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths_plugin.egg-info/entry_points.txt +0 -0
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths_plugin.egg-info/not-zip-safe +0 -0
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths_plugin.egg-info/requires.txt +0 -0
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths_plugin.egg-info/top_level.txt +0 -0
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/requirements/base.in +0 -0
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/requirements/constraints.txt +0 -0
- {learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/setup.cfg +0 -0
{learning_paths_plugin-0.3.2/learning_paths_plugin.egg-info → learning_paths_plugin-0.3.3}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: learning-paths-plugin
|
|
3
|
-
Version: 0.3.
|
|
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
|
|
|
@@ -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):
|
{learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/api/v1/serializers.py
RENAMED
|
@@ -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
|
-
|
|
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.",
|
{learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3/learning_paths_plugin.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: learning-paths-plugin
|
|
3
|
-
Version: 0.3.
|
|
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
|
|
|
@@ -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
|
-
|
|
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",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/api/v1/__init__.py
RENAMED
|
File without changes
|
{learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/api/v1/filters.py
RENAMED
|
File without changes
|
{learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/api/v1/permissions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{learning_paths_plugin-0.3.2 → learning_paths_plugin-0.3.3}/learning_paths/migrations/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|