Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ class Migration(migrations.Migration):
name='Collection',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('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)),
('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)),
('name', openedx_learning.lib.fields.case_insensitive_char_field(db_index=True, help_text='The name of the collection.', max_length=255)),
('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)),
('enabled', models.BooleanField(default=True, help_text='Whether the collection is enabled or not.')),
('created', models.DateTimeField(auto_now_add=True)),
('modified', models.DateTimeField(auto_now=True)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='collection',
name='title',
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),
field=openedx_learning.lib.fields.case_insensitive_char_field(default='Collection', help_text='The title of the collection.', max_length=500),
preserve_default=False,
),
migrations.AlterField(
Expand All @@ -39,7 +39,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='collection',
name='description',
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),
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),
),
migrations.AlterField(
model_name='collection',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='collection',
name='key',
field=openedx_learning.lib.fields.MultiCollationCharField(
db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'},
field=openedx_learning.lib.fields.case_sensitive_char_field(
db_column='_key', max_length=500, null=True, blank=True),
preserve_default=False,
),
Expand All @@ -44,8 +43,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='collection',
name='key',
field=openedx_learning.lib.fields.MultiCollationCharField(
db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'},
field=openedx_learning.lib.fields.case_sensitive_char_field(
db_column='_key', max_length=500, null=False, blank=False),
preserve_default=False,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class Migration(migrations.Migration):
name='Component',
fields=[
('publishable_entity', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='oel_publishing.publishableentity')),
('local_key', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, max_length=500)),
('local_key', openedx_learning.lib.fields.case_sensitive_char_field(max_length=500)),
],
options={
'verbose_name': 'Component',
Expand All @@ -33,8 +33,8 @@ class Migration(migrations.Migration):
name='ComponentType',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('namespace', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, max_length=100)),
('name', openedx_learning.lib.fields.MultiCollationCharField(blank=True, db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, max_length=100)),
('namespace', openedx_learning.lib.fields.case_sensitive_char_field(max_length=100)),
('name', openedx_learning.lib.fields.case_sensitive_char_field(blank=True, max_length=100)),
],
),
migrations.CreateModel(
Expand All @@ -53,7 +53,7 @@ class Migration(migrations.Migration):
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='UUID')),
('key', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, max_length=500)),
('key', openedx_learning.lib.fields.case_sensitive_char_field(max_length=500)),
('learner_downloadable', models.BooleanField(default=False)),
('component_version', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='oel_components.componentversion')),
('content', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to='oel_contents.content')),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='componentversioncontent',
name='key',
field=openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, db_column='_key', max_length=500),
field=openedx_learning.lib.fields.case_sensitive_char_field(db_column='_key', max_length=500),
),
]
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class Migration(migrations.Migration):
('size', models.PositiveBigIntegerField(validators=[django.core.validators.MaxValueValidator(50000000)])),
('hash_digest', models.CharField(editable=False, max_length=40)),
('has_file', models.BooleanField()),
('text', openedx_learning.lib.fields.MultiCollationTextField(blank=True, db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, max_length=50000, null=True)),
('text', openedx_learning.lib.fields.case_insensitive_text_field(blank=True, max_length=50000, null=True)),
('created', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])),
],
options={
Expand All @@ -36,9 +36,9 @@ class Migration(migrations.Migration):
name='MediaType',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('type', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, max_length=127)),
('sub_type', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, max_length=127)),
('suffix', openedx_learning.lib.fields.MultiCollationCharField(blank=True, db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, max_length=127)),
('type', openedx_learning.lib.fields.case_insensitive_char_field(max_length=127)),
('sub_type', openedx_learning.lib.fields.case_insensitive_char_field(max_length=127)),
('suffix', openedx_learning.lib.fields.case_insensitive_char_field(blank=True, max_length=127)),
],
),
migrations.AddConstraint(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ class Migration(migrations.Migration):
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='UUID')),
('key', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, max_length=500)),
('title', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, max_length=500)),
('description', openedx_learning.lib.fields.MultiCollationTextField(blank=True, db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, default='', max_length=10000)),
('key', openedx_learning.lib.fields.case_sensitive_char_field(max_length=500)),
('title', openedx_learning.lib.fields.case_insensitive_char_field(max_length=500)),
('description', openedx_learning.lib.fields.case_insensitive_text_field(blank=True, default='', max_length=10000)),
('created', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])),
('updated', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])),
],
Expand All @@ -41,7 +41,7 @@ class Migration(migrations.Migration):
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='UUID')),
('key', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, max_length=500)),
('key', openedx_learning.lib.fields.case_sensitive_char_field(max_length=500)),
('created', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])),
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
('learning_package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='publishable_entities', to='oel_publishing.learningpackage')),
Expand All @@ -56,7 +56,7 @@ class Migration(migrations.Migration):
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='UUID')),
('title', openedx_learning.lib.fields.MultiCollationCharField(blank=True, db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, default='', max_length=500)),
('title', openedx_learning.lib.fields.case_insensitive_char_field(blank=True, default='', max_length=500)),
('version_num', models.PositiveIntegerField(validators=[django.core.validators.MinValueValidator(1)])),
('created', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])),
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
Expand All @@ -72,7 +72,7 @@ class Migration(migrations.Migration):
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='UUID')),
('message', openedx_learning.lib.fields.MultiCollationCharField(blank=True, db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, default='', max_length=500)),
('message', openedx_learning.lib.fields.case_insensitive_char_field(blank=True, default='', max_length=500)),
('published_at', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])),
('learning_package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='oel_publishing.learningpackage')),
('published_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='learningpackage',
name='key',
field=openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, db_column='_key', max_length=500),
field=openedx_learning.lib.fields.case_sensitive_char_field(db_column='_key', max_length=500),
),
migrations.AlterField(
model_name='publishableentity',
name='key',
field=openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, db_column='_key', max_length=500),
field=openedx_learning.lib.fields.case_sensitive_char_field(db_column='_key', max_length=500),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Generated manually for PostgreSQL collation support
from django.contrib.postgres.operations import CreateCollation
from django.db import migrations


class Migration(migrations.Migration):
run_before = [
("oel_publishing", "0001_initial"),
]
operations = [
# Create a custom case-insensitive collation for PostgreSQL.
# This collation is used by case_insensitive_char_field() to provide
# case-insensitive comparisons and unique constraints on PostgreSQL,
# matching the behavior of MySQL's utf8mb4_unicode_ci collation.
#
# Note: CreateCollation is a PostgreSQL-specific operation from
# django.contrib.postgres.operations. Django automatically skips
# PostgreSQL-specific operations when running migrations on other
# database backends (MySQL, SQLite, etc.). The operation checks
# schema_editor.connection.vendor and only executes when vendor=='postgresql'.
#
# Requirements:
# - PostgreSQL 12+ (for non-deterministic collations)
# - PostgreSQL compiled with ICU support (standard in most distributions)
#
# This works regardless of the database's locale_provider setting
# (whether it's 'libc', 'icu', or 'c').
CreateCollation(
"case_insensitive",
provider="icu",
locale="und-u-ks-level2",
deterministic=False,
),
]
10 changes: 6 additions & 4 deletions openedx_learning/lib/collations.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
This module has collation-related code to allow us to attach collation settings
to specific fields on a per-database-vendor basis. This used by the ``fields``
module in order to specify field types have have normalized behavior between
SQLite and MySQL (see fields.py for more details).
SQLite, MySQL, and PostgreSQL (see fields.py for more details).
"""

from django.db import models


Expand All @@ -23,8 +24,9 @@ def __init__(self, *args, db_collations=None, db_collation=None, **kwargs): # p
collations, like::

{
'msyql': 'utf8mb4_bin',
'sqlite': 'BINARY'
'mysql': 'utf8mb4_bin',
'sqlite': 'BINARY',
'postgresql': 'C'
}

It is an error to pass in a CharField-style ``db_collation``. I
Expand All @@ -42,7 +44,7 @@ def db_parameters(self, connection):

We examine this field's ``db_collations`` attribute and return the
collation that maps to ``connection.vendor``. This will typically be
'mysql' or 'sqlite'.
'mysql', 'sqlite', or 'postgresql'.
"""
db_params = models.Field.db_parameters(self, connection)

Expand Down
Loading