feat: bump opaque-keys to get case-sensitivity support + default max_length (#38044)

refactor: remove some 'max_length=255' to be more DRY

feat: example of making an OpaqueKeyField case_sensitive (modulestore_migrator)

test: update test now that we're using case-insensitive collation on SQLite
This commit is contained in:
Braden MacDonald
2026-02-23 15:22:52 -08:00
committed by Braden MacDonald
parent ef783a1bca
commit 3e522d5272
19 changed files with 65 additions and 36 deletions

View File

@@ -255,7 +255,7 @@ class CourseEmail(Email):
class Meta:
app_label = "bulk_email"
course_id = CourseKeyField(max_length=255, db_index=True)
course_id = CourseKeyField(db_index=True)
# to_option is deprecated and unused, but dropping db columns is hard so it's still here for legacy reasons
to_option = models.CharField(max_length=64, choices=[("deprecated", "deprecated")])
targets = models.ManyToManyField(Target)
@@ -314,7 +314,7 @@ class Optout(models.Model):
# We need to first create the 'user' column with some sort of default in order to run the data migration,
# and given the unique index, 'null' is the best default value.
user = models.ForeignKey(User, db_index=True, null=True, on_delete=models.CASCADE)
course_id = CourseKeyField(max_length=255, db_index=True)
course_id = CourseKeyField(db_index=True)
class Meta:
app_label = "bulk_email"
@@ -430,7 +430,7 @@ class CourseAuthorization(models.Model):
app_label = "bulk_email"
# The course that these features are attached to.
course_id = CourseKeyField(max_length=255, db_index=True, unique=True)
course_id = CourseKeyField(db_index=True, unique=True)
# Whether or not to enable instructor email
email_enabled = models.BooleanField(default=False)
@@ -462,7 +462,7 @@ class DisabledCourse(models.Model):
class Meta:
app_label = "bulk_email"
course_id = CourseKeyField(max_length=255, db_index=True, unique=True)
course_id = CourseKeyField(db_index=True, unique=True)
@classmethod
def instructor_email_disabled_for_course(cls, course_id):

View File

@@ -26,7 +26,7 @@ class CustomCourseForEdX(models.Model):
.. no_pii:
"""
course_id = CourseKeyField(max_length=255, db_index=True)
course_id = CourseKeyField(db_index=True)
display_name = models.CharField(max_length=255)
coach = models.ForeignKey(User, db_index=True, on_delete=models.CASCADE)
# if not empty, this field contains a json serialized list of
@@ -112,7 +112,7 @@ class CcxFieldOverride(models.Model):
.. no_pii:
"""
ccx = models.ForeignKey(CustomCourseForEdX, db_index=True, on_delete=models.CASCADE)
location = UsageKeyField(max_length=255, db_index=True)
location = UsageKeyField(db_index=True)
field = models.CharField(max_length=255)
class Meta:

View File

@@ -60,7 +60,7 @@ class CertificateAllowlist(TimeStampedModel):
objects = NoneToEmptyManager()
user = models.ForeignKey(User, on_delete=models.CASCADE)
course_id = CourseKeyField(max_length=255, blank=True, default=None)
course_id = CourseKeyField(blank=True, default=None)
allowlist = models.BooleanField(default=0)
notes = models.TextField(default=None, null=True)
@@ -219,7 +219,7 @@ class GeneratedCertificate(models.Model):
]
user = models.ForeignKey(User, on_delete=models.CASCADE)
course_id = CourseKeyField(max_length=255, blank=True, default=None)
course_id = CourseKeyField(blank=True, default=None)
verify_uuid = models.CharField(max_length=32, blank=True, default='', db_index=True)
grade = models.CharField(max_length=5, blank=True, default='')
key = models.CharField(max_length=32, blank=True, default='')
@@ -554,7 +554,7 @@ class CertificateGenerationHistory(TimeStampedModel):
.. no_pii:
"""
course_id = CourseKeyField(max_length=255)
course_id = CourseKeyField()
generated_by = models.ForeignKey(User, on_delete=models.CASCADE)
instructor_task = models.ForeignKey(InstructorTask, on_delete=models.CASCADE)
is_regeneration = models.BooleanField(default=False)
@@ -714,7 +714,7 @@ class ExampleCertificateSet(TimeStampedModel):
.. no_pii:
"""
course_key = CourseKeyField(max_length=255, db_index=True)
course_key = CourseKeyField(db_index=True)
class Meta:
get_latest_by = 'created'
@@ -975,7 +975,7 @@ class CertificateGenerationCourseSetting(TimeStampedModel):
.. no_pii:
"""
course_key = CourseKeyField(max_length=255, db_index=True)
course_key = CourseKeyField(db_index=True)
self_generation_enabled = models.BooleanField(
default=False,

View File

@@ -446,8 +446,8 @@ class CourseListSearchViewTest(CourseApiTestViewMixin, ModuleStoreTestCase, Sear
self.setup_user(self.audit_user)
# These query counts were found empirically
query_counts = [52, 46, 46, 46, 46, 46, 46, 46, 46, 46, 16]
ordered_course_ids = sorted([str(cid) for cid in (course_ids + [c.id for c in self.courses])])
query_counts = [57, 46, 46, 46, 46, 46, 46, 46, 46, 43, 12]
ordered_course_ids = sorted([str(cid) for cid in (course_ids + [c.id for c in self.courses])], key=str.lower)
self.clear_caches()

View File

@@ -129,8 +129,8 @@ class GradedAssignment(models.Model):
.. no_pii:
"""
user = models.ForeignKey(User, db_index=True, on_delete=models.CASCADE)
course_key = CourseKeyField(max_length=255, db_index=True)
usage_key = UsageKeyField(max_length=255, db_index=True)
course_key = CourseKeyField(db_index=True)
usage_key = UsageKeyField(db_index=True)
outcome_service = models.ForeignKey(OutcomeService, on_delete=models.CASCADE)
lis_result_sourcedid = models.CharField(max_length=255, db_index=True)
version_number = models.IntegerField(default=0)

View File

@@ -128,7 +128,7 @@ class ProgramCourseEnrollment(TimeStampedModel):
blank=True,
on_delete=models.CASCADE
)
course_key = CourseKeyField(max_length=255)
course_key = CourseKeyField()
status = models.CharField(max_length=9, choices=STATUS_CHOICES)
historical_records = HistoricalRecords()

View File

@@ -24,7 +24,7 @@ class CourseResetCourseOptIn(TimeStampedModel):
.. no_pii:
"""
course_id = CourseKeyField(max_length=255, unique=True)
course_id = CourseKeyField(unique=True)
active = BooleanField()
def __str__(self):

View File

@@ -130,7 +130,7 @@ class CourseTeam(models.Model):
team_id = models.SlugField(max_length=255, unique=True)
discussion_topic_id = models.SlugField(max_length=255, unique=True)
name = models.CharField(max_length=255, db_index=True)
course_id = CourseKeyField(max_length=255, db_index=True)
course_id = CourseKeyField(db_index=True)
topic_id = models.CharField(default='', max_length=255, db_index=True, blank=True)
date_created = models.DateTimeField(auto_now_add=True)
description = models.CharField(max_length=300)

View File

@@ -1091,7 +1091,6 @@ class VerificationDeadline(TimeStampedModel):
app_label = "verify_student"
course_key = CourseKeyField(
max_length=255,
db_index=True,
unique=True,
help_text=gettext_lazy("The course for which this deadline applies"),