learning-credentials 0.5.2__tar.gz → 0.5.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 (58) hide show
  1. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/CHANGELOG.rst +10 -0
  2. {learning_credentials-0.5.2/learning_credentials.egg-info → learning_credentials-0.5.3}/PKG-INFO +11 -1
  3. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/api/v1/views.py +1 -1
  4. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/models.py +16 -0
  5. {learning_credentials-0.5.2 → learning_credentials-0.5.3/learning_credentials.egg-info}/PKG-INFO +11 -1
  6. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/pyproject.toml +1 -1
  7. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/tests/test_models.py +30 -0
  8. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/tests/test_views.py +35 -0
  9. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/LICENSE.txt +0 -0
  10. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/MANIFEST.in +0 -0
  11. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/README.rst +0 -0
  12. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/__init__.py +0 -0
  13. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/admin.py +0 -0
  14. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/api/__init__.py +0 -0
  15. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/api/urls.py +0 -0
  16. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/api/v1/__init__.py +0 -0
  17. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/api/v1/permissions.py +0 -0
  18. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/api/v1/serializers.py +0 -0
  19. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/api/v1/urls.py +0 -0
  20. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/apps.py +0 -0
  21. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/compat.py +0 -0
  22. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/conf/locale/config.yaml +0 -0
  23. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/exceptions.py +0 -0
  24. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/generators.py +0 -0
  25. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/migrations/0001_initial.py +0 -0
  26. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/migrations/0002_migrate_to_learning_credentials.py +0 -0
  27. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/migrations/0003_rename_certificates_to_credentials.py +0 -0
  28. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/migrations/0004_replace_course_keys_with_learning_context_keys.py +0 -0
  29. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/migrations/0005_rename_processors_and_generators.py +0 -0
  30. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/migrations/0006_cleanup_openedx_certificates_tables.py +0 -0
  31. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/migrations/0007_migrate_to_text_elements_format.py +0 -0
  32. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/migrations/0008_validation.py +0 -0
  33. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/migrations/0009_credential_user_fk.py +0 -0
  34. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/migrations/0010_credential_configuration_fk.py +0 -0
  35. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/migrations/__init__.py +0 -0
  36. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/processors.py +0 -0
  37. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/settings/__init__.py +0 -0
  38. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/settings/common.py +0 -0
  39. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/settings/production.py +0 -0
  40. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/tasks.py +0 -0
  41. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/templates/learning_credentials/base.html +0 -0
  42. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/templates/learning_credentials/edx_ace/certificate_generated/email/body.html +0 -0
  43. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/templates/learning_credentials/edx_ace/certificate_generated/email/body.txt +0 -0
  44. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/templates/learning_credentials/edx_ace/certificate_generated/email/from_name.txt +0 -0
  45. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/templates/learning_credentials/edx_ace/certificate_generated/email/head.html +0 -0
  46. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/templates/learning_credentials/edx_ace/certificate_generated/email/subject.txt +0 -0
  47. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials/urls.py +0 -0
  48. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials.egg-info/SOURCES.txt +0 -0
  49. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials.egg-info/dependency_links.txt +0 -0
  50. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials.egg-info/entry_points.txt +0 -0
  51. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials.egg-info/requires.txt +0 -0
  52. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/learning_credentials.egg-info/top_level.txt +0 -0
  53. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/setup.cfg +0 -0
  54. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/tests/test_admin.py +0 -0
  55. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/tests/test_generators.py +0 -0
  56. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/tests/test_migrations.py +0 -0
  57. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/tests/test_processors.py +0 -0
  58. {learning_credentials-0.5.2 → learning_credentials-0.5.3}/tests/test_tasks.py +0 -0
@@ -16,6 +16,16 @@ Unreleased
16
16
 
17
17
  *
18
18
 
19
+ 0.5.3 - 2026-08-11
20
+ ******************
21
+
22
+ Fixed
23
+ =====
24
+
25
+ * The ``is_generation_enabled`` field of the credential eligibility endpoint is now based on the ``expires`` field of
26
+ the periodic task, when it is set. The ``enabled`` flag is updated by Celery Beat (e.g., it disables expired and
27
+ already executed one-off tasks), so it does not reliably reflect the intended configuration.
28
+
19
29
  0.5.2 - 2026-08-11
20
30
  ******************
21
31
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: learning-credentials
3
- Version: 0.5.2
3
+ Version: 0.5.3
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
@@ -177,6 +177,16 @@ Unreleased
177
177
 
178
178
  *
179
179
 
180
+ 0.5.3 - 2026-08-11
181
+ ******************
182
+
183
+ Fixed
184
+ =====
185
+
186
+ * The ``is_generation_enabled`` field of the credential eligibility endpoint is now based on the ``expires`` field of
187
+ the periodic task, when it is set. The ``enabled`` flag is updated by Celery Beat (e.g., it disables expired and
188
+ already executed one-off tasks), so it does not reliably reflect the intended configuration.
189
+
180
190
  0.5.2 - 2026-08-11
181
191
  ******************
182
192
 
@@ -167,7 +167,7 @@ class CredentialEligibilityView(APIView):
167
167
  return {
168
168
  'credential_type_id': config.credential_type.pk,
169
169
  'name': config.credential_type.name,
170
- 'is_generation_enabled': config.periodic_task.enabled,
170
+ 'is_generation_enabled': config.is_generation_enabled,
171
171
  **progress_data,
172
172
  'existing_credential': existing_credential.uuid if existing_credential else None,
173
173
  'existing_credential_url': existing_credential.download_url if existing_credential else None,
@@ -148,6 +148,22 @@ class CredentialConfiguration(TimeStampedModel):
148
148
  self.periodic_task.args = json.dumps([self.id])
149
149
  self.periodic_task.save()
150
150
 
151
+ @property
152
+ def is_generation_enabled(self) -> bool:
153
+ """
154
+ Check whether credential generation is currently enabled for this configuration.
155
+
156
+ Celery Beat updates the `enabled` flag of the periodic task on its own (e.g., it disables one-off tasks once
157
+ they have run, and expired tasks), so it does not reliably reflect the intended configuration. When `expires`
158
+ is set, it defines the end of the generation window, so it takes precedence.
159
+
160
+ :return: True if credential generation is enabled, False otherwise.
161
+ """
162
+ if self.periodic_task.expires:
163
+ return self.periodic_task.expires > timezone.now()
164
+
165
+ return self.periodic_task.enabled
166
+
151
167
  @classmethod
152
168
  def get_enabled_configurations(cls) -> QuerySet[Self]:
153
169
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: learning-credentials
3
- Version: 0.5.2
3
+ Version: 0.5.3
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
@@ -177,6 +177,16 @@ Unreleased
177
177
 
178
178
  *
179
179
 
180
+ 0.5.3 - 2026-08-11
181
+ ******************
182
+
183
+ Fixed
184
+ =====
185
+
186
+ * The ``is_generation_enabled`` field of the credential eligibility endpoint is now based on the ``expires`` field of
187
+ the periodic task, when it is set. The ``enabled`` flag is updated by Celery Beat (e.g., it disables expired and
188
+ already executed one-off tasks), so it does not reliably reflect the intended configuration.
189
+
180
190
  0.5.2 - 2026-08-11
181
191
  ******************
182
192
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "learning-credentials"
3
- version = "0.5.2"
3
+ version = "0.5.3"
4
4
  description = "A pluggable service for preparing Open edX credentials."
5
5
  dynamic = ["readme"]
6
6
  requires-python = ">=3.11"
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from datetime import timedelta
5
6
  from pathlib import Path
6
7
  from typing import TYPE_CHECKING
7
8
  from unittest.mock import Mock, patch
@@ -10,6 +11,7 @@ from uuid import uuid4
10
11
  import pytest
11
12
  from django.core.exceptions import ValidationError
12
13
  from django.core.files.base import ContentFile
14
+ from django.utils import timezone
13
15
  from django_celery_beat.models import PeriodicTask
14
16
 
15
17
  from learning_credentials.exceptions import AssetNotFoundError, CredentialGenerationError
@@ -371,6 +373,34 @@ class TestCredentialConfiguration:
371
373
  assert enabled_configs.count() == 1
372
374
  assert enabled_configs.first() == mock_credential_config
373
375
 
376
+ @pytest.mark.django_db
377
+ @pytest.mark.parametrize(
378
+ ('enabled', 'expires_offset', 'expected'),
379
+ [
380
+ # Without `expires`, the `enabled` flag of the periodic task is used.
381
+ (False, None, False),
382
+ (True, None, True),
383
+ # With `expires` set, it takes precedence over the `enabled` flag.
384
+ (False, timedelta(days=1), True),
385
+ (True, timedelta(days=-1), False),
386
+ (False, timedelta(days=-1), False),
387
+ (True, timedelta(days=1), True),
388
+ ],
389
+ )
390
+ def test_is_generation_enabled(
391
+ self,
392
+ mock_credential_config: CredentialConfiguration,
393
+ enabled: bool,
394
+ expires_offset: timedelta | None,
395
+ expected: bool,
396
+ ):
397
+ """Test that the `expires` field takes precedence over the `enabled` flag, which Celery Beat updates."""
398
+ mock_credential_config.periodic_task.enabled = enabled
399
+ mock_credential_config.periodic_task.expires = timezone.now() + expires_offset if expires_offset else None
400
+ mock_credential_config.periodic_task.save()
401
+
402
+ assert mock_credential_config.is_generation_enabled is expected
403
+
374
404
  @pytest.mark.django_db
375
405
  def test_save_updates_existing_periodic_task(self, mock_credential_config: CredentialConfiguration):
376
406
  """Test that saving an existing CredentialConfiguration updates the periodic task."""
@@ -2,11 +2,13 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from datetime import timedelta
5
6
  from typing import TYPE_CHECKING
6
7
  from unittest.mock import Mock, patch
7
8
 
8
9
  import pytest
9
10
  from django.urls import reverse
11
+ from django.utils import timezone
10
12
  from learning_paths.models import LearningPathStep
11
13
  from rest_framework import status
12
14
  from rest_framework.test import APIClient
@@ -313,6 +315,39 @@ class TestCredentialEligibilityView:
313
315
  assert 'existing_credential' not in cred
314
316
  assert 'existing_credential_url' not in cred
315
317
 
318
+ @pytest.mark.parametrize(
319
+ ('enabled', 'expires_offset', 'expected'),
320
+ [
321
+ (False, None, False),
322
+ (True, None, True),
323
+ # Celery Beat disables expired tasks, so `expires` takes precedence over the `enabled` flag.
324
+ (False, timedelta(days=1), True),
325
+ (True, timedelta(days=-1), False),
326
+ ],
327
+ )
328
+ @patch('learning_credentials.api.v1.permissions.get_course_enrollments')
329
+ def test_get_is_generation_enabled( # noqa: PLR0917
330
+ self,
331
+ mock_enrollments: Mock,
332
+ user: User,
333
+ course_key: CourseKey,
334
+ mock_credential_config: CredentialConfiguration,
335
+ enabled: bool,
336
+ expires_offset: timedelta | None,
337
+ expected: bool,
338
+ ):
339
+ """Test that the generation status is based on the `expires` field of the periodic task, when it is set."""
340
+ mock_enrollments.return_value = [user]
341
+ mock_credential_config.periodic_task.enabled = enabled
342
+ mock_credential_config.periodic_task.expires = timezone.now() + expires_offset if expires_offset else None
343
+ mock_credential_config.periodic_task.save()
344
+
345
+ with patch.object(CredentialConfiguration, 'get_user_eligibility_details', return_value={'is_eligible': True}):
346
+ response = self._make_get_request(user, course_key)
347
+
348
+ assert response.status_code == status.HTTP_200_OK
349
+ assert response.data['credentials'][0]['is_generation_enabled'] is expected
350
+
316
351
  @patch('learning_credentials.api.v1.permissions.get_course_enrollments')
317
352
  def test_get_filters_by_retrieval_func(
318
353
  self,