learning-credentials 0.3.0rc4__py3-none-any.whl → 0.3.0rc10__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.
@@ -1,77 +0,0 @@
1
- """API functions for the Learning Credentials app."""
2
-
3
- import logging
4
- from typing import TYPE_CHECKING
5
-
6
- from .models import Credential, CredentialConfiguration
7
- from .tasks import generate_credential_for_user_task
8
-
9
- if TYPE_CHECKING:
10
- from django.contrib.auth.models import User
11
- from opaque_keys.edx.keys import CourseKey
12
-
13
- logger = logging.getLogger(__name__)
14
-
15
-
16
- def get_eligible_users_by_credential_type(
17
- course_id: 'CourseKey', user_id: int | None = None
18
- ) -> dict[str, list['User']]:
19
- """
20
- Retrieve eligible users for each credential type in the given course.
21
-
22
- :param course_id: The key of the course for which to check eligibility.
23
- :param user_id: Optional. If provided, will check eligibility for the specific user.
24
- :return: A dictionary with credential type as the key and eligible users as the value.
25
- """
26
- credential_configs = CredentialConfiguration.objects.filter(course_id=course_id)
27
-
28
- if not credential_configs:
29
- return {}
30
-
31
- eligible_users_by_type = {}
32
- for credential_config in credential_configs:
33
- user_ids = credential_config.get_eligible_user_ids(user_id)
34
- filtered_user_ids = credential_config.filter_out_user_ids_with_credentials(user_ids)
35
-
36
- if user_id:
37
- eligible_users_by_type[credential_config.credential_type.name] = list(set(filtered_user_ids) & {user_id})
38
- else:
39
- eligible_users_by_type[credential_config.credential_type.name] = filtered_user_ids
40
-
41
- return eligible_users_by_type
42
-
43
-
44
- def get_user_credentials_by_type(course_id: 'CourseKey', user_id: int) -> dict[str, dict[str, str]]:
45
- """
46
- Retrieve the available credentials for a given user in a course.
47
-
48
- :param course_id: The course ID for which to retrieve credentials.
49
- :param user_id: The ID of the user for whom credentials are being retrieved.
50
- :return: A dict where keys are credential types and values are dicts with the download link and status.
51
- """
52
- credentials = Credential.objects.filter(user_id=user_id, course_id=course_id)
53
-
54
- return {cred.credential_type: {'download_url': cred.download_url, 'status': cred.status} for cred in credentials}
55
-
56
-
57
- def generate_credential_for_user(course_id: 'CourseKey', credential_type: str, user_id: int, force: bool = False):
58
- """
59
- Generate a credential for a user in a course.
60
-
61
- :param course_id: The course ID for which to generate the credential.
62
- :param credential_type: The type of credential to generate.
63
- :param user_id: The ID of the user for whom the credential is being generated.
64
- :param force: If True, will generate the credential even if the user is not eligible.
65
- """
66
- credential_config = CredentialConfiguration.objects.get(course_id=course_id, credential_type__name=credential_type)
67
-
68
- if not credential_config:
69
- logger.error('No course configuration found for course %s', course_id)
70
- return
71
-
72
- if not force and not credential_config.get_eligible_user_ids(user_id):
73
- logger.error('User %s is not eligible for the credential in course %s', user_id, course_id)
74
- msg = 'User is not eligible for the credential.'
75
- raise ValueError(msg)
76
-
77
- generate_credential_for_user_task.delay(credential_config.id, user_id)
@@ -1,7 +0,0 @@
1
- .credentials-block .credentials-list .credential {
2
- padding-bottom: 20px;
3
-
4
- .credential-status {
5
- margin-bottom: 10px;
6
- }
7
- }
@@ -1,48 +0,0 @@
1
- <div class="credentials-block">
2
- {% if is_author_mode %}
3
- <p>The Studio view of this XBlock is not supported yet. Please preview the XBlock in the LMS.</p>
4
- {% else %}
5
- <h3>Check Your Certificate Eligibility Status</h3>
6
- <ul class="credentials-list">
7
- {% if credentials %}
8
- {% for credential_type, credential in credentials.items %}
9
- <li class="credential">
10
- <strong>Type:</strong> {{ credential_type }}
11
- {% if credential.download_url %}
12
- <p class="credential-status">Congratulations on finishing strong!</p>
13
- <strong>Download Link:</strong> <a href="{{ credential.download_url }}">Download Certificate</a>
14
- {% elif credential.status == credential.Status.ERROR %}
15
- <p class="credential-status">Something went wrong. Please contact us via the Help page for assistance.</p>
16
- {% endif %}
17
- <button class="btn-brand generate-credential" data-credential-type="{{ credential_type }}" disabled>
18
- Certificate Claimed
19
- </button>
20
- <div id="message-area-{{ credential_type }}"></div>
21
- </li>
22
- {% endfor %}
23
- {% endif %}
24
-
25
- {% if eligible_types %}
26
- {% for credential_type, is_eligible in eligible_types.items %}
27
- {% if not credentials or credential_type not in credentials %}
28
- <li class="credential">
29
- <strong>Type:</strong> {{ credential_type }}
30
- {% if is_eligible %}
31
- <p class="credential-status">Congratulations! You have earned this certificate. Please claim it below.</p>
32
- <button class="btn-brand generate-credential" data-credential-type="{{ credential_type }}">
33
- Claim Certificate
34
- </button>
35
- {% else %}
36
- <p class="certificate-status">You are not yet eligible for this certificate.</p>
37
- <button class="btn-brand generate-certificate" data-certificate-type="{{ credential_type }}" disabled>
38
- Claim Certificate
39
- </button>
40
- {% endif %}
41
- <div id="message-area-{{ credential_type }}"></div>
42
- </li>
43
- {% endif %}
44
- {% endfor %}
45
- {% endif %}
46
- </ul>
47
- {% endif %}
48
- </div>
@@ -1,23 +0,0 @@
1
- function CredentialsXBlock(runtime, element) {
2
- function generateCredential(event) {
3
- const button = event.target;
4
- const credentialType = $(button).data('credential-type');
5
- const handlerUrl = runtime.handlerUrl(element, 'generate_credential');
6
-
7
- $.post(handlerUrl, JSON.stringify({ credential_type: credentialType }))
8
- .done(function(data) {
9
- const messageArea = $(element).find('#message-area-' + credentialType);
10
- if (data.status === 'success') {
11
- messageArea.html('<p style="color:green;">Certificate generation initiated successfully.</p>');
12
- } else {
13
- messageArea.html('<p style="color:red;">' + data.message + '</p>');
14
- }
15
- })
16
- .fail(function() {
17
- const messageArea = $(element).find('#message-area-' + credentialType);
18
- messageArea.html('<p style="color:red;">An error occurred while processing your request.</p>');
19
- });
20
- }
21
-
22
- $(element).find('.generate-credential').on('click', generateCredential);
23
- }
@@ -1,85 +0,0 @@
1
- """XBlocks for Learning Credentials."""
2
-
3
- import logging
4
-
5
- from xblock.core import XBlock
6
- from xblock.fields import Scope, String
7
- from xblock.fragment import Fragment
8
- from xblock.utils.resources import ResourceLoader
9
- from xblock.utils.studio_editable import StudioEditableXBlockMixin
10
-
11
- from .core_api import generate_credential_for_user, get_eligible_users_by_credential_type, get_user_credentials_by_type
12
-
13
- loader = ResourceLoader(__name__)
14
- logger = logging.getLogger(__name__)
15
-
16
-
17
- class CredentialsXBlock(StudioEditableXBlockMixin, XBlock):
18
- """XBlock that displays the credential eligibility status and allows eligible users to generate credentials."""
19
-
20
- display_name = String(
21
- help='The display name for this component.',
22
- scope=Scope.content,
23
- display_name="Display name",
24
- default='Credentials',
25
- )
26
-
27
- def student_view(self, context) -> Fragment: # noqa: ANN001, ARG002
28
- """Main view for the student. Displays the credential eligibility or ineligibility status."""
29
- fragment = Fragment()
30
- eligible_types = False
31
- credentials = []
32
-
33
- if not (is_author_mode := getattr(self.runtime, 'is_author_mode', False)):
34
- credentials = self.get_credentials()
35
- eligible_types = self.get_eligible_credential_types()
36
-
37
- # Filter out the eligible types that already have a credential generated
38
- for cred_type in credentials:
39
- if cred_type in eligible_types:
40
- del eligible_types[cred_type]
41
-
42
- fragment.add_content(
43
- loader.render_django_template(
44
- 'public/html/credentials_xblock.html',
45
- {
46
- 'credentials': credentials,
47
- 'eligible_types': eligible_types,
48
- 'is_author_mode': is_author_mode,
49
- },
50
- )
51
- )
52
-
53
- fragment.add_css_url(self.runtime.local_resource_url(self, "public/css/credentials_xblock.css"))
54
- fragment.add_javascript_url(self.runtime.local_resource_url(self, "public/js/credentials_xblock.js"))
55
- fragment.initialize_js('CredentialsXBlock')
56
- return fragment
57
-
58
- def get_eligible_credential_types(self) -> dict[str, bool]:
59
- """Retrieve the eligibility status for each credential type."""
60
- eligible_users = get_eligible_users_by_credential_type(self.runtime.course_id, user_id=self.scope_ids.user_id)
61
-
62
- return {credential_type: bool(users) for credential_type, users in eligible_users.items()}
63
-
64
- def get_credentials(self) -> dict[str, dict[str, str]]:
65
- """Retrieve the credentials for the current user in the current course."""
66
- return get_user_credentials_by_type(self.runtime.course_id, self.scope_ids.user_id)
67
-
68
- @XBlock.json_handler
69
- def generate_credential(self, data: dict, suffix: str = '') -> dict[str, str]: # noqa: ARG002
70
- """Handler for generating a credential for a specific type."""
71
- credential_type = data.get('credential_type')
72
- if not credential_type:
73
- return {'status': 'error', 'message': 'No credential type specified.'}
74
-
75
- course_id = self.runtime.course_id
76
- user_id = self.scope_ids.user_id
77
- logger.info(
78
- 'Generating a credential for user %s in course %s with type %s.', user_id, course_id, credential_type
79
- )
80
-
81
- try:
82
- generate_credential_for_user(course_id, credential_type, user_id)
83
- except ValueError as e:
84
- return {'status': 'error', 'message': str(e)}
85
- return {'status': 'success'}