From 650b4b461f760df9bb116ec0a9bd0efb7edac4e0 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Mon, 18 Aug 2025 10:56:53 +0200 Subject: [PATCH] Voucher: Add creation date (Z#23202621) (#5359) * Voucher: Add creation date (Z#23202621) * Migration fix * Update doc/api/resources/vouchers.rst Co-authored-by: luelista * Update src/pretix/base/migrations/0285_voucher_created.py Co-authored-by: luelista --------- Co-authored-by: luelista --- doc/api/resources/vouchers.rst | 6 +++ src/pretix/api/serializers/voucher.py | 2 +- .../base/migrations/0285_voucher_created.py | 46 +++++++++++++++++++ src/pretix/base/models/vouchers.py | 3 ++ src/tests/api/test_vouchers.py | 2 + 5 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 src/pretix/base/migrations/0285_voucher_created.py diff --git a/doc/api/resources/vouchers.rst b/doc/api/resources/vouchers.rst index ff13f48c0..1450f255a 100644 --- a/doc/api/resources/vouchers.rst +++ b/doc/api/resources/vouchers.rst @@ -14,6 +14,7 @@ The voucher resource contains the following public fields: Field Type Description ===================================== ========================== ======================================================= id integer Internal ID of the voucher +created datetime The creation date of the voucher. For vouchers created before pretix 2025.7.0, this is guessed retroactively and might not be accurate. code string The voucher code that is required to redeem the voucher max_usages integer The maximum number of times this voucher can be redeemed (default: 1). @@ -84,6 +85,7 @@ Endpoints "results": [ { "id": 1, + "created": "2020-09-18T14:17:40.971519Z", "code": "43K6LKM37FBVR2YG", "max_usages": 1, "redeemed": 0, @@ -156,6 +158,7 @@ Endpoints { "id": 1, + "created": "2020-09-18T14:17:40.971519Z", "code": "43K6LKM37FBVR2YG", "max_usages": 1, "redeemed": 0, @@ -228,6 +231,7 @@ Endpoints { "id": 1, + "created": "2020-09-18T14:17:40.971519Z", "code": "43K6LKM37FBVR2YG", "max_usages": 1, "redeemed": 0, @@ -321,6 +325,7 @@ Endpoints [ { "id": 1, + "created": "2020-09-18T14:17:40.971519Z", "code": "43K6LKM37FBVR2YG", … }, … @@ -367,6 +372,7 @@ Endpoints { "id": 1, + "created": "2020-09-18T14:17:40.971519Z", "code": "43K6LKM37FBVR2YG", "max_usages": 1, "redeemed": 0, diff --git a/src/pretix/api/serializers/voucher.py b/src/pretix/api/serializers/voucher.py index e99c4d46b..425df1eb8 100644 --- a/src/pretix/api/serializers/voucher.py +++ b/src/pretix/api/serializers/voucher.py @@ -70,7 +70,7 @@ class VoucherSerializer(I18nAwareModelSerializer): class Meta: model = Voucher - fields = ('id', 'code', 'max_usages', 'redeemed', 'min_usages', 'valid_until', 'block_quota', + fields = ('id', 'created', 'code', 'max_usages', 'redeemed', 'min_usages', 'valid_until', 'block_quota', 'allow_ignore_quota', 'price_mode', 'value', 'item', 'variation', 'quota', 'tag', 'comment', 'subevent', 'show_hidden_items', 'seat', 'all_addons_included', 'all_bundles_included', 'budget', 'budget_used') diff --git a/src/pretix/base/migrations/0285_voucher_created.py b/src/pretix/base/migrations/0285_voucher_created.py new file mode 100644 index 000000000..7b9e44aab --- /dev/null +++ b/src/pretix/base/migrations/0285_voucher_created.py @@ -0,0 +1,46 @@ +# Generated by Django 4.2.16 on 2025-08-08 09:13 + +from django.db import migrations, models +from django.db.models import Min +from django.utils.timezone import now + + +def backfill_voucher_created(apps, schema_editor): + Voucher = apps.get_model("pretixbase", "Voucher") + LogEntry = apps.get_model("pretixbase", "LogEntry") + ContentType = apps.get_model("contenttypes", "ContentType") + ct = None + + for v in Voucher.objects.filter(created__isnull=True).iterator(): + if not ct: + # "Lazy-loading" to prevent this to be executed on new DBs where the content type does not yet + # exist -- but also no vouchers do + ct = ContentType.objects.get(app_label='pretixbase', model='voucher') + v.created = LogEntry.objects.filter( + content_type=ct, + object_id=v.pk, + ).aggregate(m=Min("datetime"))["m"] or now() + v.save(update_fields=["created"]) + + +class Migration(migrations.Migration): + dependencies = [ + ("pretixbase", "0284_ordersyncresult_ordersyncqueue"), + ] + + operations = [ + migrations.AddField( + model_name="voucher", + name="created", + field=models.DateTimeField(auto_now_add=True, null=True), + ), + migrations.RunPython( + backfill_voucher_created, + migrations.RunPython.noop, + ), + migrations.AlterField( + model_name="voucher", + name="created", + field=models.DateTimeField(auto_now_add=True), + ), + ] diff --git a/src/pretix/base/models/vouchers.py b/src/pretix/base/models/vouchers.py index cd6c4bafa..9dc8afe3d 100644 --- a/src/pretix/base/models/vouchers.py +++ b/src/pretix/base/models/vouchers.py @@ -174,6 +174,9 @@ class Voucher(LoggedModel): ('percent', _('Reduce product price by (%)')), ) + created = models.DateTimeField( + auto_now_add=True, + ) event = models.ForeignKey( Event, on_delete=models.CASCADE, diff --git a/src/tests/api/test_vouchers.py b/src/tests/api/test_vouchers.py index 227a419fd..20b1fde4d 100644 --- a/src/tests/api/test_vouchers.py +++ b/src/tests/api/test_vouchers.py @@ -91,6 +91,7 @@ def test_voucher_list(token_client, organizer, event, voucher, item, quota, sube res = dict(TEST_VOUCHER_RES) res['item'] = item.pk res['id'] = voucher.pk + res['created'] = voucher.created.isoformat().replace('+00:00', 'Z') res['code'] = voucher.code q2 = copy.copy(quota) q2.pk = None @@ -264,6 +265,7 @@ def test_voucher_detail(token_client, organizer, event, voucher, item): res['item'] = item.pk res['id'] = voucher.pk res['code'] = voucher.code + res['created'] = voucher.created.isoformat().replace('+00:00', 'Z') resp = token_client.get('/api/v1/organizers/{}/events/{}/vouchers/{}/'.format(organizer.slug, event.slug, voucher.pk))