learning-credentials 0.4.1rc4__tar.gz → 0.4.1rc6__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_credentials-0.4.1rc4/learning_credentials.egg-info → learning_credentials-0.4.1rc6}/PKG-INFO +1 -1
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/generators.py +3 -2
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/models.py +21 -2
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6/learning_credentials.egg-info}/PKG-INFO +1 -1
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/pyproject.toml +1 -1
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/tests/test_generators.py +9 -1
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/tests/test_models.py +55 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/CHANGELOG.rst +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/LICENSE.txt +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/MANIFEST.in +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/README.rst +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/__init__.py +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/admin.py +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/api/__init__.py +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/api/urls.py +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/api/v1/__init__.py +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/api/v1/permissions.py +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/api/v1/urls.py +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/api/v1/views.py +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/apps.py +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/compat.py +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/conf/locale/config.yaml +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/exceptions.py +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/migrations/0001_initial.py +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/migrations/0002_migrate_to_learning_credentials.py +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/migrations/0003_rename_certificates_to_credentials.py +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/migrations/0004_replace_course_keys_with_learning_context_keys.py +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/migrations/0005_rename_processors_and_generators.py +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/migrations/0006_cleanup_openedx_certificates_tables.py +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/migrations/0007_migrate_to_text_elements_format.py +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/migrations/__init__.py +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/processors.py +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/settings/__init__.py +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/settings/common.py +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/settings/production.py +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/tasks.py +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/templates/learning_credentials/base.html +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/templates/learning_credentials/edx_ace/certificate_generated/email/body.html +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/templates/learning_credentials/edx_ace/certificate_generated/email/body.txt +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/templates/learning_credentials/edx_ace/certificate_generated/email/from_name.txt +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/templates/learning_credentials/edx_ace/certificate_generated/email/head.html +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/templates/learning_credentials/edx_ace/certificate_generated/email/subject.txt +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/urls.py +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials.egg-info/SOURCES.txt +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials.egg-info/dependency_links.txt +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials.egg-info/entry_points.txt +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials.egg-info/requires.txt +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials.egg-info/top_level.txt +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/setup.cfg +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/tests/test_migrations.py +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/tests/test_processors.py +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/tests/test_tasks.py +0 -0
- {learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/tests/test_views.py +0 -0
{learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/generators.py
RENAMED
|
@@ -378,12 +378,13 @@ def generate_pdf_credential(
|
|
|
378
378
|
|
|
379
379
|
# Handle multiline context name.
|
|
380
380
|
context_name = get_learning_context_name(learning_context_key)
|
|
381
|
+
custom_context_name = ''
|
|
381
382
|
custom_context_text_element = options.get('text_elements', {}).get('context', {})
|
|
382
383
|
if isinstance(custom_context_text_element, dict):
|
|
383
|
-
|
|
384
|
+
custom_context_name = custom_context_text_element.get('text', '')
|
|
384
385
|
|
|
385
386
|
template_path = options.get('template')
|
|
386
|
-
if '\n' in context_name:
|
|
387
|
+
if '\n' in context_name or '\n' in custom_context_name:
|
|
387
388
|
template_path = options.get('template_multiline', template_path)
|
|
388
389
|
|
|
389
390
|
if not template_path:
|
{learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/models.py
RENAMED
|
@@ -33,6 +33,25 @@ if TYPE_CHECKING: # pragma: no cover
|
|
|
33
33
|
log = logging.getLogger(__name__)
|
|
34
34
|
|
|
35
35
|
|
|
36
|
+
def _deep_merge(base: dict, override: dict) -> dict:
|
|
37
|
+
"""
|
|
38
|
+
Deep merge two dictionaries.
|
|
39
|
+
|
|
40
|
+
Values from `override` take precedence. Nested dictionaries are merged recursively.
|
|
41
|
+
|
|
42
|
+
:param base: The base dictionary.
|
|
43
|
+
:param override: The dictionary with overriding values.
|
|
44
|
+
:return: A new dictionary with merged values.
|
|
45
|
+
"""
|
|
46
|
+
result = base.copy()
|
|
47
|
+
for key, value in override.items():
|
|
48
|
+
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
|
|
49
|
+
result[key] = _deep_merge(result[key], value)
|
|
50
|
+
else:
|
|
51
|
+
result[key] = value
|
|
52
|
+
return result
|
|
53
|
+
|
|
54
|
+
|
|
36
55
|
class CredentialType(TimeStampedModel):
|
|
37
56
|
"""
|
|
38
57
|
Model to store global credential configurations for each type.
|
|
@@ -176,7 +195,7 @@ class CredentialConfiguration(TimeStampedModel):
|
|
|
176
195
|
module = import_module(module_path)
|
|
177
196
|
func = getattr(module, func_name)
|
|
178
197
|
|
|
179
|
-
custom_options =
|
|
198
|
+
custom_options = _deep_merge(self.credential_type.custom_options, self.custom_options)
|
|
180
199
|
return func(self.learning_context_key, custom_options)
|
|
181
200
|
|
|
182
201
|
def generate_credential_for_user(self, user_id: int, celery_task_id: int = 0):
|
|
@@ -195,7 +214,7 @@ class CredentialConfiguration(TimeStampedModel):
|
|
|
195
214
|
# Use the name from the profile if it is not empty. Otherwise, use the first and last name.
|
|
196
215
|
# We check if the profile exists because it may not exist in some cases (e.g., when a User is created manually).
|
|
197
216
|
user_full_name = getattr(getattr(user, 'profile', None), 'name', f"{user.first_name} {user.last_name}")
|
|
198
|
-
custom_options =
|
|
217
|
+
custom_options = _deep_merge(self.credential_type.custom_options, self.custom_options)
|
|
199
218
|
|
|
200
219
|
credential, _ = Credential.objects.update_or_create(
|
|
201
220
|
user_id=user_id,
|
|
@@ -484,7 +484,15 @@ def test_save_credential(mock_contentfile: Mock, mock_token_hex: Mock, storage:
|
|
|
484
484
|
'Single Line',
|
|
485
485
|
{'template_multiline': 'multiline', 'text_elements': {'context': {'text': 'Line 1\nLine 2'}}},
|
|
486
486
|
'multiline',
|
|
487
|
-
'Line
|
|
487
|
+
'Single Line',
|
|
488
|
+
),
|
|
489
|
+
# Custom context text without newlines still uses multiline template if the original context name has newlines.
|
|
490
|
+
# This allows using the `context_name` placeholder in the `context` text element.
|
|
491
|
+
(
|
|
492
|
+
'Multi\nLine',
|
|
493
|
+
{'template_multiline': 'multiline', 'text_elements': {'context': {'text': 'Single Line'}}},
|
|
494
|
+
'multiline',
|
|
495
|
+
'Multi\nLine',
|
|
488
496
|
),
|
|
489
497
|
# Disabled context element falls back to learning context name for template selection.
|
|
490
498
|
(
|
|
@@ -160,6 +160,61 @@ class TestCredentialConfiguration:
|
|
|
160
160
|
eligible_user_ids = self.config.get_eligible_user_ids()
|
|
161
161
|
assert eligible_user_ids == [1, 2, 3]
|
|
162
162
|
|
|
163
|
+
@patch('test_models._mock_retrieval_func')
|
|
164
|
+
def test_custom_options_deep_merge(self, mock_retrieval_func: Mock):
|
|
165
|
+
"""Test that custom_options are deep-merged between CredentialType and CredentialConfiguration."""
|
|
166
|
+
self.credential_type.custom_options = {
|
|
167
|
+
'top_level': 'base_value',
|
|
168
|
+
'nested': {
|
|
169
|
+
'key1': 'base_key1',
|
|
170
|
+
'key2': 'base_key2',
|
|
171
|
+
'key3': {
|
|
172
|
+
'sub_key1': 'sub_value1',
|
|
173
|
+
'sub_key2': 'sub_value2',
|
|
174
|
+
},
|
|
175
|
+
'key4': False,
|
|
176
|
+
'key5': {
|
|
177
|
+
'sub_key1': 'sub_value1',
|
|
178
|
+
'sub_key2': 'sub_value2',
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
}
|
|
182
|
+
self.config.custom_options = {
|
|
183
|
+
'top_level': 'override_value',
|
|
184
|
+
'nested': {
|
|
185
|
+
'key2': 'override_key2',
|
|
186
|
+
'key6': 'new_key5',
|
|
187
|
+
'key3': {
|
|
188
|
+
'sub_key1': 'sub_override_value1',
|
|
189
|
+
},
|
|
190
|
+
'key4': {
|
|
191
|
+
'sub_key': 'sub_value',
|
|
192
|
+
},
|
|
193
|
+
'key5': False,
|
|
194
|
+
},
|
|
195
|
+
'new_top': 'new_value',
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
self.config.get_eligible_user_ids()
|
|
199
|
+
|
|
200
|
+
assert mock_retrieval_func.call_args[0][1] == {
|
|
201
|
+
'top_level': 'override_value', # Overridden.
|
|
202
|
+
'nested': {
|
|
203
|
+
'key1': 'base_key1', # Preserved from type.
|
|
204
|
+
'key2': 'override_key2', # Overridden.
|
|
205
|
+
'key3': { # Merged into type, with an override.
|
|
206
|
+
'sub_key1': 'sub_override_value1',
|
|
207
|
+
'sub_key2': 'sub_value2',
|
|
208
|
+
},
|
|
209
|
+
'key4': { # Overridden non-dict with dict.
|
|
210
|
+
'sub_key': 'sub_value',
|
|
211
|
+
},
|
|
212
|
+
'key5': False, # Overridden dict with non-dict.
|
|
213
|
+
'key6': 'new_key5', # Added from override.
|
|
214
|
+
},
|
|
215
|
+
'new_top': 'new_value', # Added from override.
|
|
216
|
+
}
|
|
217
|
+
|
|
163
218
|
@pytest.mark.django_db
|
|
164
219
|
def test_filter_out_user_ids_with_credentials(self):
|
|
165
220
|
"""Test the filter_out_user_ids_with_credentials method."""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/__init__.py
RENAMED
|
File without changes
|
{learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/admin.py
RENAMED
|
File without changes
|
{learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/api/__init__.py
RENAMED
|
File without changes
|
{learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/api/urls.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/api/v1/urls.py
RENAMED
|
File without changes
|
{learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/api/v1/views.py
RENAMED
|
File without changes
|
{learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/apps.py
RENAMED
|
File without changes
|
{learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/compat.py
RENAMED
|
File without changes
|
|
File without changes
|
{learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/exceptions.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
|
{learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/processors.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/tasks.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{learning_credentials-0.4.1rc4 → learning_credentials-0.4.1rc6}/learning_credentials/urls.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
|