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 ff13f48c01..1450f255ad 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 e99c4d46b4..425df1eb83 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 0000000000..7b9e44aab0 --- /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 cd6c4bafac..9dc8afe3df 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 227a419fd4..20b1fde4d4 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))