diff --git a/common/djangoapps/split_modulestore_django/migrations/0001_initial.py b/common/djangoapps/split_modulestore_django/migrations/0001_initial.py index 25b752da68d8..46f3381044ba 100644 --- a/common/djangoapps/split_modulestore_django/migrations/0001_initial.py +++ b/common/djangoapps/split_modulestore_django/migrations/0001_initial.py @@ -1,15 +1,40 @@ # Generated by Django 2.2.20 on 2021-05-07 18:29, manually modified to make "course_id" column case sensitive from django.conf import settings -from django.db import migrations, models +from django.db import migrations, models, connection import django.db.models.deletion import opaque_keys.edx.django.models import simple_history.models +def generate_split_module_sql(db_engine): + if 'mysql' in db_engine: + return 'ALTER TABLE split_modulestore_django_splitmodulestorecourseindex MODIFY course_id varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL UNIQUE;' + elif 'postgresql' in db_engine: + return """ + ALTER TABLE split_modulestore_django_splitmodulestorecourseindex + ALTER COLUMN course_id TYPE VARCHAR(255), + ALTER COLUMN course_id SET NOT NULL; + + ALTER TABLE split_modulestore_django_splitmodulestorecourseindex + ADD CONSTRAINT course_id_unique UNIQUE (course_id); + """ + + +def generate_split_history_module_sql(db_engine): + if 'mysql' in db_engine: + return 'ALTER TABLE split_modulestore_django_historicalsplitmodulestorecourseindex MODIFY course_id varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL;' + elif 'postgresql' in db_engine: + return """ + ALTER TABLE split_modulestore_django_historicalsplitmodulestorecourseindex + ALTER COLUMN course_id TYPE VARCHAR(255), + ALTER COLUMN course_id SET NOT NULL, + ALTER COLUMN course_id SET DATA TYPE VARCHAR(255) COLLATE "C"; + """ class Migration(migrations.Migration): initial = True + db_engine = connection.settings_dict['ENGINE'] dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), @@ -65,11 +90,11 @@ class Migration(migrations.Migration): # Custom code: Convert columns to utf8_bin because we want to allow # case-sensitive comparisons for CourseKeys, which were case-sensitive in MongoDB migrations.RunSQL( - 'ALTER TABLE split_modulestore_django_splitmodulestorecourseindex MODIFY course_id varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL UNIQUE;', + generate_split_module_sql(db_engine), reverse_sql=migrations.RunSQL.noop, ), migrations.RunSQL( - 'ALTER TABLE split_modulestore_django_historicalsplitmodulestorecourseindex MODIFY course_id varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL;', + generate_split_history_module_sql(db_engine), reverse_sql=migrations.RunSQL.noop, ), ] diff --git a/lms/djangoapps/commerce/migrations/0001_data__add_ecommerce_service_user.py b/lms/djangoapps/commerce/migrations/0001_data__add_ecommerce_service_user.py index bc83ce2288b5..9deb9dac4a7c 100644 --- a/lms/djangoapps/commerce/migrations/0001_data__add_ecommerce_service_user.py +++ b/lms/djangoapps/commerce/migrations/0001_data__add_ecommerce_service_user.py @@ -1,7 +1,7 @@ from django.conf import settings from django.contrib.auth import get_user_model from django.contrib.auth.models import User -from django.db import migrations, models +from django.db import migrations, models, transaction USERNAME = settings.ECOMMERCE_SERVICE_WORKER_USERNAME EMAIL = USERNAME + '@fake.email' @@ -9,14 +9,16 @@ def forwards(apps, schema_editor): """Add the service user.""" User = get_user_model() - user, created = User.objects.get_or_create(username=USERNAME, email=EMAIL) - if created: - user.set_unusable_password() - user.save() + with transaction.atomic(): + user, created = User.objects.get_or_create(username=USERNAME, email=EMAIL) + if created: + user.set_unusable_password() + user.save() def backwards(apps, schema_editor): """Remove the service user.""" - User.objects.get(username=USERNAME, email=EMAIL).delete() + with transaction.atomic(): + User.objects.get(username=USERNAME, email=EMAIL).delete() class Migration(migrations.Migration): diff --git a/lms/djangoapps/courseware/fields.py b/lms/djangoapps/courseware/fields.py index 7929ae5efe94..55c20f948120 100644 --- a/lms/djangoapps/courseware/fields.py +++ b/lms/djangoapps/courseware/fields.py @@ -1,36 +1,3 @@ """ Custom fields """ - - -from django.db.models.fields import AutoField - - -class UnsignedBigIntAutoField(AutoField): - """ - An unsigned 8-byte integer for auto-incrementing primary keys. - """ - def db_type(self, connection): - if connection.settings_dict['ENGINE'] == 'django.db.backends.mysql': - return "bigint UNSIGNED AUTO_INCREMENT" - elif connection.settings_dict['ENGINE'] == 'django.db.backends.sqlite3': - # Sqlite will only auto-increment the ROWID column. Any INTEGER PRIMARY KEY column - # is an alias for that (https://www.sqlite.org/autoinc.html). An unsigned integer - # isn't an alias for ROWID, so we have to give up on the unsigned part. - return "integer" - elif connection.settings_dict['ENGINE'] == 'django.db.backends.postgresql_psycopg2': - # Pg's bigserial is implicitly unsigned (doesn't allow negative numbers) and - # goes 1-9.2x10^18 - return "BIGSERIAL" - else: - return None - - def rel_db_type(self, connection): - if connection.settings_dict['ENGINE'] == 'django.db.backends.mysql': - return "bigint UNSIGNED" - elif connection.settings_dict['ENGINE'] == 'django.db.backends.sqlite3': - return "integer" - elif connection.settings_dict['ENGINE'] == 'django.db.backends.postgresql_psycopg2': - return "BIGSERIAL" - else: - return None diff --git a/lms/djangoapps/courseware/migrations/0001_initial.py b/lms/djangoapps/courseware/migrations/0001_initial.py index 56fed9178c88..ff6d8a4bfd06 100644 --- a/lms/djangoapps/courseware/migrations/0001_initial.py +++ b/lms/djangoapps/courseware/migrations/0001_initial.py @@ -53,7 +53,7 @@ class Migration(migrations.Migration): migrations.CreateModel( name='StudentModule', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('id', models.BigAutoField(primary_key=True, serialize=False, verbose_name='ID')), ('module_type', models.CharField(default='problem', max_length=32, db_index=True, choices=[('problem', 'problem'), ('video', 'video'), ('html', 'html'), ('course', 'course'), ('chapter', 'Section'), ('sequential', 'Subsection'), ('library_content', 'Library Content')])), ('module_state_key', UsageKeyField(max_length=255, db_column='module_id', db_index=True)), ('course_id', CourseKeyField(max_length=255, db_index=True)), diff --git a/lms/djangoapps/courseware/migrations/0008_move_idde_to_edx_when.py b/lms/djangoapps/courseware/migrations/0008_move_idde_to_edx_when.py index 632409025cfa..fab182163326 100644 --- a/lms/djangoapps/courseware/migrations/0008_move_idde_to_edx_when.py +++ b/lms/djangoapps/courseware/migrations/0008_move_idde_to_edx_when.py @@ -33,5 +33,5 @@ class Migration(migrations.Migration): ] operations = [ - migrations.RunPython(move_overrides_to_edx_when) + migrations.RunPython(move_overrides_to_edx_when, reverse_code=migrations.RunPython.noop) ] diff --git a/lms/djangoapps/courseware/migrations/0011_csm_id_bigint.py b/lms/djangoapps/courseware/migrations/0011_csm_id_bigint.py index abcebcf0b45f..d76c4c489b2b 100644 --- a/lms/djangoapps/courseware/migrations/0011_csm_id_bigint.py +++ b/lms/djangoapps/courseware/migrations/0011_csm_id_bigint.py @@ -1,31 +1,41 @@ # Generated by Django 1.11.23 on 2019-08-28 15:50 - import lms.djangoapps.courseware.fields from django.conf import settings -from django.db import migrations +from django.db import migrations, models from django.db.migrations import AlterField class CsmBigInt(AlterField): ''' Subclass AlterField migration class to split SQL between two different databases - We can't use the normal AlterField migration operation because Django generate and routes migrations at the model + We can't use the normal AlterField migration operation because Django generates and routes migrations at the model level and the coursewarehistoryextended_studentmodulehistoryextended table is in a different database ''' def database_forwards(self, app_label, schema_editor, from_state, to_state): if hasattr(schema_editor.connection, 'is_in_memory_db') and schema_editor.connection.is_in_memory_db(): # sqlite3 doesn't support 'MODIFY', so skipping during tests return + to_model = to_state.apps.get_model(app_label, self.model_name) + if schema_editor.connection.alias == 'student_module_history': if settings.FEATURES["ENABLE_CSMH_EXTENDED"]: - schema_editor.execute("ALTER TABLE `coursewarehistoryextended_studentmodulehistoryextended` MODIFY `student_module_id` bigint UNSIGNED NOT NULL;") + if schema_editor.connection.vendor == 'mysql': + schema_editor.execute("ALTER TABLE `coursewarehistoryextended_studentmodulehistoryextended` MODIFY `student_module_id` bigint UNSIGNED NOT NULL;") + elif schema_editor.connection.vendor == 'postgresql': + schema_editor.execute("ALTER TABLE coursewarehistoryextended_studentmodulehistoryextended ALTER COLUMN student_module_id TYPE bigint;") elif self.allow_migrate_model(schema_editor.connection.alias, to_model): - schema_editor.execute("ALTER TABLE `courseware_studentmodule` MODIFY `id` bigint UNSIGNED AUTO_INCREMENT NOT NULL;") + if schema_editor.connection.vendor == 'postgresql': + # For PostgreSQL + schema_editor.execute("ALTER TABLE courseware_studentmodule ALTER COLUMN id SET DATA TYPE bigint;") + schema_editor.execute("ALTER TABLE courseware_studentmodule ALTER COLUMN id SET NOT NULL;") + else: + # For MySQL + schema_editor.execute("ALTER TABLE `courseware_studentmodule` MODIFY `id` bigint UNSIGNED AUTO_INCREMENT NOT NULL;") def database_backwards(self, app_label, schema_editor, from_state, to_state): - # Make backwards migration a no-op, app will still work if column is wider than expected + # Make backwards migration a no-op; app will still work if column is wider than expected pass class Migration(migrations.Migration): @@ -33,6 +43,7 @@ class Migration(migrations.Migration): dependencies = [ ('courseware', '0010_auto_20190709_1559'), ] + if settings.FEATURES["ENABLE_CSMH_EXTENDED"]: dependencies.append(('coursewarehistoryextended', '0002_force_studentmodule_index')) @@ -40,6 +51,6 @@ class Migration(migrations.Migration): CsmBigInt( model_name='studentmodule', name='id', - field=lms.djangoapps.courseware.fields.UnsignedBigIntAutoField(primary_key=True, serialize=False), + field=models.BigAutoField(primary_key=True, serialize=False, verbose_name='ID'), ) ] diff --git a/lms/djangoapps/courseware/models.py b/lms/djangoapps/courseware/models.py index b5cd3839c351..3a701644615c 100644 --- a/lms/djangoapps/courseware/models.py +++ b/lms/djangoapps/courseware/models.py @@ -28,7 +28,6 @@ from edx_django_utils.cache.utils import RequestCache from model_utils.models import TimeStampedModel from opaque_keys.edx.django.models import BlockTypeKeyField, CourseKeyField, LearningContextKeyField, UsageKeyField -from lms.djangoapps.courseware.fields import UnsignedBigIntAutoField from openedx.core.djangolib.markup import HTML @@ -86,7 +85,7 @@ class StudentModule(models.Model): """ objects = ChunkingManager() - id = UnsignedBigIntAutoField(primary_key=True) # pylint: disable=invalid-name + id = models.BigAutoField(verbose_name='ID', primary_key=True) # pylint: disable=invalid-name ## The XBlock/XModule type (e.g. "problem") module_type = models.CharField(max_length=32, db_index=True) diff --git a/lms/djangoapps/coursewarehistoryextended/migrations/0001_initial.py b/lms/djangoapps/coursewarehistoryextended/migrations/0001_initial.py index 12687dd7aa26..8f788aefa607 100644 --- a/lms/djangoapps/coursewarehistoryextended/migrations/0001_initial.py +++ b/lms/djangoapps/coursewarehistoryextended/migrations/0001_initial.py @@ -2,7 +2,6 @@ from django.db import migrations, models import django.db.models.deletion -from lms.djangoapps.courseware.fields import UnsignedBigIntAutoField from django.conf import settings def bump_pk_start(apps, schema_editor): @@ -45,7 +44,7 @@ class Migration(migrations.Migration): ('state', models.TextField(null=True, blank=True)), ('grade', models.FloatField(null=True, blank=True)), ('max_grade', models.FloatField(null=True, blank=True)), - ('id', UnsignedBigIntAutoField(serialize=False, primary_key=True)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), ('student_module', models.ForeignKey(to='courseware.StudentModule', on_delete=django.db.models.deletion.DO_NOTHING, db_constraint=False)), ], options={ diff --git a/lms/djangoapps/coursewarehistoryextended/models.py b/lms/djangoapps/coursewarehistoryextended/models.py index 37de4a93ab58..ddd098304586 100644 --- a/lms/djangoapps/coursewarehistoryextended/models.py +++ b/lms/djangoapps/coursewarehistoryextended/models.py @@ -19,7 +19,6 @@ from django.dispatch import receiver from lms.djangoapps.courseware.models import BaseStudentModuleHistory, StudentModule -from lms.djangoapps.courseware.fields import UnsignedBigIntAutoField class StudentModuleHistoryExtended(BaseStudentModuleHistory): @@ -35,7 +34,7 @@ class Meta: get_latest_by = "created" index_together = ['student_module'] - id = UnsignedBigIntAutoField(primary_key=True) # pylint: disable=invalid-name + id = models.BigAutoField(primary_key=True, serialize=False) # pylint: disable=invalid-name student_module = models.ForeignKey(StudentModule, db_index=True, db_constraint=False, on_delete=models.DO_NOTHING) diff --git a/lms/djangoapps/grades/migrations/0001_initial.py b/lms/djangoapps/grades/migrations/0001_initial.py index aac99bb659aa..cedea6b3150b 100644 --- a/lms/djangoapps/grades/migrations/0001_initial.py +++ b/lms/djangoapps/grades/migrations/0001_initial.py @@ -3,8 +3,6 @@ from django.db import migrations, models from opaque_keys.edx.django.models import CourseKeyField, UsageKeyField -from lms.djangoapps.courseware.fields import UnsignedBigIntAutoField - class Migration(migrations.Migration): @@ -17,7 +15,7 @@ class Migration(migrations.Migration): fields=[ ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)), ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)), - ('id', UnsignedBigIntAutoField(serialize=False, primary_key=True)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), ('user_id', models.IntegerField()), ('course_id', CourseKeyField(max_length=255)), ('usage_key', UsageKeyField(max_length=255)), diff --git a/lms/djangoapps/grades/migrations/0006_persistent_course_grades.py b/lms/djangoapps/grades/migrations/0006_persistent_course_grades.py index 182e4e6e9f87..650049803370 100644 --- a/lms/djangoapps/grades/migrations/0006_persistent_course_grades.py +++ b/lms/djangoapps/grades/migrations/0006_persistent_course_grades.py @@ -3,8 +3,6 @@ from django.db import migrations, models from opaque_keys.edx.django.models import CourseKeyField -from lms.djangoapps.courseware.fields import UnsignedBigIntAutoField - class Migration(migrations.Migration): @@ -18,7 +16,7 @@ class Migration(migrations.Migration): fields=[ ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)), ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)), - ('id', UnsignedBigIntAutoField(serialize=False, primary_key=True)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), ('user_id', models.IntegerField(db_index=True)), ('course_id', CourseKeyField(max_length=255)), ('course_edited_timestamp', models.DateTimeField(verbose_name='Last content edit timestamp')), diff --git a/lms/djangoapps/grades/migrations/0015_historicalpersistentsubsectiongradeoverride.py b/lms/djangoapps/grades/migrations/0015_historicalpersistentsubsectiongradeoverride.py index 75522833ae54..8670853f9acf 100644 --- a/lms/djangoapps/grades/migrations/0015_historicalpersistentsubsectiongradeoverride.py +++ b/lms/djangoapps/grades/migrations/0015_historicalpersistentsubsectiongradeoverride.py @@ -1,14 +1,13 @@ # Generated by Django 1.11.20 on 2019-06-05 13:59 -from django.conf import settings -from django.db import migrations, models import django.db.models.deletion import simple_history.models +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ('grades', '0014_persistentsubsectiongradeoverridehistory'), @@ -28,15 +27,24 @@ class Migration(migrations.Migration): ('history_id', models.AutoField(primary_key=True, serialize=False)), ('history_date', models.DateTimeField()), ('history_change_reason', models.CharField(max_length=100, null=True)), - ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), - ('grade', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='grades.PersistentSubsectionGrade')), - ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), + ('history_type', + models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), + ('history_user', + models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', + to=settings.AUTH_USER_MODEL)), ], - options={ - 'ordering': ('-history_date', '-history_id'), - 'get_latest_by': 'history_date', - 'verbose_name': 'historical persistent subsection grade override', - }, - bases=(simple_history.models.HistoricalChanges, models.Model), + options = { + 'ordering': ('-history_date', '-history_id'), + 'get_latest_by': 'history_date', + 'verbose_name': 'historical persistent subsection grade override', + }, + bases = (simple_history.models.HistoricalChanges, models.Model), + ), + migrations.AddField( + model_name='historicalpersistentsubsectiongradeoverride', + name='grade', + field=models.ForeignKey(blank=True, db_constraint=False, null=True, + on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', + to='grades.PersistentSubsectionGrade'), ), ] diff --git a/lms/djangoapps/grades/models.py b/lms/djangoapps/grades/models.py index a5608eb39afd..b846d0132704 100644 --- a/lms/djangoapps/grades/models.py +++ b/lms/djangoapps/grades/models.py @@ -26,7 +26,6 @@ from opaque_keys.edx.keys import CourseKey, UsageKey from simple_history.models import HistoricalRecords -from lms.djangoapps.courseware.fields import UnsignedBigIntAutoField from lms.djangoapps.grades import events # lint-amnesty, pylint: disable=unused-import from openedx.core.lib.cache_utils import get_cache from lms.djangoapps.grades.signals.signals import ( @@ -321,7 +320,7 @@ class Meta: ] # primary key will need to be large for this table - id = UnsignedBigIntAutoField(primary_key=True) # pylint: disable=invalid-name + id = models.BigAutoField(primary_key=True) # pylint: disable=invalid-name user_id = models.IntegerField(blank=False) course_id = CourseKeyField(blank=False, max_length=255) @@ -581,7 +580,7 @@ class Meta: ] # primary key will need to be large for this table - id = UnsignedBigIntAutoField(primary_key=True) # pylint: disable=invalid-name + id = models.BigAutoField(primary_key=True) # pylint: disable=invalid-name user_id = models.IntegerField(blank=False, db_index=True) course_id = CourseKeyField(blank=False, max_length=255) diff --git a/openedx/core/djangoapps/common_initialization/apps.py b/openedx/core/djangoapps/common_initialization/apps.py index ccbe6d76d929..f6977c68897e 100644 --- a/openedx/core/djangoapps/common_initialization/apps.py +++ b/openedx/core/djangoapps/common_initialization/apps.py @@ -2,8 +2,8 @@ Common initialization app for the LMS and CMS """ - from django.apps import AppConfig +from django.db import connection class CommonInitializationConfig(AppConfig): # lint-amnesty, pylint: disable=missing-class-docstring @@ -14,6 +14,7 @@ def ready(self): # Common settings validations for the LMS and CMS. from . import checks # lint-amnesty, pylint: disable=unused-import self._add_mimetypes() + self._add_required_adapters() @staticmethod def _add_mimetypes(): @@ -26,3 +27,18 @@ def _add_mimetypes(): mimetypes.add_type('application/x-font-opentype', '.otf') mimetypes.add_type('application/x-font-ttf', '.ttf') mimetypes.add_type('application/font-woff', '.woff') + + @staticmethod + def _add_required_adapters(): + """ + Register CourseLocator in psycopg2 extensions + :return: + """ + if 'postgresql' in connection.vendor.lower(): + from opaque_keys.edx.locator import CourseLocator + from psycopg2.extensions import QuotedString, register_adapter + def adapt_course_locator(course_locator): + return QuotedString(course_locator._to_string()) # lint-amnesty, pylint: disable=protected-access + + # Register the adapter + register_adapter(CourseLocator, adapt_course_locator) diff --git a/openedx/core/djangoapps/content/course_overviews/migrations/0009_readd_facebook_url.py b/openedx/core/djangoapps/content/course_overviews/migrations/0009_readd_facebook_url.py index dc99d79731ef..37bd76548e9e 100644 --- a/openedx/core/djangoapps/content/course_overviews/migrations/0009_readd_facebook_url.py +++ b/openedx/core/djangoapps/content/course_overviews/migrations/0009_readd_facebook_url.py @@ -1,45 +1,42 @@ from django.db import migrations, models, connection def table_description(): - """Handle Mysql/Pg vs Sqlite""" - # django's mysql/pg introspection.get_table_description tries to select * - # from table and fails during initial migrations from scratch. - # sqlite does not have this failure, so we can use the API. - # For not-sqlite, query information-schema directly with code lifted - # from the internals of django.db.backends.mysql.introspection.py - + """Handle MySQL/Postgres vs SQLite compatibility for table introspection""" if connection.vendor == 'sqlite': fields = connection.introspection.get_table_description(connection.cursor(), 'course_overviews_courseoverview') return [f.name for f in fields] else: cursor = connection.cursor() - cursor.execute(""" - SELECT column_name - FROM information_schema.columns - WHERE table_name = 'course_overviews_courseoverview' AND table_schema = DATABASE()""") + if connection.vendor == 'mysql': + cursor.execute(""" + SELECT column_name + FROM information_schema.columns + WHERE table_name = 'course_overviews_courseoverview' AND table_schema = DATABASE() + """) + elif connection.vendor == 'postgresql': + cursor.execute(""" + SELECT column_name + FROM information_schema.columns + WHERE table_name = 'course_overviews_courseoverview' AND table_catalog = current_database() + """) rows = cursor.fetchall() return [r[0] for r in rows] - class Migration(migrations.Migration): dependencies = [ ('course_overviews', '0008_remove_courseoverview_facebook_url'), ] - # An original version of 0008 removed the facebook_url field We need to - # handle the case where our noop 0008 ran AND the case where the original - # 0008 ran. We do that by using the standard information_schema to find out - # what columns exist. _meta is unavailable as the column has already been - # removed from the model operations = [] fields = table_description() - # during a migration from scratch, fields will be empty, but we do not want to add - # an additional facebook_url + # Ensure 'facebook_url' is added if it does not exist in the table if fields and not any(f == 'facebook_url' for f in fields): - operations += migrations.AddField( - model_name='courseoverview', - name='facebook_url', - field=models.TextField(null=True), - ), + operations.append( + migrations.AddField( + model_name='courseoverview', + name='facebook_url', + field=models.TextField(null=True), + ) + ) diff --git a/openedx/core/djangoapps/content/learning_sequences/migrations/0001_initial.py b/openedx/core/djangoapps/content/learning_sequences/migrations/0001_initial.py index 22aad21ffb27..665a8082d979 100644 --- a/openedx/core/djangoapps/content/learning_sequences/migrations/0001_initial.py +++ b/openedx/core/djangoapps/content/learning_sequences/migrations/0001_initial.py @@ -3,122 +3,159 @@ # Manually modified to collate some fields as utf8_bin for case sensitive # matching. -from django.db import migrations, models -import django.db.models.deletion import django.utils.timezone import model_utils.fields import opaque_keys.edx.django.models +from django.db import connection +from django.db import migrations, models -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='CourseSection', - fields=[ - ('id', models.BigAutoField(primary_key=True, serialize=False)), - ('ordering', models.PositiveIntegerField()), - ('usage_key', opaque_keys.edx.django.models.UsageKeyField(max_length=255)), - ('title', models.CharField(max_length=1000)), - ('hide_from_toc', models.BooleanField(default=False)), - ('visible_to_staff_only', models.BooleanField(default=False)), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), - ], - ), - migrations.CreateModel( - name='CourseSectionSequence', - fields=[ - ('id', models.BigAutoField(primary_key=True, serialize=False)), - ('ordering', models.PositiveIntegerField()), - ('hide_from_toc', models.BooleanField(default=False)), - ('visible_to_staff_only', models.BooleanField(default=False)), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), - ], - ), - migrations.CreateModel( - name='LearningContext', - fields=[ - ('id', models.BigAutoField(primary_key=True, serialize=False)), - ('context_key', opaque_keys.edx.django.models.LearningContextKeyField(db_index=True, max_length=255, unique=True)), - ('title', models.CharField(max_length=255)), - ('published_at', models.DateTimeField()), - ('published_version', models.CharField(max_length=255)), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), - ], - ), - migrations.CreateModel( - name='LearningSequence', - fields=[ - ('id', models.BigAutoField(primary_key=True, serialize=False)), - ('learning_context', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sequences', to='learning_sequences.LearningContext')), - ('usage_key', opaque_keys.edx.django.models.UsageKeyField(max_length=255)), - ('title', models.CharField(max_length=1000)), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), - ], - ), - migrations.AddIndex( - model_name='learningcontext', - index=models.Index(fields=['-published_at'], name='learning_se_publish_62319b_idx'), - ), - migrations.AddField( - model_name='coursesectionsequence', - name='learning_context', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='section_sequences', to='learning_sequences.LearningContext'), - ), - migrations.AddField( - model_name='coursesectionsequence', - name='section', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='learning_sequences.CourseSection'), - ), - migrations.AddField( - model_name='coursesectionsequence', - name='sequence', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='learning_sequences.LearningSequence'), - ), - migrations.AddField( - model_name='coursesection', - name='learning_context', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sections', to='learning_sequences.LearningContext'), - ), - migrations.AlterUniqueTogether( - name='learningsequence', - unique_together={('learning_context', 'usage_key')}, - ), - migrations.AlterUniqueTogether( - name='coursesectionsequence', - unique_together={('learning_context', 'ordering')}, - ), - migrations.AlterUniqueTogether( - name='coursesection', - unique_together={('learning_context', 'usage_key')}, - ), - migrations.AlterIndexTogether( - name='coursesection', - index_together={('learning_context', 'ordering')}, - ), +def run_before_migrate(migrations, db_engine): + if 'postgresql' in db_engine: + # PostgreSQL: Use binary collation + return [ + migrations.RunSQL( + 'ALTER TABLE learning_sequences_learningcontext ALTER COLUMN context_key TYPE VARCHAR(255) COLLATE "C";', + reverse_sql=migrations.RunSQL.noop, ), + migrations.RunSQL( + 'ALTER TABLE learning_sequences_coursesection ALTER COLUMN usage_key TYPE VARCHAR(255) COLLATE "C";', + reverse_sql=migrations.RunSQL.noop, ), + migrations.RunSQL( + 'ALTER TABLE learning_sequences_learningsequence ALTER COLUMN usage_key TYPE VARCHAR(255) COLLATE "C";', + reverse_sql=migrations.RunSQL.noop, ), + ] - # Custom code: Convert columns to utf8_bin because we want to allow - # case-sensitive comparisons for things like UsageKeys, CourseKeys, and - # slugs. + return [ migrations.RunSQL( 'ALTER TABLE learning_sequences_learningcontext MODIFY context_key VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_bin;', - reverse_sql=migrations.RunSQL.noop, - ), + reverse_sql=migrations.RunSQL.noop, ), migrations.RunSQL( 'ALTER TABLE learning_sequences_coursesection MODIFY usage_key VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_bin;', - reverse_sql=migrations.RunSQL.noop, - ), + reverse_sql=migrations.RunSQL.noop, ), migrations.RunSQL( 'ALTER TABLE learning_sequences_learningsequence MODIFY usage_key VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_bin;', - reverse_sql=migrations.RunSQL.noop, - ), + reverse_sql=migrations.RunSQL.noop, ), ] + + +class Migration(migrations.Migration): + initial = True + db_engine = connection.settings_dict['ENGINE'] + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='CourseSection', + fields=[ + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('ordering', models.PositiveIntegerField()), + ('usage_key', opaque_keys.edx.django.models.UsageKeyField(max_length=255)), + ('title', models.CharField(max_length=1000)), + ('hide_from_toc', models.BooleanField(default=False)), + ('visible_to_staff_only', models.BooleanField(default=False)), + ('created', + model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, + verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, + editable=False, + verbose_name='modified')), + ], + ), + migrations.CreateModel( + name='CourseSectionSequence', + fields=[ + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('ordering', models.PositiveIntegerField()), + ('hide_from_toc', models.BooleanField(default=False)), + ('visible_to_staff_only', models.BooleanField(default=False)), + ('created', + model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, + verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, + editable=False, + verbose_name='modified')), + ], + ), + migrations.CreateModel( + name='LearningContext', + fields=[ + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('context_key', + opaque_keys.edx.django.models.LearningContextKeyField(db_index=True, max_length=255, + unique=True)), + ('title', models.CharField(max_length=255)), + ('published_at', models.DateTimeField()), + ('published_version', models.CharField(max_length=255)), + ('created', + model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, + verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, + editable=False, + verbose_name='modified')), + ], + ), + migrations.CreateModel( + name='LearningSequence', + fields=[ + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('learning_context', + models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sequences', + to='learning_sequences.LearningContext')), + ('usage_key', opaque_keys.edx.django.models.UsageKeyField(max_length=255)), + ('title', models.CharField(max_length=1000)), + ('created', + model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, + verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, + editable=False, + verbose_name='modified')), + ], + ), + migrations.AddIndex( + model_name='learningcontext', + index=models.Index(fields=['-published_at'], name='learning_se_publish_62319b_idx'), + ), + migrations.AddField( + model_name='coursesectionsequence', + name='learning_context', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, + related_name='section_sequences', + to='learning_sequences.LearningContext'), + ), + migrations.AddField( + model_name='coursesectionsequence', + name='section', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, + to='learning_sequences.CourseSection'), + ), + migrations.AddField( + model_name='coursesectionsequence', + name='sequence', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, + to='learning_sequences.LearningSequence'), + ), + migrations.AddField( + model_name='coursesection', + name='learning_context', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sections', + to='learning_sequences.LearningContext'), + ), + migrations.AlterUniqueTogether( + name='learningsequence', + unique_together={('learning_context', 'usage_key')}, + ), + migrations.AlterUniqueTogether( + name='coursesectionsequence', + unique_together={('learning_context', 'ordering')}, + ), + migrations.AlterUniqueTogether( + name='coursesection', + unique_together={('learning_context', 'usage_key')}, + ), + migrations.AlterIndexTogether( + name='coursesection', + index_together={('learning_context', 'ordering')}, + ), + ] + run_before_migrate(migrations, db_engine=db_engine) diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index f1a7db4c80ed..0858bff10d64 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -898,6 +898,8 @@ psutil==6.1.0 # via # -r requirements/edx/paver.txt # edx-django-utils +psycopg2-binary==2.9.10 + # via -r requirements/edx/kernel.in py2neo @ https://github.com/overhangio/py2neo/releases/download/2021.2.3/py2neo-2021.2.3.tar.gz # via # -c requirements/edx/../constraints.txt diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index eec25125f84b..3ede28e78e40 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -1516,6 +1516,10 @@ psutil==6.1.0 # edx-django-utils # pact-python # pytest-xdist +psycopg2-binary==2.9.10 + # via + # -r requirements/edx/doc.txt + # -r requirements/edx/testing.txt py==1.11.0 # via -r requirements/edx/testing.txt py2neo @ https://github.com/overhangio/py2neo/releases/download/2021.2.3/py2neo-2021.2.3.tar.gz diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index 62f435c49719..c9bbdb4c7fd0 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -1079,6 +1079,8 @@ psutil==6.1.0 # via # -r requirements/edx/base.txt # edx-django-utils +psycopg2-binary==2.9.10 + # via -r requirements/edx/base.txt py2neo @ https://github.com/overhangio/py2neo/releases/download/2021.2.3/py2neo-2021.2.3.tar.gz # via # -c requirements/edx/../constraints.txt diff --git a/requirements/edx/kernel.in b/requirements/edx/kernel.in index a5b510742ac7..dd3a99cbd160 100644 --- a/requirements/edx/kernel.in +++ b/requirements/edx/kernel.in @@ -160,3 +160,4 @@ webob web-fragments # Provides the ability to render fragments of web pages XBlock[django] # Courseware component architecture xss-utils # https://github.com/openedx/edx-platform/pull/20633 Fix XSS via Translations +psycopg2-binary diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 58f778af500e..27f82f44916f 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -1144,6 +1144,8 @@ psutil==6.1.0 # edx-django-utils # pact-python # pytest-xdist +psycopg2-binary==2.9.10 + # via -r requirements/edx/base.txt py==1.11.0 # via -r requirements/edx/testing.in py2neo @ https://github.com/overhangio/py2neo/releases/download/2021.2.3/py2neo-2021.2.3.tar.gz