Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
70bbd68
fix: migrations to make postgresql compatible.
Nov 4, 2024
c222853
fix: duplicate column issue
Nov 4, 2024
fe65206
fix: learning sequences migration for mysql
qasimgulzar Nov 4, 2024
2335e69
fix: mysql specific change
qasimgulzar Nov 4, 2024
4fd6ca0
fix: compile dependencies
Nov 5, 2024
1c4295c
fix: migration issue
Nov 5, 2024
60c4830
fix: broken test
Nov 5, 2024
0a9cd28
fix: using models.BigAutoField instead of custom field.
Nov 18, 2024
00b235a
fix: existing migrations.
Nov 18, 2024
2614432
fix: existing migrations.
Nov 18, 2024
8c21658
fix: mismatched migrations.
Nov 18, 2024
d096773
fix: quality issues
Nov 18, 2024
63875d9
fix: quality issues
Nov 22, 2024
78c2209
Merge branch 'master' into qasim/postgres
e0d Apr 3, 2025
fc80ce2
Merge branch 'master' into qasim/postgres
qasimgulzar Apr 8, 2025
c739e41
Merge remote-tracking branch 'openedx/master' into qasim/postgres
May 4, 2025
c0f0695
Merge branch 'master' into qasim/postgres
qasimgulzar Jul 1, 2025
03b4b00
Merge branch 'master' into qasim/postgres
qasimgulzar Aug 27, 2025
43938ac
Merge branch 'master' into qasim/postgres
qasimgulzar Sep 9, 2025
1f5c9d0
Merge branch 'master' into qasim/postgres
qasimgulzar Sep 11, 2025
499c852
fix: typo
qasimgulzar Sep 15, 2025
558f5ad
Merge branch 'master' into qasim/postgres
qasimgulzar Sep 15, 2025
22dc5f9
fix: accommodated PR feedback
qasimgulzar Sep 29, 2025
139b8a6
Merge branch 'master' into qasim/postgres
qasimgulzar Sep 29, 2025
cdd8403
fix: accommodated PR feedback
qasimgulzar Sep 29, 2025
42f9b3c
fix: putting back `UnsignedBigIntAutoField` to avoid type issue for e…
qasimgulzar Oct 1, 2025
5e73119
Merge branch 'master' into qasim/postgres
qasimgulzar Oct 1, 2025
023e5b0
Merge branch 'master' into qasim/postgres
qasimgulzar Oct 27, 2025
30811ef
fix: register adapters for locators
qasimgulzar Oct 31, 2025
6e11456
Merge branch 'master' into qasim/postgres
qasimgulzar Oct 31, 2025
c44cd5d
Merge branch 'master' into qasim/postgres
qasimgulzar Dec 2, 2025
d4049f3
Merge branch 'master' into qasim/postgres
qasimgulzar Dec 19, 2025
7df2ae2
Merge branch 'master' into qasim/postgres
qasimgulzar Dec 20, 2025
aaa8a71
fix: move models.BigAutoField to UnsignedBigIntAutoField
qasimgulzar Dec 28, 2025
c4f859c
Merge branch 'master' into qasim/postgres
qasimgulzar Dec 28, 2025
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
@@ -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),
Expand Down Expand Up @@ -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,
),
]
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
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'

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):

Expand Down
4 changes: 2 additions & 2 deletions lms/djangoapps/courseware/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def db_type(self, connection):
# 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':
elif "postgresql" in connection.settings_dict['ENGINE']:
# Pg's bigserial is implicitly unsigned (doesn't allow negative numbers) and
# goes 1-9.2x10^18
return "BIGSERIAL"
Expand All @@ -30,7 +30,7 @@ def rel_db_type(self, connection):
return "bigint UNSIGNED"
elif connection.settings_dict['ENGINE'] == 'django.db.backends.sqlite3':
return "integer"
elif connection.settings_dict['ENGINE'] == 'django.db.backends.postgresql_psycopg2':
elif "postgresql" in connection.settings_dict['ENGINE']:
return "BIGSERIAL"
else:
return None
Original file line number Diff line number Diff line change
Expand Up @@ -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)
]
21 changes: 16 additions & 5 deletions lms/djangoapps/courseware/migrations/0011_csm_id_bigint.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,38 @@
# 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
Expand All @@ -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'))

Expand Down
Original file line number Diff line number Diff line change
@@ -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'),
Expand All @@ -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'),
),
]
21 changes: 20 additions & 1 deletion openedx/core/djangoapps/common_initialization/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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():
Expand All @@ -26,3 +27,21 @@ 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, LibraryLocator, BlockUsageLocator
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)
register_adapter(LibraryLocator, adapt_course_locator)
register_adapter(BlockUsageLocator, adapt_course_locator)
Original file line number Diff line number Diff line change
@@ -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),
)
)
Loading
Loading