Skip to content

Commit 8bc745c

Browse files
feat: update migrations to support pg18+
Also refactor common calls for DRY and simplifying any later standard collation updates Signed-off-by: Anton Melser <[email protected]>
1 parent 740ef71 commit 8bc745c

File tree

15 files changed

+94
-106
lines changed

15 files changed

+94
-106
lines changed

openedx_learning/apps/authoring/collections/migrations/0001_initial.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ class Migration(migrations.Migration):
1919
name='Collection',
2020
fields=[
2121
('id', models.AutoField(primary_key=True, serialize=False)),
22-
('name', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, db_index=True, help_text='The name of the collection.', max_length=255)),
23-
('description', openedx_learning.lib.fields.MultiCollationCharField(blank=True, db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, help_text='Provides extra information for the user about this collection.', max_length=10000)),
22+
('name', openedx_learning.lib.fields.case_insensitive_char_field(db_index=True, help_text='The name of the collection.', max_length=255)),
23+
('description', openedx_learning.lib.fields.case_insensitive_char_field(blank=True, help_text='Provides extra information for the user about this collection.', max_length=10000)),
2424
('enabled', models.BooleanField(default=True, help_text='Whether the collection is enabled or not.')),
2525
('created', models.DateTimeField(auto_now_add=True)),
2626
('modified', models.DateTimeField(auto_now=True)),

openedx_learning/apps/authoring/collections/migrations/0002_remove_collection_name_collection_created_by_and_more.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class Migration(migrations.Migration):
2828
migrations.AddField(
2929
model_name='collection',
3030
name='title',
31-
field=openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, default='Collection', help_text='The title of the collection.', max_length=500),
31+
field=openedx_learning.lib.fields.case_insensitive_char_field(default='Collection', help_text='The title of the collection.', max_length=500),
3232
preserve_default=False,
3333
),
3434
migrations.AlterField(
@@ -39,7 +39,7 @@ class Migration(migrations.Migration):
3939
migrations.AlterField(
4040
model_name='collection',
4141
name='description',
42-
field=openedx_learning.lib.fields.MultiCollationTextField(blank=True, db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, default='', help_text='Provides extra information for the user about this collection.', max_length=10000),
42+
field=openedx_learning.lib.fields.case_insensitive_text_field(blank=True, default='', help_text='Provides extra information for the user about this collection.', max_length=10000),
4343
),
4444
migrations.AlterField(
4545
model_name='collection',

openedx_learning/apps/authoring/collections/migrations/0004_collection_key.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,7 @@ class Migration(migrations.Migration):
3333
migrations.AddField(
3434
model_name='collection',
3535
name='key',
36-
field=openedx_learning.lib.fields.MultiCollationCharField(
37-
db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'},
36+
field=openedx_learning.lib.fields.case_sensitive_char_field(
3837
db_column='_key', max_length=500, null=True, blank=True),
3938
preserve_default=False,
4039
),
@@ -44,8 +43,7 @@ class Migration(migrations.Migration):
4443
migrations.AlterField(
4544
model_name='collection',
4645
name='key',
47-
field=openedx_learning.lib.fields.MultiCollationCharField(
48-
db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'},
46+
field=openedx_learning.lib.fields.case_sensitive_char_field(
4947
db_column='_key', max_length=500, null=False, blank=False),
5048
preserve_default=False,
5149
),

openedx_learning/apps/authoring/components/migrations/0001_initial.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class Migration(migrations.Migration):
2222
name='Component',
2323
fields=[
2424
('publishable_entity', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='oel_publishing.publishableentity')),
25-
('local_key', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, max_length=500)),
25+
('local_key', openedx_learning.lib.fields.case_sensitive_char_field(max_length=500)),
2626
],
2727
options={
2828
'verbose_name': 'Component',
@@ -33,8 +33,8 @@ class Migration(migrations.Migration):
3333
name='ComponentType',
3434
fields=[
3535
('id', models.AutoField(primary_key=True, serialize=False)),
36-
('namespace', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, max_length=100)),
37-
('name', openedx_learning.lib.fields.MultiCollationCharField(blank=True, db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, max_length=100)),
36+
('namespace', openedx_learning.lib.fields.case_sensitive_char_field(max_length=100)),
37+
('name', openedx_learning.lib.fields.case_sensitive_char_field(blank=True, max_length=100)),
3838
],
3939
),
4040
migrations.CreateModel(
@@ -53,7 +53,7 @@ class Migration(migrations.Migration):
5353
fields=[
5454
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
5555
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='UUID')),
56-
('key', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, max_length=500)),
56+
('key', openedx_learning.lib.fields.case_sensitive_char_field(max_length=500)),
5757
('learner_downloadable', models.BooleanField(default=False)),
5858
('component_version', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='oel_components.componentversion')),
5959
('content', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to='oel_contents.content')),

openedx_learning/apps/authoring/components/migrations/0002_alter_componentversioncontent_key.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@ class Migration(migrations.Migration):
1515
migrations.AlterField(
1616
model_name='componentversioncontent',
1717
name='key',
18-
field=openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, db_column='_key', max_length=500),
18+
field=openedx_learning.lib.fields.case_sensitive_char_field(db_column='_key', max_length=500),
1919
),
2020
]

openedx_learning/apps/authoring/contents/migrations/0001_initial.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class Migration(migrations.Migration):
2424
('size', models.PositiveBigIntegerField(validators=[django.core.validators.MaxValueValidator(50000000)])),
2525
('hash_digest', models.CharField(editable=False, max_length=40)),
2626
('has_file', models.BooleanField()),
27-
('text', openedx_learning.lib.fields.MultiCollationTextField(blank=True, db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, max_length=50000, null=True)),
27+
('text', openedx_learning.lib.fields.case_insensitive_text_field(blank=True, max_length=50000, null=True)),
2828
('created', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])),
2929
],
3030
options={
@@ -36,9 +36,9 @@ class Migration(migrations.Migration):
3636
name='MediaType',
3737
fields=[
3838
('id', models.AutoField(primary_key=True, serialize=False)),
39-
('type', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, max_length=127)),
40-
('sub_type', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, max_length=127)),
41-
('suffix', openedx_learning.lib.fields.MultiCollationCharField(blank=True, db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, max_length=127)),
39+
('type', openedx_learning.lib.fields.case_insensitive_char_field(max_length=127)),
40+
('sub_type', openedx_learning.lib.fields.case_insensitive_char_field(max_length=127)),
41+
('suffix', openedx_learning.lib.fields.case_insensitive_char_field(blank=True, max_length=127)),
4242
],
4343
),
4444
migrations.AddConstraint(

openedx_learning/apps/authoring/publishing/migrations/0001_initial.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ class Migration(migrations.Migration):
2525
fields=[
2626
('id', models.AutoField(primary_key=True, serialize=False)),
2727
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='UUID')),
28-
('key', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, max_length=500)),
29-
('title', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, max_length=500)),
30-
('description', openedx_learning.lib.fields.MultiCollationTextField(blank=True, db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, default='', max_length=10000)),
28+
('key', openedx_learning.lib.fields.case_sensitive_char_field(max_length=500)),
29+
('title', openedx_learning.lib.fields.case_insensitive_char_field(max_length=500)),
30+
('description', openedx_learning.lib.fields.case_insensitive_text_field(blank=True, default='', max_length=10000)),
3131
('created', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])),
3232
('updated', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])),
3333
],
@@ -41,7 +41,7 @@ class Migration(migrations.Migration):
4141
fields=[
4242
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
4343
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='UUID')),
44-
('key', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, max_length=500)),
44+
('key', openedx_learning.lib.fields.case_sensitive_char_field(max_length=500)),
4545
('created', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])),
4646
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
4747
('learning_package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='publishable_entities', to='oel_publishing.learningpackage')),
@@ -56,7 +56,7 @@ class Migration(migrations.Migration):
5656
fields=[
5757
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
5858
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='UUID')),
59-
('title', openedx_learning.lib.fields.MultiCollationCharField(blank=True, db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, default='', max_length=500)),
59+
('title', openedx_learning.lib.fields.case_insensitive_char_field(blank=True, default='', max_length=500)),
6060
('version_num', models.PositiveIntegerField(validators=[django.core.validators.MinValueValidator(1)])),
6161
('created', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])),
6262
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
@@ -72,7 +72,7 @@ class Migration(migrations.Migration):
7272
fields=[
7373
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
7474
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='UUID')),
75-
('message', openedx_learning.lib.fields.MultiCollationCharField(blank=True, db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, default='', max_length=500)),
75+
('message', openedx_learning.lib.fields.case_insensitive_char_field(blank=True, default='', max_length=500)),
7676
('published_at', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])),
7777
('learning_package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='oel_publishing.learningpackage')),
7878
('published_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),

openedx_learning/apps/authoring/publishing/migrations/0002_alter_learningpackage_key_and_more.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ class Migration(migrations.Migration):
1515
migrations.AlterField(
1616
model_name='learningpackage',
1717
name='key',
18-
field=openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, db_column='_key', max_length=500),
18+
field=openedx_learning.lib.fields.case_sensitive_char_field(db_column='_key', max_length=500),
1919
),
2020
migrations.AlterField(
2121
model_name='publishableentity',
2222
name='key',
23-
field=openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, db_column='_key', max_length=500),
23+
field=openedx_learning.lib.fields.case_sensitive_char_field(db_column='_key', max_length=500),
2424
),
2525
]

openedx_learning/lib/fields.py

Lines changed: 54 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,8 @@ def create_hash_digest(data_bytes: bytes) -> str:
3636
return hashlib.blake2b(data_bytes, digest_size=20).hexdigest()
3737

3838

39-
def case_insensitive_char_field(**kwargs) -> MultiCollationCharField:
40-
"""
41-
Return a case-insensitive ``MultiCollationCharField``.
42-
43-
This means that entries will sort in a case-insensitive manner, and that
44-
unique indexes will be case insensitive, e.g. you would not be able to
45-
insert "abc" and "ABC" into the same table field if you put a unique index
46-
on this field.
47-
48-
You may override any argument that you would normally pass into
49-
``MultiCollationCharField`` (which is itself a subclass of ``CharField``).
50-
"""
51-
# Set our default arguments
52-
final_kwargs = {
39+
def default_case_insensitive_collations_args(**kwargs):
40+
return {
5341
"null": False,
5442
"db_collations": {
5543
"sqlite": "NOCASE",
@@ -67,11 +55,40 @@ def case_insensitive_char_field(**kwargs) -> MultiCollationCharField:
6755
# This gives us behavior similar to MySQL's utf8mb4_unicode_ci.
6856
"postgresql": "case_insensitive",
6957
},
58+
**kwargs,
7059
}
71-
# Override our defaults with whatever is passed in.
72-
final_kwargs.update(kwargs)
7360

74-
return MultiCollationCharField(**final_kwargs)
61+
62+
def default_case_sensitive_collations_args(**kwargs):
63+
return {
64+
"null": False,
65+
"db_collations": {
66+
"sqlite": "BINARY",
67+
"mysql": "utf8mb4_bin",
68+
# PostgreSQL: Using "C" collation for case-sensitive, byte-order comparisons.
69+
# This is the fastest collation and provides strict case-sensitive matching
70+
# similar to MySQL's utf8mb4_bin and SQLite's BINARY.
71+
# The "C" collation is always available in PostgreSQL and doesn't depend on
72+
# locale settings.
73+
"postgresql": "C",
74+
},
75+
**kwargs,
76+
}
77+
78+
79+
def case_insensitive_char_field(**kwargs) -> MultiCollationCharField:
80+
"""
81+
Return a case-insensitive ``MultiCollationCharField``.
82+
83+
This means that entries will sort in a case-insensitive manner, and that
84+
unique indexes will be case insensitive, e.g. you would not be able to
85+
insert "abc" and "ABC" into the same table field if you put a unique index
86+
on this field.
87+
88+
You may override any argument that you would normally pass into
89+
``MultiCollationCharField`` (which is itself a subclass of ``CharField``).
90+
"""
91+
return MultiCollationCharField(**default_case_insensitive_collations_args(**kwargs))
7592

7693

7794
def case_sensitive_char_field(**kwargs) -> MultiCollationCharField:
@@ -86,24 +103,27 @@ def case_sensitive_char_field(**kwargs) -> MultiCollationCharField:
86103
You may override any argument that you would normally pass into
87104
``MultiCollationCharField`` (which is itself a subclass of ``CharField``).
88105
"""
89-
# Set our default arguments
90-
final_kwargs = {
91-
"null": False,
92-
"db_collations": {
93-
"sqlite": "BINARY",
94-
"mysql": "utf8mb4_bin",
95-
# PostgreSQL: Using "C" collation for case-sensitive, byte-order comparisons.
96-
# This is the fastest collation and provides strict case-sensitive matching
97-
# similar to MySQL's utf8mb4_bin and SQLite's BINARY.
98-
# The "C" collation is always available in PostgreSQL and doesn't depend on
99-
# locale settings.
100-
"postgresql": "C",
101-
},
102-
}
103-
# Override our defaults with whatever is passed in.
104-
final_kwargs.update(kwargs)
106+
return MultiCollationCharField(**default_case_sensitive_collations_args(**kwargs))
105107

106-
return MultiCollationCharField(**final_kwargs)
108+
109+
def case_insensitive_text_field(**kwargs) -> MultiCollationTextField:
110+
"""
111+
Return a case-insensitive ``MultiCollationTextField``.
112+
113+
You may override any argument that you would normally pass into
114+
``MultiCollationTextField`` (which is itself a subclass of ``TextField``).
115+
"""
116+
return MultiCollationTextField(**default_case_insensitive_collations_args(**kwargs))
117+
118+
119+
def case_sensitive_text_field(**kwargs) -> MultiCollationTextField:
120+
"""
121+
Return a case-sensitive ``MultiCollationTextField``.
122+
123+
You may override any argument that you would normally pass into
124+
``MultiCollationTextField`` (which is itself a subclass of ``TextField``).
125+
"""
126+
return MultiCollationTextField(**default_case_sensitive_collations_args(**kwargs))
107127

108128

109129
def immutable_uuid_field() -> models.UUIDField:

0 commit comments

Comments
 (0)