learning-credentials 0.5.4__tar.gz → 0.5.5__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.4 → learning_credentials-0.5.5}/CHANGELOG.rst +9 -0
  2. {learning_credentials-0.5.4/learning_credentials.egg-info → learning_credentials-0.5.5}/PKG-INFO +10 -1
  3. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/generators.py +3 -0
  4. {learning_credentials-0.5.4 → learning_credentials-0.5.5/learning_credentials.egg-info}/PKG-INFO +10 -1
  5. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/pyproject.toml +1 -1
  6. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/tests/test_generators.py +40 -1
  7. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/LICENSE.txt +0 -0
  8. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/MANIFEST.in +0 -0
  9. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/README.rst +0 -0
  10. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/__init__.py +0 -0
  11. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/admin.py +0 -0
  12. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/api/__init__.py +0 -0
  13. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/api/urls.py +0 -0
  14. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/api/v1/__init__.py +0 -0
  15. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/api/v1/permissions.py +0 -0
  16. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/api/v1/serializers.py +0 -0
  17. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/api/v1/urls.py +0 -0
  18. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/api/v1/views.py +0 -0
  19. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/apps.py +0 -0
  20. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/compat.py +0 -0
  21. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/conf/locale/config.yaml +0 -0
  22. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/exceptions.py +0 -0
  23. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/migrations/0001_initial.py +0 -0
  24. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/migrations/0002_migrate_to_learning_credentials.py +0 -0
  25. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/migrations/0003_rename_certificates_to_credentials.py +0 -0
  26. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/migrations/0004_replace_course_keys_with_learning_context_keys.py +0 -0
  27. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/migrations/0005_rename_processors_and_generators.py +0 -0
  28. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/migrations/0006_cleanup_openedx_certificates_tables.py +0 -0
  29. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/migrations/0007_migrate_to_text_elements_format.py +0 -0
  30. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/migrations/0008_validation.py +0 -0
  31. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/migrations/0009_credential_user_fk.py +0 -0
  32. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/migrations/0010_credential_configuration_fk.py +0 -0
  33. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/migrations/__init__.py +0 -0
  34. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/models.py +0 -0
  35. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/processors.py +0 -0
  36. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/settings/__init__.py +0 -0
  37. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/settings/common.py +0 -0
  38. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/settings/production.py +0 -0
  39. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/tasks.py +0 -0
  40. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/templates/learning_credentials/base.html +0 -0
  41. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/templates/learning_credentials/edx_ace/certificate_generated/email/body.html +0 -0
  42. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/templates/learning_credentials/edx_ace/certificate_generated/email/body.txt +0 -0
  43. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/templates/learning_credentials/edx_ace/certificate_generated/email/from_name.txt +0 -0
  44. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/templates/learning_credentials/edx_ace/certificate_generated/email/head.html +0 -0
  45. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/templates/learning_credentials/edx_ace/certificate_generated/email/subject.txt +0 -0
  46. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials/urls.py +0 -0
  47. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials.egg-info/SOURCES.txt +0 -0
  48. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials.egg-info/dependency_links.txt +0 -0
  49. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials.egg-info/entry_points.txt +0 -0
  50. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials.egg-info/requires.txt +0 -0
  51. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/learning_credentials.egg-info/top_level.txt +0 -0
  52. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/setup.cfg +0 -0
  53. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/tests/test_admin.py +0 -0
  54. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/tests/test_migrations.py +0 -0
  55. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/tests/test_models.py +0 -0
  56. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/tests/test_processors.py +0 -0
  57. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/tests/test_tasks.py +0 -0
  58. {learning_credentials-0.5.4 → learning_credentials-0.5.5}/tests/test_views.py +0 -0
@@ -16,6 +16,15 @@ Unreleased
16
16
 
17
17
  *
18
18
 
19
+ 0.5.5 - 2026-11-11
20
+ ******************
21
+
22
+ Fixed
23
+ =====
24
+
25
+ * Missing text (e.g., name, context, date) in encrypted PDF credentials generated with ``pypdf>=7.0.0``. The merged
26
+ content streams are now serialized before encryption, so the overlay is no longer saved as an empty stream.
27
+
19
28
  0.5.4 - 2026-11-10
20
29
  ******************
21
30
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: learning-credentials
3
- Version: 0.5.4
3
+ Version: 0.5.5
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,15 @@ Unreleased
177
177
 
178
178
  *
179
179
 
180
+ 0.5.5 - 2026-11-11
181
+ ******************
182
+
183
+ Fixed
184
+ =====
185
+
186
+ * Missing text (e.g., name, context, date) in encrypted PDF credentials generated with ``pypdf>=7.0.0``. The merged
187
+ content streams are now serialized before encryption, so the overlay is no longer saved as an empty stream.
188
+
180
189
  0.5.4 - 2026-11-10
181
190
  ******************
182
191
 
@@ -451,6 +451,9 @@ def generate_pdf_credential(credential: Credential, options: dict[str, Any], *,
451
451
  overlay_pdf = PdfReader(io.BytesIO(pdf_canvas.getpdfdata()))
452
452
  page = pdf_writer.add_page(template)
453
453
  page.merge_page(overlay_pdf.pages[0])
454
+ # Serialize the merged content streams. Otherwise, `pypdf>=7.0.0` encrypts the raw stream data before the
455
+ # pending operations of the merged overlay are written, which results in an empty overlay stream.
456
+ page.compress_content_streams()
454
457
 
455
458
  url = _save_credential(pdf_writer, credential.uuid)
456
459
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: learning-credentials
3
- Version: 0.5.4
3
+ Version: 0.5.5
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,15 @@ Unreleased
177
177
 
178
178
  *
179
179
 
180
+ 0.5.5 - 2026-11-11
181
+ ******************
182
+
183
+ Fixed
184
+ =====
185
+
186
+ * Missing text (e.g., name, context, date) in encrypted PDF credentials generated with ``pypdf>=7.0.0``. The merged
187
+ content streams are now serialized before encryption, so the overlay is no longer saved as an empty stream.
188
+
180
189
  0.5.4 - 2026-11-10
181
190
  ******************
182
191
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "learning-credentials"
3
- version = "0.5.4"
3
+ version = "0.5.5"
4
4
  description = "A pluggable service for preparing Open edX credentials."
5
5
  dynamic = ["readme"]
6
6
  requires-python = ">=3.11"
@@ -12,7 +12,7 @@ from django.core.files.base import ContentFile
12
12
  from django.core.files.storage import DefaultStorage, FileSystemStorage
13
13
  from django.test import override_settings
14
14
  from inmemorystorage import InMemoryStorage
15
- from pypdf import PdfWriter
15
+ from pypdf import PdfReader, PdfWriter
16
16
  from pypdf.constants import UserAccessPermissions
17
17
 
18
18
  from learning_credentials.exceptions import AssetNotFoundError
@@ -571,6 +571,45 @@ def test_generate_pdf_credential_merges_overlay_into_template(mock_get_asset_by_
571
571
  assert 'Test Course' in page_text
572
572
 
573
573
 
574
+ @patch('learning_credentials.generators.CredentialAsset.get_asset_by_slug')
575
+ def test_generate_pdf_credential_saved_file_contains_overlay(mock_get_asset_by_slug: Mock):
576
+ """
577
+ Test that the saved (encrypted) PDF contains the overlay text.
578
+
579
+ With ``pypdf>=7.0.0``, encryption reads the raw stream data of content streams that were modified during the merge
580
+ before they are serialized, so the overlay was written as an empty stream.
581
+ """
582
+ template_writer = PdfWriter()
583
+ template_writer.add_blank_page(width=842, height=595)
584
+ template_buffer = io.BytesIO()
585
+ template_writer.write(template_buffer)
586
+ mock_get_asset_by_slug.return_value = ContentFile(template_buffer.getvalue(), name='template.pdf')
587
+
588
+ credential = Mock(
589
+ uuid=uuid4(),
590
+ user_full_name='Test User',
591
+ learning_context_name='Test Course',
592
+ verify_uuid=uuid4(),
593
+ created=Mock(),
594
+ )
595
+
596
+ storage = InMemoryStorage()
597
+ with (
598
+ patch('learning_credentials.generators.default_storage', storage),
599
+ patch('learning_credentials.generators.get_localized_credential_date', return_value='April 1, 2021'),
600
+ ):
601
+ generate_pdf_credential(credential, {'template': 'template'})
602
+
603
+ with storage.open(f'learning_credentials/{credential.uuid}.pdf', 'rb') as saved_file:
604
+ reader = PdfReader(io.BytesIO(saved_file.read()))
605
+ assert reader.is_encrypted
606
+ reader.decrypt('')
607
+ page_text = reader.pages[0].extract_text()
608
+ assert 'Test User' in page_text
609
+ assert 'Test Course' in page_text
610
+ assert 'April 1, 2021' in page_text
611
+
612
+
574
613
  def test_generate_pdf_credential_no_template():
575
614
  """Test that generate_pdf_credential raises ValueError when no template is specified."""
576
615
  credential = Mock(learning_context_name='Test Course')