Normalize supervisor emails and add unique constraint for case-insensitive email uniqueness
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
from django.db import migrations
|
||||
# no additional imports required
|
||||
|
||||
|
||||
def normalize_and_dedupe(apps, schema_editor):
|
||||
Supervisor = apps.get_model('generic', 'Supervisor')
|
||||
UserProfile = apps.get_model('generic', 'UserProfile')
|
||||
CidUser = apps.get_model('generic', 'CidUser')
|
||||
# Build groups by normalized email without saving changes yet to avoid unique constraint errors
|
||||
supervisors = list(Supervisor.objects.all())
|
||||
groups = {}
|
||||
|
||||
for s in supervisors:
|
||||
norm_email = (s.email or "").strip().lower()
|
||||
norm_name = (s.name or "").strip()
|
||||
groups.setdefault(norm_email, []).append((s, norm_name))
|
||||
|
||||
# For each group, pick a keeper (smallest PK), reassign FKs from duplicates to keeper, delete duplicates,
|
||||
# then update the keeper's normalized email/name.
|
||||
for norm_email, entries in groups.items():
|
||||
# pick keeper by lowest id
|
||||
entries_sorted = sorted(entries, key=lambda e: e[0].pk)
|
||||
keeper_obj, keeper_name = entries_sorted[0]
|
||||
|
||||
# reassign duplicates
|
||||
for dup_obj, dup_name in entries_sorted[1:]:
|
||||
UserProfile.objects.filter(supervisor_id=dup_obj.pk).update(supervisor_id=keeper_obj.pk)
|
||||
CidUser.objects.filter(supervisor_id=dup_obj.pk).update(supervisor_id=keeper_obj.pk)
|
||||
dup_obj.delete()
|
||||
|
||||
# update keeper's email and name to normalized values if needed
|
||||
changed = False
|
||||
if norm_email and keeper_obj.email != norm_email:
|
||||
keeper_obj.email = norm_email
|
||||
changed = True
|
||||
if keeper_name and keeper_obj.name != keeper_name:
|
||||
keeper_obj.name = keeper_name
|
||||
changed = True
|
||||
if changed:
|
||||
keeper_obj.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('generic', '0027_alter_questionreview_status'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(normalize_and_dedupe, reverse_code=migrations.RunPython.noop),
|
||||
]
|
||||
@@ -0,0 +1,17 @@
|
||||
from django.db import migrations
|
||||
from django.db.models import UniqueConstraint
|
||||
from django.db.models.functions import Lower
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('generic', '0028_normalize_supervisor_emails'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddConstraint(
|
||||
model_name='supervisor',
|
||||
constraint=UniqueConstraint(Lower('email'), name='unique_supervisor_email_ci'),
|
||||
),
|
||||
]
|
||||
+12
-2
@@ -5,6 +5,8 @@ import os
|
||||
from typing import Optional, Self, Tuple
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.db import models
|
||||
from django.db.models import UniqueConstraint
|
||||
from django.db.models.functions import Lower
|
||||
from django.forms import ValidationError
|
||||
from django.http import Http404, HttpRequest
|
||||
from django.shortcuts import render
|
||||
@@ -1859,8 +1861,16 @@ class Supervisor(models.Model):
|
||||
return reverse("generic:supervisor_detail", kwargs={"pk": self.pk})
|
||||
|
||||
def clean(self):
|
||||
self.email = self.email.strip()
|
||||
self.name = self.name.strip()
|
||||
if self.email:
|
||||
# normalize email: strip and lowercase for consistency
|
||||
self.email = self.email.strip().lower()
|
||||
if self.name:
|
||||
self.name = self.name.strip()
|
||||
|
||||
class Meta:
|
||||
constraints = [
|
||||
UniqueConstraint(Lower('email'), name='unique_supervisor_email_ci')
|
||||
]
|
||||
|
||||
|
||||
@receiver(post_save, sender=User)
|
||||
|
||||
Reference in New Issue
Block a user