learning-credentials 0.3.0rc3__tar.gz → 0.3.0rc10__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 (62) hide show
  1. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/CHANGELOG.rst +15 -0
  2. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/MANIFEST.in +0 -2
  3. {learning_credentials-0.3.0rc3/learning_credentials.egg-info → learning_credentials-0.3.0rc10}/PKG-INFO +19 -3
  4. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/README.rst +1 -1
  5. learning_credentials-0.3.0rc10/learning_credentials/api/v1/permissions.py +81 -0
  6. learning_credentials-0.3.0rc10/learning_credentials/api/v1/urls.py +13 -0
  7. learning_credentials-0.3.0rc10/learning_credentials/api/v1/views.py +83 -0
  8. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/learning_credentials/apps.py +7 -10
  9. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/learning_credentials/compat.py +1 -7
  10. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/learning_credentials/generators.py +0 -1
  11. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/learning_credentials/models.py +10 -44
  12. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/learning_credentials/processors.py +40 -148
  13. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/learning_credentials/settings/common.py +5 -2
  14. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/learning_credentials/settings/production.py +5 -2
  15. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10/learning_credentials.egg-info}/PKG-INFO +19 -3
  16. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/learning_credentials.egg-info/SOURCES.txt +0 -7
  17. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/learning_credentials.egg-info/entry_points.txt +0 -3
  18. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/learning_credentials.egg-info/requires.txt +2 -1
  19. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/pyproject.toml +28 -12
  20. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/tests/test_generators.py +3 -2
  21. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/tests/test_models.py +1 -28
  22. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/tests/test_processors.py +32 -281
  23. learning_credentials-0.3.0rc10/tests/test_views.py +304 -0
  24. learning_credentials-0.3.0rc3/learning_credentials/api/v1/permissions.py +0 -63
  25. learning_credentials-0.3.0rc3/learning_credentials/api/v1/serializers.py +0 -74
  26. learning_credentials-0.3.0rc3/learning_credentials/api/v1/urls.py +0 -17
  27. learning_credentials-0.3.0rc3/learning_credentials/api/v1/views.py +0 -357
  28. learning_credentials-0.3.0rc3/learning_credentials/core_api.py +0 -77
  29. learning_credentials-0.3.0rc3/learning_credentials/public/css/credentials_xblock.css +0 -7
  30. learning_credentials-0.3.0rc3/learning_credentials/public/html/credentials_xblock.html +0 -48
  31. learning_credentials-0.3.0rc3/learning_credentials/public/js/credentials_xblock.js +0 -23
  32. learning_credentials-0.3.0rc3/learning_credentials/xblocks.py +0 -85
  33. learning_credentials-0.3.0rc3/tests/test_serializers.py +0 -205
  34. learning_credentials-0.3.0rc3/tests/test_views.py +0 -665
  35. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/LICENSE.txt +0 -0
  36. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/learning_credentials/__init__.py +0 -0
  37. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/learning_credentials/admin.py +0 -0
  38. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/learning_credentials/api/__init__.py +0 -0
  39. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/learning_credentials/api/urls.py +0 -0
  40. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/learning_credentials/api/v1/__init__.py +0 -0
  41. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/learning_credentials/conf/locale/config.yaml +0 -0
  42. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/learning_credentials/exceptions.py +0 -0
  43. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/learning_credentials/migrations/0001_initial.py +0 -0
  44. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/learning_credentials/migrations/0002_migrate_to_learning_credentials.py +0 -0
  45. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/learning_credentials/migrations/0003_rename_certificates_to_credentials.py +0 -0
  46. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/learning_credentials/migrations/0004_replace_course_keys_with_learning_context_keys.py +0 -0
  47. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/learning_credentials/migrations/0005_rename_processors_and_generators.py +0 -0
  48. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/learning_credentials/migrations/0006_cleanup_openedx_certificates_tables.py +0 -0
  49. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/learning_credentials/migrations/__init__.py +0 -0
  50. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/learning_credentials/settings/__init__.py +0 -0
  51. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/learning_credentials/tasks.py +0 -0
  52. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/learning_credentials/templates/learning_credentials/base.html +0 -0
  53. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/learning_credentials/templates/learning_credentials/edx_ace/certificate_generated/email/body.html +0 -0
  54. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/learning_credentials/templates/learning_credentials/edx_ace/certificate_generated/email/body.txt +0 -0
  55. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/learning_credentials/templates/learning_credentials/edx_ace/certificate_generated/email/from_name.txt +0 -0
  56. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/learning_credentials/templates/learning_credentials/edx_ace/certificate_generated/email/head.html +0 -0
  57. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/learning_credentials/templates/learning_credentials/edx_ace/certificate_generated/email/subject.txt +0 -0
  58. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/learning_credentials/urls.py +0 -0
  59. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/learning_credentials.egg-info/dependency_links.txt +0 -0
  60. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/learning_credentials.egg-info/top_level.txt +0 -0
  61. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/setup.cfg +0 -0
  62. {learning_credentials-0.3.0rc3 → learning_credentials-0.3.0rc10}/tests/test_tasks.py +0 -0
@@ -16,6 +16,21 @@ Unreleased
16
16
 
17
17
  *
18
18
 
19
+ 0.3.0 - 2025-09-17
20
+ ******************
21
+
22
+ Added
23
+ =====
24
+
25
+ * REST API endpoint to check if credentials are configured for a learning context.
26
+
27
+ 0.2.4 - 2025-09-07
28
+
29
+ Added
30
+ =====
31
+
32
+ * Option to customize the learner's name size on the PDF certificate.
33
+
19
34
  0.2.3 - 2025-08-18
20
35
 
21
36
  Modified
@@ -1,6 +1,4 @@
1
1
  include CHANGELOG.rst
2
2
  include LICENSE.txt
3
3
  include README.rst
4
- include requirements/base.in
5
- include requirements/constraints.txt
6
4
  recursive-include learning_credentials *.html *.png *.gif *.js *.css *.jpg *.jpeg *.svg *.txt
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: learning-credentials
3
- Version: 0.3.0rc3
3
+ Version: 0.3.0rc10
4
4
  Summary: A pluggable service for preparing Open edX credentials.
5
5
  Author-email: OpenCraft <help@opencraft.com>
6
6
  License-Expression: AGPL-3.0-or-later
@@ -21,6 +21,8 @@ Description-Content-Type: text/x-rst
21
21
  License-File: LICENSE.txt
22
22
  Requires-Dist: django
23
23
  Requires-Dist: django-model-utils
24
+ Requires-Dist: edx-api-doc-tools
25
+ Requires-Dist: edx-django-utils
24
26
  Requires-Dist: edx-opaque-keys
25
27
  Requires-Dist: celery
26
28
  Requires-Dist: django-celery-beat
@@ -31,7 +33,6 @@ Requires-Dist: pypdf
31
33
  Requires-Dist: reportlab
32
34
  Requires-Dist: openedx-completion-aggregator
33
35
  Requires-Dist: edx_ace
34
- Requires-Dist: edx-api-doc-tools
35
36
  Requires-Dist: learning-paths-plugin>=0.3.4
36
37
  Dynamic: license-file
37
38
 
@@ -123,7 +124,7 @@ file in this repo.
123
124
  Reporting Security Issues
124
125
  *************************
125
126
 
126
- Please do not report security issues in public. Please email security@openedx.org.
127
+ Please do not report security issues in public. Please email help@opencraft.com.
127
128
 
128
129
  .. |pypi-badge| image:: https://img.shields.io/pypi/v/learning-credentials.svg
129
130
  :target: https://pypi.python.org/pypi/learning-credentials/
@@ -175,6 +176,21 @@ Unreleased
175
176
 
176
177
  *
177
178
 
179
+ 0.3.0 - 2025-09-17
180
+ ******************
181
+
182
+ Added
183
+ =====
184
+
185
+ * REST API endpoint to check if credentials are configured for a learning context.
186
+
187
+ 0.2.4 - 2025-09-07
188
+
189
+ Added
190
+ =====
191
+
192
+ * Option to customize the learner's name size on the PDF certificate.
193
+
178
194
  0.2.3 - 2025-08-18
179
195
 
180
196
  Modified
@@ -86,7 +86,7 @@ file in this repo.
86
86
  Reporting Security Issues
87
87
  *************************
88
88
 
89
- Please do not report security issues in public. Please email security@openedx.org.
89
+ Please do not report security issues in public. Please email help@opencraft.com.
90
90
 
91
91
  .. |pypi-badge| image:: https://img.shields.io/pypi/v/learning-credentials.svg
92
92
  :target: https://pypi.python.org/pypi/learning-credentials/
@@ -0,0 +1,81 @@
1
+ """Django REST framework permissions."""
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from django.db.models import Q
6
+ from learning_paths.models import LearningPath
7
+ from opaque_keys import InvalidKeyError
8
+ from opaque_keys.edx.keys import LearningContextKey
9
+ from rest_framework.exceptions import NotFound, ParseError
10
+ from rest_framework.permissions import BasePermission
11
+
12
+ from learning_credentials.compat import get_course_enrollments
13
+
14
+ if TYPE_CHECKING:
15
+ from django.contrib.auth.models import User
16
+ from learning_paths.keys import LearningPathKey
17
+ from opaque_keys.edx.keys import CourseKey
18
+ from rest_framework.request import Request
19
+ from rest_framework.views import APIView
20
+
21
+
22
+ class CanAccessLearningContext(BasePermission):
23
+ """Permission to allow access to learning context if the user is enrolled."""
24
+
25
+ def has_permission(self, request: "Request", view: "APIView") -> bool:
26
+ """Check if the user can access the learning context."""
27
+ try:
28
+ key = view.kwargs.get("learning_context_key") or request.query_params.get("learning_context_key")
29
+ learning_context_key = LearningContextKey.from_string(key)
30
+ except InvalidKeyError as e:
31
+ msg = "Invalid learning context key."
32
+ raise ParseError(msg) from e
33
+
34
+ if request.user.is_staff:
35
+ return True
36
+
37
+ if learning_context_key.is_course:
38
+ if self._can_access_course(learning_context_key, request.user):
39
+ return True
40
+
41
+ msg = "Course not found or user does not have access."
42
+ raise NotFound(msg)
43
+
44
+ # For learning paths, check enrollment or if it's not invite-only.
45
+ if self._can_access_learning_path(learning_context_key, request.user):
46
+ return True
47
+
48
+ msg = "Learning path not found or user does not have access."
49
+ raise NotFound(msg)
50
+
51
+ def _can_access_course(self, course_key: "CourseKey", user: "User") -> bool:
52
+ """Check if user can access a course."""
53
+ # Check if user is enrolled in the course.
54
+ if get_course_enrollments(course_key, user.id): # ty: ignore[unresolved-attribute]
55
+ return True
56
+
57
+ # Check if the course is a part of a learning path the user can access.
58
+ return self._can_access_course_via_learning_path(course_key, user)
59
+
60
+ def _get_accessible_learning_paths_filter(self, user: "User") -> Q:
61
+ """Get Q filter for learning paths that the user can access."""
62
+ return Q(invite_only=False) | Q(learningpathenrollment__user=user, learningpathenrollment__is_active=True)
63
+
64
+ def _can_access_course_via_learning_path(self, course_key: "CourseKey", user: "User") -> bool:
65
+ """Check if user can access a course through learning path membership."""
66
+ accessible_paths = (
67
+ LearningPath.objects.filter(steps__course_key=course_key)
68
+ .filter(self._get_accessible_learning_paths_filter(user))
69
+ .distinct()
70
+ )
71
+
72
+ return accessible_paths.exists()
73
+
74
+ def _can_access_learning_path(self, learning_path_key: "LearningPathKey", user: "User") -> bool:
75
+ """Check if user can access a learning path."""
76
+ # Single query to check if learning path exists and user can access it
77
+ accessible_path = LearningPath.objects.filter(key=learning_path_key).filter(
78
+ self._get_accessible_learning_paths_filter(user)
79
+ )
80
+
81
+ return accessible_path.exists()
@@ -0,0 +1,13 @@
1
+ """API v1 URLs."""
2
+
3
+ from django.urls import path
4
+
5
+ from .views import CredentialConfigurationCheckView
6
+
7
+ urlpatterns = [
8
+ path(
9
+ 'configured/<str:learning_context_key>/',
10
+ CredentialConfigurationCheckView.as_view(),
11
+ name='credential-configuration-check',
12
+ ),
13
+ ]
@@ -0,0 +1,83 @@
1
+ """API views for Learning Credentials."""
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ import edx_api_doc_tools as apidocs
6
+ from edx_api_doc_tools import ParameterLocation
7
+ from rest_framework import status
8
+ from rest_framework.permissions import IsAuthenticated
9
+ from rest_framework.response import Response
10
+ from rest_framework.views import APIView
11
+
12
+ from learning_credentials.models import CredentialConfiguration
13
+
14
+ from .permissions import CanAccessLearningContext
15
+
16
+ if TYPE_CHECKING:
17
+ from rest_framework.request import Request
18
+
19
+
20
+ class CredentialConfigurationCheckView(APIView):
21
+ """API view to check if any credentials are configured for a specific learning context."""
22
+
23
+ permission_classes = (IsAuthenticated, CanAccessLearningContext)
24
+
25
+ @apidocs.schema(
26
+ parameters=[
27
+ apidocs.string_parameter(
28
+ "learning_context_key",
29
+ ParameterLocation.PATH,
30
+ description=(
31
+ "Learning context identifier. Can be a course key (course-v1:OpenedX+DemoX+DemoCourse) "
32
+ "or learning path key (path-v1:OpenedX+DemoX+DemoPath+Demo)"
33
+ ),
34
+ ),
35
+ ],
36
+ responses={
37
+ 200: "Boolean indicating if credentials are configured.",
38
+ 400: "Invalid context key format.",
39
+ 403: "User is not authenticated or does not have permission to access the learning context.",
40
+ 404: "Learning context not found or user does not have access.",
41
+ },
42
+ )
43
+ def get(self, _request: "Request", learning_context_key: str) -> Response:
44
+ """
45
+ Check if any credentials are configured for the given learning context.
46
+
47
+ **Example Request**
48
+
49
+ ``GET /api/learning_credentials/v1/configured/course-v1:OpenedX+DemoX+DemoCourse/``
50
+
51
+ **Response Values**
52
+
53
+ - **200 OK**: Request successful, returns credential configuration status.
54
+ - **400 Bad Request**: Invalid learning context key format.
55
+ - **403 Forbidden**: User is not authenticated or does not have permission to access the learning context.
56
+ - **404 Not Found**: Learning context not found or user does not have access.
57
+
58
+ **Example Response**
59
+
60
+ .. code-block:: json
61
+
62
+ {
63
+ "has_credentials": true,
64
+ "credential_count": 2
65
+ }
66
+
67
+ **Response Fields**
68
+
69
+ - ``has_credentials``: Boolean indicating if any credentials are configured
70
+ - ``credential_count``: Number of credential configurations available
71
+
72
+ **Note**
73
+
74
+ This endpoint does not perform learning context existence validation, so it will not return 404 for staff users.
75
+ """
76
+ credential_count = CredentialConfiguration.objects.filter(learning_context_key=learning_context_key).count()
77
+
78
+ response_data = {
79
+ 'has_credentials': credential_count > 0,
80
+ 'credential_count': credential_count,
81
+ }
82
+
83
+ return Response(response_data, status=status.HTTP_200_OK)
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  from typing import ClassVar
6
6
 
7
7
  from django.apps import AppConfig
8
+ from edx_django_utils.plugins.constants import PluginSettings, PluginURLs
8
9
 
9
10
 
10
11
  class LearningCredentialsConfig(AppConfig):
@@ -15,20 +16,16 @@ class LearningCredentialsConfig(AppConfig):
15
16
 
16
17
  # https://edx.readthedocs.io/projects/edx-django-utils/en/latest/plugins/how_tos/how_to_create_a_plugin_app.html
17
18
  plugin_app: ClassVar[dict[str, dict[str, dict]]] = {
18
- 'url_config': {
19
+ PluginURLs.CONFIG: {
19
20
  'lms.djangoapp': {
20
- 'namespace': 'learning_credentials',
21
- 'app_name': 'learning_credentials',
21
+ PluginURLs.NAMESPACE: 'learning_credentials',
22
+ PluginURLs.APP_NAME: 'learning_credentials',
22
23
  }
23
24
  },
24
- 'settings_config': {
25
+ PluginSettings.CONFIG: {
25
26
  'lms.djangoapp': {
26
- 'common': {'relative_path': 'settings.common'},
27
- 'production': {'relative_path': 'settings.production'},
27
+ 'common': {PluginSettings.RELATIVE_PATH: 'settings.common'},
28
+ 'production': {PluginSettings.RELATIVE_PATH: 'settings.production'},
28
29
  },
29
- # 'cms.djangoapp': {
30
- # 'common': {'relative_path': 'settings.common'},
31
- # 'production': {'relative_path': 'settings.production'},
32
- # },
33
30
  },
34
31
  }
@@ -74,13 +74,7 @@ def get_learning_context_name(learning_context_key: LearningContextKey) -> str:
74
74
 
75
75
 
76
76
  def get_course_enrollments(course_id: CourseKey, user_id: int | None = None) -> list[User]:
77
- """
78
- Get the course enrollments from Open edX.
79
-
80
- :param course_id: The course ID.
81
- :param user_id: Optional. If provided, will filter the enrollments by user.
82
- :return: A list of users enrolled in the course.
83
- """
77
+ """Get the course enrollments from Open edX."""
84
78
  # noinspection PyUnresolvedReferences,PyPackageRequirements
85
79
  from common.djangoapps.student.models import CourseEnrollment
86
80
 
@@ -89,7 +89,6 @@ def _write_text_on_template(template: any, font: str, username: str, context_nam
89
89
  pdf_canvas = canvas.Canvas(io.BytesIO(), pagesize=(template_width, template_height))
90
90
 
91
91
  # Write the learner name.
92
- # TODO: Add tests.
93
92
  pdf_canvas.setFont(font, options.get('name_size', 32))
94
93
  name_color = options.get('name_color', '#000')
95
94
  pdf_canvas.setFillColorRGB(*hex_to_rgb(name_color))
@@ -7,7 +7,7 @@ import logging
7
7
  import uuid
8
8
  from importlib import import_module
9
9
  from pathlib import Path
10
- from typing import TYPE_CHECKING, Any
10
+ from typing import TYPE_CHECKING
11
11
 
12
12
  import jsonfield
13
13
  from django.conf import settings
@@ -165,12 +165,11 @@ class CredentialConfiguration(TimeStampedModel):
165
165
  filtered_user_ids_set = set(user_ids) - set(users_ids_with_credentials)
166
166
  return list(filtered_user_ids_set)
167
167
 
168
- def _call_retrieval_func(self, user_id: int | None = None) -> list[int] | dict[str, Any]:
168
+ def get_eligible_user_ids(self) -> list[int]:
169
169
  """
170
- Call the retrieval function with the given parameters.
170
+ Get the list of eligible learners for the given course.
171
171
 
172
- :param user_id: Optional. If provided, will check eligibility for the specific user.
173
- :return: Raw result from the retrieval function - list of user IDs or user details dict.
172
+ :return: A list of user IDs.
174
173
  """
175
174
  func_path = self.credential_type.retrieval_func
176
175
  module_path, func_name = func_path.rsplit('.', 1)
@@ -178,40 +177,7 @@ class CredentialConfiguration(TimeStampedModel):
178
177
  func = getattr(module, func_name)
179
178
 
180
179
  custom_options = {**self.credential_type.custom_options, **self.custom_options}
181
- return func(self.learning_context_key, custom_options, user_id=user_id)
182
-
183
- def get_eligible_user_ids(self, user_id: int | None = None) -> list[int]:
184
- """
185
- Get the list of eligible learners for the given course.
186
-
187
- :param user_id: Optional. If provided, will check eligibility for the specific user.
188
- :return: A list of user IDs.
189
- """
190
- result = self._call_retrieval_func(user_id)
191
-
192
- if user_id is not None:
193
- # Single user case: return list with user ID if eligible
194
- if isinstance(result, dict) and result.get('is_eligible', False):
195
- return [user_id]
196
- return []
197
-
198
- # Multiple users case: result should already be a list of user IDs
199
- return result if isinstance(result, list) else []
200
-
201
- def get_user_eligibility_details(self, user_id: int) -> dict[str, Any]:
202
- """
203
- Get detailed eligibility information for a specific user.
204
-
205
- :param user_id: The user ID to check eligibility for.
206
- :return: Dictionary containing eligibility details and progress information.
207
- """
208
- result = self._call_retrieval_func(user_id)
209
-
210
- if isinstance(result, dict):
211
- return result
212
-
213
- # Fallback for processors that don't support detailed results
214
- return {'is_eligible': False}
180
+ return func(self.learning_context_key, custom_options)
215
181
 
216
182
  def generate_credential_for_user(self, user_id: int, celery_task_id: int = 0):
217
183
  """
@@ -328,15 +294,15 @@ class Credential(TimeStampedModel):
328
294
  learning_context_name = get_learning_context_name(self.learning_context_key)
329
295
  user = get_user_model().objects.get(id=self.user_id)
330
296
  msg = Message(
331
- name="certificate_generated", # type: ignore[unknown-argument]
332
- app_label="learning_credentials", # type: ignore[unknown-argument]
333
- recipient=Recipient(lms_user_id=user.id, email_address=user.email), # type: ignore[unknown-argument]
334
- language='en', # type: ignore[unknown-argument]
297
+ name="certificate_generated",
298
+ app_label="learning_credentials",
299
+ recipient=Recipient(lms_user_id=user.id, email_address=user.email),
300
+ language='en',
335
301
  context={
336
302
  'certificate_link': self.download_url,
337
303
  'course_name': learning_context_name,
338
304
  'platform_name': settings.PLATFORM_NAME,
339
- }, # type: ignore[unknown-argument]
305
+ },
340
306
  )
341
307
  ace.send(msg)
342
308