From 14c3baa2aa91fc31271f008c99f864f8ace15d04 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Tue, 7 Jul 2026 17:44:19 +0200 Subject: [PATCH] Drop nullability on Order.organizer and OrderPosition.organizer (#4278) * Drop nullability on Order.organizer and OrderPosition.organizer * Rebase migration and add autoclean * Utilize new relationship for scopes * Declare reverse noop * Update src/pretix/base/migrations/0302_resolve_duplicate_codes_and_secrets.py Co-authored-by: Martin Gross * Update src/pretix/base/migrations/0302_resolve_duplicate_codes_and_secrets.py Co-authored-by: Martin Gross --------- Co-authored-by: Martin Gross --- ...302_resolve_duplicate_codes_and_secrets.py | 58 +++++++++++++++++++ ...organizer_alter_orderposition_organizer.py | 46 +++++++++++++++ src/pretix/base/models/orders.py | 8 +-- 3 files changed, 106 insertions(+), 6 deletions(-) create mode 100644 src/pretix/base/migrations/0302_resolve_duplicate_codes_and_secrets.py create mode 100644 src/pretix/base/migrations/0303_alter_order_organizer_alter_orderposition_organizer.py diff --git a/src/pretix/base/migrations/0302_resolve_duplicate_codes_and_secrets.py b/src/pretix/base/migrations/0302_resolve_duplicate_codes_and_secrets.py new file mode 100644 index 000000000..f8d50d473 --- /dev/null +++ b/src/pretix/base/migrations/0302_resolve_duplicate_codes_and_secrets.py @@ -0,0 +1,58 @@ +# Generated by Django 4.2.8 on 2024-07-01 09:27 +import logging + +from django.db import migrations +from django.db.models import Count + +logger = logging.getLogger(__name__) + + +def clean_duplicate_secrets(apps, schema_editor): + # This will autofix all possible duplicate Order.code and OrderPosition.secret values, + # unless Order.code is already too long to append something. This would need to be fixed by + # sysadmins manually. + OrderPosition = apps.get_model("pretixbase", "OrderPosition") + Order = apps.get_model("pretixbase", "Order") + + qs = OrderPosition.all.values("secret", "order__event__organizer_id").order_by().annotate(c=Count("*")).filter(c__gt=1) + for row in qs: + affected = OrderPosition.all.filter( + **{k: v for k, v in row.items() if k != "c"} + ).order_by("pk") + logger.error(f"Found {row['c']} tickets with with the same secret \"{row['secret']}\" in organizer {row['order__event__organizer_id']}, all except one will be changed") + for i, a in enumerate(affected): + if i > 0: + a.secret = a.secret + "__dupl__" + str(a.pk) + logger.info( + f"Ticket {a.pk} has new secret {a.secret}" + ) + a.save(update_fields=["organizer_id", "secret"]) + + qs = Order.objects.values("code", "event__organizer_id").order_by().annotate(c=Count("*")).filter(c__gt=1) + for row in qs: + affected = Order.objects.filter( + **{k: v for k, v in row.items() if k != "c"} + ).order_by("pk") + logger.error(f"Found {row['c']} orders with with the same code \"{row['code']}\" in organizer {row['event__organizer_id']}, all except one will be changed") + for i, a in enumerate(affected): + if i > 0: + if len(a.code) > 16 - len(str(a.pk)): + raise ValueError(f"Cannot auto-fix order with duplicate code {a.code}, order code is too long already") + a.code = a.code + str(a.pk).zfill(16 - len(a.code)) + logger.info( + f"Order {a.pk} has new code {a.code}" + ) + a.save(update_fields=["organizer_id", "code"]) + + +class Migration(migrations.Migration): + dependencies = [ + ( + "pretixbase", + "0301_reusablemedium_remove_orderposition", + ), + ] + + operations = [ + migrations.RunPython(clean_duplicate_secrets, migrations.RunPython.noop), + ] diff --git a/src/pretix/base/migrations/0303_alter_order_organizer_alter_orderposition_organizer.py b/src/pretix/base/migrations/0303_alter_order_organizer_alter_orderposition_organizer.py new file mode 100644 index 000000000..e101ce980 --- /dev/null +++ b/src/pretix/base/migrations/0303_alter_order_organizer_alter_orderposition_organizer.py @@ -0,0 +1,46 @@ +# Generated by Django 4.2.8 on 2024-07-01 09:27 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ( + "pretixbase", + "0302_resolve_duplicate_codes_and_secrets", + ), + ] + + operations = [ + migrations.RunSQL( + "UPDATE pretixbase_order " + "SET organizer_id = (SELECT e.organizer_id FROM pretixbase_event e WHERE e.id = pretixbase_order.event_id) " + "WHERE pretixbase_order.organizer_id IS NULL;", + migrations.RunSQL.noop, + ), + migrations.RunSQL( + "UPDATE pretixbase_orderposition " + "SET organizer_id = (SELECT e.organizer_id FROM pretixbase_order o LEFT JOIN pretixbase_event e ON e.id = o.event_id WHERE o.id = pretixbase_orderposition.order_id) " + "WHERE pretixbase_orderposition.organizer_id IS NULL;", + migrations.RunSQL.noop, + ), + migrations.AlterField( + model_name="order", + name="organizer", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="orders", + to="pretixbase.organizer", + ), + ), + migrations.AlterField( + model_name="orderposition", + name="organizer", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="order_positions", + to="pretixbase.organizer", + ), + ), + ] diff --git a/src/pretix/base/models/orders.py b/src/pretix/base/models/orders.py index a58b5014a..a0b1d63bf 100644 --- a/src/pretix/base/models/orders.py +++ b/src/pretix/base/models/orders.py @@ -224,8 +224,6 @@ class Order(LockModel, LoggedModel): "Organizer", related_name="orders", on_delete=models.CASCADE, - null=True, - blank=True, ) event = models.ForeignKey( Event, @@ -329,7 +327,7 @@ class Order(LockModel, LoggedModel): default="line", ) - objects = ScopedManager(OrderQuerySet.as_manager().__class__, organizer='event__organizer') + objects = ScopedManager(OrderQuerySet.as_manager().__class__, organizer='organizer') class Meta: verbose_name = _("Order") @@ -2541,8 +2539,6 @@ class OrderPosition(AbstractPosition): "Organizer", related_name="order_positions", on_delete=models.CASCADE, - null=True, - blank=True, ) order = models.ForeignKey( Order, @@ -2599,7 +2595,7 @@ class OrderPosition(AbstractPosition): blank=True, ) - all = ScopedManager(organizer='order__event__organizer') + all = ScopedManager(organizer='organizer') objects = ActivePositionManager() def __init__(self, *args, **kwargs):