learning-credentials 0.4.1rc6__py3-none-any.whl → 0.5.0rc3__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.
- learning_credentials/admin.py +59 -10
- learning_credentials/api/v1/serializers.py +13 -0
- learning_credentials/api/v1/urls.py +2 -1
- learning_credentials/api/v1/views.py +61 -1
- learning_credentials/compat.py +13 -7
- learning_credentials/generators.py +81 -39
- learning_credentials/migrations/0001_squashed_0010.py +265 -0
- learning_credentials/migrations/0008_validation.py +94 -0
- learning_credentials/migrations/0009_credential_user_fk.py +63 -0
- learning_credentials/migrations/0010_credential_configuration_fk.py +79 -0
- learning_credentials/models.py +84 -27
- learning_credentials/templates/learning_credentials/verify.html +83 -0
- learning_credentials/urls.py +2 -0
- {learning_credentials-0.4.1rc6.dist-info → learning_credentials-0.5.0rc3.dist-info}/METADATA +12 -2
- {learning_credentials-0.4.1rc6.dist-info → learning_credentials-0.5.0rc3.dist-info}/RECORD +19 -13
- {learning_credentials-0.4.1rc6.dist-info → learning_credentials-0.5.0rc3.dist-info}/WHEEL +1 -1
- {learning_credentials-0.4.1rc6.dist-info → learning_credentials-0.5.0rc3.dist-info}/entry_points.txt +0 -0
- {learning_credentials-0.4.1rc6.dist-info → learning_credentials-0.5.0rc3.dist-info}/licenses/LICENSE.txt +0 -0
- {learning_credentials-0.4.1rc6.dist-info → learning_credentials-0.5.0rc3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
from django.conf import settings
|
|
2
|
+
from django.db import migrations, models
|
|
3
|
+
import django.db.models.deletion
|
|
4
|
+
import django.utils.timezone
|
|
5
|
+
import jsonfield.fields
|
|
6
|
+
import learning_credentials.models
|
|
7
|
+
import model_utils.fields
|
|
8
|
+
import opaque_keys.edx.django.models
|
|
9
|
+
import uuid
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Migration(migrations.Migration):
|
|
13
|
+
replaces = [
|
|
14
|
+
('learning_credentials', '0001_initial'),
|
|
15
|
+
('learning_credentials', '0002_migrate_to_learning_credentials'),
|
|
16
|
+
('learning_credentials', '0003_rename_certificates_to_credentials'),
|
|
17
|
+
('learning_credentials', '0004_replace_course_keys_with_learning_context_keys'),
|
|
18
|
+
('learning_credentials', '0005_rename_processors_and_generators'),
|
|
19
|
+
('learning_credentials', '0006_cleanup_openedx_certificates_tables'),
|
|
20
|
+
('learning_credentials', '0007_migrate_to_text_elements_format'),
|
|
21
|
+
('learning_credentials', '0008_validation'),
|
|
22
|
+
('learning_credentials', '0009_credential_user_fk'),
|
|
23
|
+
('learning_credentials', '0010_credential_configuration_fk'),
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
initial = True
|
|
27
|
+
|
|
28
|
+
dependencies = [
|
|
29
|
+
("django_celery_beat", "0019_alter_periodictasks_options"),
|
|
30
|
+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
operations = [
|
|
34
|
+
migrations.CreateModel(
|
|
35
|
+
name="CredentialAsset",
|
|
36
|
+
fields=[
|
|
37
|
+
("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
|
38
|
+
(
|
|
39
|
+
"created",
|
|
40
|
+
model_utils.fields.AutoCreatedField(
|
|
41
|
+
default=django.utils.timezone.now, editable=False, verbose_name="created"
|
|
42
|
+
),
|
|
43
|
+
),
|
|
44
|
+
(
|
|
45
|
+
"modified",
|
|
46
|
+
model_utils.fields.AutoLastModifiedField(
|
|
47
|
+
default=django.utils.timezone.now, editable=False, verbose_name="modified"
|
|
48
|
+
),
|
|
49
|
+
),
|
|
50
|
+
("description", models.CharField(blank=True, help_text="Description of the asset.", max_length=255)),
|
|
51
|
+
(
|
|
52
|
+
"asset",
|
|
53
|
+
models.FileField(
|
|
54
|
+
help_text="Asset file. It could be a PDF template, image or font file.",
|
|
55
|
+
max_length=255,
|
|
56
|
+
upload_to=learning_credentials.models.CredentialAsset.template_assets_path,
|
|
57
|
+
),
|
|
58
|
+
),
|
|
59
|
+
(
|
|
60
|
+
"asset_slug",
|
|
61
|
+
models.SlugField(
|
|
62
|
+
help_text="Asset's unique slug. We can reference the asset in templates using this value.",
|
|
63
|
+
max_length=255,
|
|
64
|
+
unique=True,
|
|
65
|
+
),
|
|
66
|
+
),
|
|
67
|
+
],
|
|
68
|
+
options={"get_latest_by": "created"},
|
|
69
|
+
),
|
|
70
|
+
migrations.CreateModel(
|
|
71
|
+
name="CredentialType",
|
|
72
|
+
fields=[
|
|
73
|
+
("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
|
74
|
+
(
|
|
75
|
+
"created",
|
|
76
|
+
model_utils.fields.AutoCreatedField(
|
|
77
|
+
default=django.utils.timezone.now, editable=False, verbose_name="created"
|
|
78
|
+
),
|
|
79
|
+
),
|
|
80
|
+
(
|
|
81
|
+
"modified",
|
|
82
|
+
model_utils.fields.AutoLastModifiedField(
|
|
83
|
+
default=django.utils.timezone.now, editable=False, verbose_name="modified"
|
|
84
|
+
),
|
|
85
|
+
),
|
|
86
|
+
("name", models.CharField(help_text="Name of the credential type.", max_length=255, unique=True)),
|
|
87
|
+
(
|
|
88
|
+
"retrieval_func",
|
|
89
|
+
models.CharField(help_text="A name of the function to retrieve eligible users.", max_length=200),
|
|
90
|
+
),
|
|
91
|
+
(
|
|
92
|
+
"generation_func",
|
|
93
|
+
models.CharField(help_text="A name of the function to generate credentials.", max_length=200),
|
|
94
|
+
),
|
|
95
|
+
(
|
|
96
|
+
"custom_options",
|
|
97
|
+
jsonfield.fields.JSONField(blank=True, default=dict, help_text="Custom options for the functions."),
|
|
98
|
+
),
|
|
99
|
+
],
|
|
100
|
+
options={"abstract": False},
|
|
101
|
+
),
|
|
102
|
+
migrations.CreateModel(
|
|
103
|
+
name="CredentialConfiguration",
|
|
104
|
+
fields=[
|
|
105
|
+
(
|
|
106
|
+
"id",
|
|
107
|
+
models.AutoField(
|
|
108
|
+
auto_created=True,
|
|
109
|
+
primary_key=True,
|
|
110
|
+
serialize=False,
|
|
111
|
+
verbose_name="ID",
|
|
112
|
+
),
|
|
113
|
+
),
|
|
114
|
+
(
|
|
115
|
+
"created",
|
|
116
|
+
model_utils.fields.AutoCreatedField(
|
|
117
|
+
default=django.utils.timezone.now,
|
|
118
|
+
editable=False,
|
|
119
|
+
verbose_name="created",
|
|
120
|
+
),
|
|
121
|
+
),
|
|
122
|
+
(
|
|
123
|
+
"modified",
|
|
124
|
+
model_utils.fields.AutoLastModifiedField(
|
|
125
|
+
default=django.utils.timezone.now,
|
|
126
|
+
editable=False,
|
|
127
|
+
verbose_name="modified",
|
|
128
|
+
),
|
|
129
|
+
),
|
|
130
|
+
(
|
|
131
|
+
"learning_context_key",
|
|
132
|
+
opaque_keys.edx.django.models.LearningContextKeyField(
|
|
133
|
+
help_text="ID of a learning context (e.g., a course or a Learning Path).",
|
|
134
|
+
max_length=255,
|
|
135
|
+
),
|
|
136
|
+
),
|
|
137
|
+
(
|
|
138
|
+
"custom_options",
|
|
139
|
+
jsonfield.fields.JSONField(
|
|
140
|
+
blank=True,
|
|
141
|
+
default=dict,
|
|
142
|
+
help_text="Custom options for the functions. If specified, they are merged with the options defined in the credential type.",
|
|
143
|
+
),
|
|
144
|
+
),
|
|
145
|
+
(
|
|
146
|
+
"credential_type",
|
|
147
|
+
models.ForeignKey(
|
|
148
|
+
help_text="Associated credential type.",
|
|
149
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
150
|
+
to="learning_credentials.credentialtype",
|
|
151
|
+
),
|
|
152
|
+
),
|
|
153
|
+
(
|
|
154
|
+
"periodic_task",
|
|
155
|
+
models.OneToOneField(
|
|
156
|
+
help_text="Associated periodic task.",
|
|
157
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
158
|
+
to="django_celery_beat.periodictask",
|
|
159
|
+
),
|
|
160
|
+
),
|
|
161
|
+
],
|
|
162
|
+
options={"unique_together": {("learning_context_key", "credential_type")}},
|
|
163
|
+
),
|
|
164
|
+
migrations.CreateModel(
|
|
165
|
+
name="Credential",
|
|
166
|
+
fields=[
|
|
167
|
+
(
|
|
168
|
+
"created",
|
|
169
|
+
model_utils.fields.AutoCreatedField(
|
|
170
|
+
default=django.utils.timezone.now, editable=False, verbose_name="created"
|
|
171
|
+
),
|
|
172
|
+
),
|
|
173
|
+
(
|
|
174
|
+
"modified",
|
|
175
|
+
model_utils.fields.AutoLastModifiedField(
|
|
176
|
+
default=django.utils.timezone.now, editable=False, verbose_name="modified"
|
|
177
|
+
),
|
|
178
|
+
),
|
|
179
|
+
(
|
|
180
|
+
"uuid",
|
|
181
|
+
models.UUIDField(
|
|
182
|
+
default=uuid.uuid4,
|
|
183
|
+
editable=False,
|
|
184
|
+
help_text="Auto-generated UUID of the credential",
|
|
185
|
+
primary_key=True,
|
|
186
|
+
serialize=False,
|
|
187
|
+
),
|
|
188
|
+
),
|
|
189
|
+
(
|
|
190
|
+
"verify_uuid",
|
|
191
|
+
models.UUIDField(
|
|
192
|
+
default=uuid.uuid4, editable=False, help_text="UUID used for verifying the credential"
|
|
193
|
+
),
|
|
194
|
+
),
|
|
195
|
+
(
|
|
196
|
+
"user_full_name",
|
|
197
|
+
models.CharField(
|
|
198
|
+
editable=False,
|
|
199
|
+
help_text="User receiving the credential. This field is used for validation purposes.",
|
|
200
|
+
max_length=255,
|
|
201
|
+
),
|
|
202
|
+
),
|
|
203
|
+
(
|
|
204
|
+
"learning_context_name",
|
|
205
|
+
models.CharField(
|
|
206
|
+
editable=False,
|
|
207
|
+
help_text="Name of the learning context for which the credential was issued. This field is used for validation purposes.",
|
|
208
|
+
max_length=255,
|
|
209
|
+
),
|
|
210
|
+
),
|
|
211
|
+
(
|
|
212
|
+
"status",
|
|
213
|
+
models.CharField(
|
|
214
|
+
choices=[
|
|
215
|
+
("generating", "Generating"),
|
|
216
|
+
("available", "Available"),
|
|
217
|
+
("error", "Error"),
|
|
218
|
+
("invalidated", "Invalidated"),
|
|
219
|
+
],
|
|
220
|
+
default="generating",
|
|
221
|
+
help_text="Status of the credential generation task",
|
|
222
|
+
max_length=32,
|
|
223
|
+
),
|
|
224
|
+
),
|
|
225
|
+
(
|
|
226
|
+
"download_url",
|
|
227
|
+
models.URLField(blank=True, help_text="URL of the generated credential PDF (e.g., to S3)"),
|
|
228
|
+
),
|
|
229
|
+
(
|
|
230
|
+
"legacy_id",
|
|
231
|
+
models.IntegerField(
|
|
232
|
+
help_text="Legacy ID of the credential imported from another system", null=True
|
|
233
|
+
),
|
|
234
|
+
),
|
|
235
|
+
("generation_task_id", models.CharField(help_text="Task ID from the Celery queue", max_length=255)),
|
|
236
|
+
(
|
|
237
|
+
"invalidated_at",
|
|
238
|
+
models.DateTimeField(
|
|
239
|
+
editable=False, help_text="Timestamp when the credential was invalidated", null=True
|
|
240
|
+
),
|
|
241
|
+
),
|
|
242
|
+
(
|
|
243
|
+
"invalidation_reason",
|
|
244
|
+
models.CharField(blank=True, help_text="Reason for invalidating the credential", max_length=255),
|
|
245
|
+
),
|
|
246
|
+
(
|
|
247
|
+
"configuration",
|
|
248
|
+
models.ForeignKey(
|
|
249
|
+
help_text="Associated credential configuration",
|
|
250
|
+
on_delete=django.db.models.deletion.PROTECT,
|
|
251
|
+
to="learning_credentials.credentialconfiguration",
|
|
252
|
+
),
|
|
253
|
+
),
|
|
254
|
+
(
|
|
255
|
+
"user",
|
|
256
|
+
models.ForeignKey(
|
|
257
|
+
help_text="User receiving the credential",
|
|
258
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
259
|
+
to=settings.AUTH_USER_MODEL,
|
|
260
|
+
),
|
|
261
|
+
),
|
|
262
|
+
],
|
|
263
|
+
options={"abstract": False},
|
|
264
|
+
),
|
|
265
|
+
]
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Generated by Django 4.2.25 on 2025-10-31 17:43
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
import uuid
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def backfill_credential_fields(apps, schema_editor):
|
|
8
|
+
"""Generate verification UUIDs and backfill learning_context_name for all existing credentials."""
|
|
9
|
+
from learning_credentials.compat import get_learning_context_name
|
|
10
|
+
|
|
11
|
+
Credential = apps.get_model("learning_credentials", "Credential")
|
|
12
|
+
for credential in Credential.objects.all():
|
|
13
|
+
credential.verify_uuid = uuid.uuid4()
|
|
14
|
+
credential.learning_context_name = get_learning_context_name(credential.learning_context_key)
|
|
15
|
+
credential.save(update_fields=["verify_uuid", "learning_context_name"])
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Migration(migrations.Migration):
|
|
19
|
+
dependencies = [
|
|
20
|
+
("learning_credentials", "0007_migrate_to_text_elements_format"),
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
operations = [
|
|
24
|
+
migrations.AlterUniqueTogether(
|
|
25
|
+
name="credential",
|
|
26
|
+
unique_together=set(),
|
|
27
|
+
),
|
|
28
|
+
migrations.AlterField(
|
|
29
|
+
model_name="credential",
|
|
30
|
+
name="user_full_name",
|
|
31
|
+
field=models.CharField(
|
|
32
|
+
editable=False,
|
|
33
|
+
help_text="User receiving the credential. This field is used for validation purposes.",
|
|
34
|
+
max_length=255,
|
|
35
|
+
),
|
|
36
|
+
),
|
|
37
|
+
migrations.AddField(
|
|
38
|
+
model_name="credential",
|
|
39
|
+
name="invalidated_at",
|
|
40
|
+
field=models.DateTimeField(
|
|
41
|
+
editable=False,
|
|
42
|
+
help_text="Timestamp when the credential was invalidated",
|
|
43
|
+
null=True,
|
|
44
|
+
),
|
|
45
|
+
),
|
|
46
|
+
migrations.AddField(
|
|
47
|
+
model_name="credential",
|
|
48
|
+
name="invalidation_reason",
|
|
49
|
+
field=models.CharField(
|
|
50
|
+
blank=True,
|
|
51
|
+
help_text="Reason for invalidating the credential",
|
|
52
|
+
max_length=255,
|
|
53
|
+
),
|
|
54
|
+
),
|
|
55
|
+
migrations.AddField(
|
|
56
|
+
model_name="credential",
|
|
57
|
+
name="learning_context_name",
|
|
58
|
+
field=models.CharField(
|
|
59
|
+
editable=False,
|
|
60
|
+
help_text="Name of the learning context for which the credential was issued. This field is used for validation purposes.",
|
|
61
|
+
max_length=255,
|
|
62
|
+
null=True,
|
|
63
|
+
),
|
|
64
|
+
),
|
|
65
|
+
migrations.AddField(
|
|
66
|
+
model_name="credential",
|
|
67
|
+
name="verify_uuid",
|
|
68
|
+
field=models.UUIDField(
|
|
69
|
+
default=uuid.uuid4,
|
|
70
|
+
editable=False,
|
|
71
|
+
help_text="UUID used for verifying the credential",
|
|
72
|
+
null=True,
|
|
73
|
+
),
|
|
74
|
+
),
|
|
75
|
+
migrations.RunPython(backfill_credential_fields, reverse_code=migrations.RunPython.noop),
|
|
76
|
+
migrations.AlterField(
|
|
77
|
+
model_name="credential",
|
|
78
|
+
name="learning_context_name",
|
|
79
|
+
field=models.CharField(
|
|
80
|
+
editable=False,
|
|
81
|
+
help_text="Name of the learning context for which the credential was issued. This field is used for validation purposes.",
|
|
82
|
+
max_length=255,
|
|
83
|
+
),
|
|
84
|
+
),
|
|
85
|
+
migrations.AlterField(
|
|
86
|
+
model_name="credential",
|
|
87
|
+
name="verify_uuid",
|
|
88
|
+
field=models.UUIDField(
|
|
89
|
+
default=uuid.uuid4,
|
|
90
|
+
editable=False,
|
|
91
|
+
help_text="UUID used for verifying the credential",
|
|
92
|
+
),
|
|
93
|
+
),
|
|
94
|
+
]
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from django.conf import settings
|
|
2
|
+
from django.db import migrations, models
|
|
3
|
+
import django.db.models.deletion
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def populate_user_fk(apps, schema_editor):
|
|
7
|
+
"""Copy user_id values to the new user FK field."""
|
|
8
|
+
Credential = apps.get_model("learning_credentials", "Credential")
|
|
9
|
+
for credential in Credential.objects.all():
|
|
10
|
+
credential.user_id = credential.user_id_old
|
|
11
|
+
credential.save(update_fields=["user_id"])
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def reverse_populate_user_fk(apps, schema_editor):
|
|
15
|
+
"""Copy user FK values back to user_id_old."""
|
|
16
|
+
Credential = apps.get_model("learning_credentials", "Credential")
|
|
17
|
+
for credential in Credential.objects.all():
|
|
18
|
+
credential.user_id_old = credential.user_id
|
|
19
|
+
credential.save(update_fields=["user_id_old"])
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Migration(migrations.Migration):
|
|
23
|
+
dependencies = [
|
|
24
|
+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
25
|
+
("learning_credentials", "0008_validation"),
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
operations = [
|
|
29
|
+
# Rename the old user_id field to user_id_old.
|
|
30
|
+
migrations.RenameField(
|
|
31
|
+
model_name="credential",
|
|
32
|
+
old_name="user_id",
|
|
33
|
+
new_name="user_id_old",
|
|
34
|
+
),
|
|
35
|
+
# Add the new user FK field (nullable initially).
|
|
36
|
+
migrations.AddField(
|
|
37
|
+
model_name="credential",
|
|
38
|
+
name="user",
|
|
39
|
+
field=models.ForeignKey(
|
|
40
|
+
help_text="User receiving the credential",
|
|
41
|
+
null=True,
|
|
42
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
43
|
+
to=settings.AUTH_USER_MODEL,
|
|
44
|
+
),
|
|
45
|
+
),
|
|
46
|
+
# Populate the user FK from user_id_old.
|
|
47
|
+
migrations.RunPython(populate_user_fk, reverse_code=reverse_populate_user_fk),
|
|
48
|
+
# Make the user FK non-nullable.
|
|
49
|
+
migrations.AlterField(
|
|
50
|
+
model_name="credential",
|
|
51
|
+
name="user",
|
|
52
|
+
field=models.ForeignKey(
|
|
53
|
+
help_text="User receiving the credential",
|
|
54
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
55
|
+
to=settings.AUTH_USER_MODEL,
|
|
56
|
+
),
|
|
57
|
+
),
|
|
58
|
+
# Remove the old user_id_old field.
|
|
59
|
+
migrations.RemoveField(
|
|
60
|
+
model_name="credential",
|
|
61
|
+
name="user_id_old",
|
|
62
|
+
),
|
|
63
|
+
]
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from django.db import migrations, models
|
|
2
|
+
import django.db.models.deletion
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def populate_configuration_fk(apps, schema_editor):
|
|
6
|
+
"""
|
|
7
|
+
Populate the configuration FK by finding the matching CredentialConfiguration.
|
|
8
|
+
"""
|
|
9
|
+
Credential = apps.get_model("learning_credentials", "Credential")
|
|
10
|
+
CredentialConfiguration = apps.get_model("learning_credentials", "CredentialConfiguration")
|
|
11
|
+
|
|
12
|
+
for credential in Credential.objects.all():
|
|
13
|
+
try:
|
|
14
|
+
config = CredentialConfiguration.objects.get(
|
|
15
|
+
learning_context_key=credential.learning_context_key,
|
|
16
|
+
credential_type__name=credential.credential_type,
|
|
17
|
+
)
|
|
18
|
+
credential.configuration = config
|
|
19
|
+
credential.save(update_fields=["configuration"])
|
|
20
|
+
except CredentialConfiguration.DoesNotExist:
|
|
21
|
+
# If no matching configuration exists, we cannot migrate this credential.
|
|
22
|
+
# This should not happen in normal circumstances.
|
|
23
|
+
raise ValueError(
|
|
24
|
+
f"No CredentialConfiguration found for Credential {credential.uuid} "
|
|
25
|
+
f"with learning_context_key={credential.learning_context_key} "
|
|
26
|
+
f"and credential_type={credential.credential_type}"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def reverse_populate_configuration_fk(apps, schema_editor):
|
|
31
|
+
"""Reverse migration: populate credential_type and learning_context_key from configuration."""
|
|
32
|
+
Credential = apps.get_model("learning_credentials", "Credential")
|
|
33
|
+
|
|
34
|
+
for credential in Credential.objects.select_related("configuration__credential_type").all():
|
|
35
|
+
credential.credential_type = credential.configuration.credential_type.name
|
|
36
|
+
credential.learning_context_key = credential.configuration.learning_context_key
|
|
37
|
+
credential.save(update_fields=["credential_type", "learning_context_key"])
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Migration(migrations.Migration):
|
|
41
|
+
dependencies = [
|
|
42
|
+
("learning_credentials", "0009_credential_user_fk"),
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
operations = [
|
|
46
|
+
# Add the new configuration FK field (nullable initially).
|
|
47
|
+
migrations.AddField(
|
|
48
|
+
model_name="credential",
|
|
49
|
+
name="configuration",
|
|
50
|
+
field=models.ForeignKey(
|
|
51
|
+
help_text="Associated credential configuration",
|
|
52
|
+
null=True,
|
|
53
|
+
on_delete=django.db.models.deletion.PROTECT,
|
|
54
|
+
to="learning_credentials.credentialconfiguration",
|
|
55
|
+
),
|
|
56
|
+
),
|
|
57
|
+
# Populate the configuration FK from credential_type and learning_context_key.
|
|
58
|
+
migrations.RunPython(populate_configuration_fk, reverse_code=reverse_populate_configuration_fk),
|
|
59
|
+
# Make the configuration FK non-nullable
|
|
60
|
+
migrations.AlterField(
|
|
61
|
+
model_name="credential",
|
|
62
|
+
name="configuration",
|
|
63
|
+
field=models.ForeignKey(
|
|
64
|
+
help_text="Associated credential configuration",
|
|
65
|
+
on_delete=django.db.models.deletion.PROTECT,
|
|
66
|
+
to="learning_credentials.credentialconfiguration",
|
|
67
|
+
),
|
|
68
|
+
),
|
|
69
|
+
# Remove the old credential_type field.
|
|
70
|
+
migrations.RemoveField(
|
|
71
|
+
model_name="credential",
|
|
72
|
+
name="credential_type",
|
|
73
|
+
),
|
|
74
|
+
# Remove the redundant learning_context_key field (now accessible via configuration).
|
|
75
|
+
migrations.RemoveField(
|
|
76
|
+
model_name="credential",
|
|
77
|
+
name="learning_context_key",
|
|
78
|
+
),
|
|
79
|
+
]
|