diff --git a/wafer/locale/django.pot b/wafer/locale/django.pot index eb07516c..136a7923 100644 --- a/wafer/locale/django.pot +++ b/wafer/locale/django.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-05 20:23+0000\n" +"POT-Creation-Date: 2024-10-06 10:25+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -1237,6 +1237,18 @@ msgstr "" msgid "Sign up / Log In" msgstr "" +#: wafer/tickets/admin.py:10 +msgid "ticket claimed" +msgstr "" + +#: wafer/tickets/admin.py:15 +msgid "Ticket has been claimed" +msgstr "" + +#: wafer/tickets/admin.py:16 +msgid "Ticket is unclaimed" +msgstr "" + #: wafer/tickets/forms.py:18 msgid "Claim" msgstr "" diff --git a/wafer/tickets/admin.py b/wafer/tickets/admin.py index 4205224c..02cbf349 100644 --- a/wafer/tickets/admin.py +++ b/wafer/tickets/admin.py @@ -1,6 +1,56 @@ from django.contrib import admin +from django.db import models +from django.utils.translation import gettext_lazy as _ +from reversion.admin import VersionAdmin -from wafer.tickets.models import Ticket, TicketType +from wafer.tickets.models import Ticket, TicketType, TicketTypeTag -admin.site.register(Ticket) -admin.site.register(TicketType) + +class ClaimedFilter(admin.SimpleListFilter): + title = _('ticket claimed') + parameter_name = 'claimed' + + def lookups(self, request, model_admins): + return ( + ('yes', _('Ticket has been claimed')), + ('no', _('Ticket is unclaimed')) + ) + + def queryset(self, request, queryset): + if self.value() == 'yes': + return queryset.filter(user__isnull=False) + elif self.value() == 'no': + return queryset.filter(user__isnull=True) + return queryset + +class TicketTypeTagAdmin(VersionAdmin): + pass + + +# We don't use the versioned admin here, as these are usually created and +# updated by external triggers and we don't currently version that +class TicketTypeAdmin(admin.ModelAdmin): + list_display = ('name', 'get_tags', 'get_ticket_count') + list_filter = ('tags',) + + def get_queryset(self, request): + qs = super().get_queryset(request) + qs = qs.annotate( + ticket_count_annotation=models.Count('ticket', distinct=True), + ) + return qs + + def get_ticket_count(self, obj): + return obj.ticket_count_annotation + + get_ticket_count.short_description = 'total purchased' + get_ticket_count.admin_order_field = 'ticket_count_annotation' + + +class TicketAdmin(admin.ModelAdmin): + list_filter = (ClaimedFilter, 'type') + + +admin.site.register(Ticket, TicketAdmin) +admin.site.register(TicketType, TicketTypeAdmin) +admin.site.register(TicketTypeTag, TicketTypeTagAdmin) diff --git a/wafer/tickets/migrations/0004_tickettypetag_tickettype_tags.py b/wafer/tickets/migrations/0004_tickettypetag_tickettype_tags.py new file mode 100644 index 00000000..a78b574a --- /dev/null +++ b/wafer/tickets/migrations/0004_tickettypetag_tickettype_tags.py @@ -0,0 +1,32 @@ +# Generated by Django 5.0.9 on 2024-10-05 15:38 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("tickets", "0003_longer_email_field"), + ] + + operations = [ + migrations.CreateModel( + name="TicketTypeTag", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=75)), + ], + ), + migrations.AddField( + model_name="tickettype", + name="tags", + field=models.ManyToManyField(to="tickets.tickettypetag"), + ), + ] diff --git a/wafer/tickets/models.py b/wafer/tickets/models.py index 1686898c..1eef945e 100644 --- a/wafer/tickets/models.py +++ b/wafer/tickets/models.py @@ -2,15 +2,35 @@ from django.conf import settings +class TicketTypeTag(models.Model): + """Tags that can be added to a TicketType. + + For example 'online', 'sponsor'. + """ + + MAX_NAME_LENGTH = 75 + name = models.CharField(max_length=MAX_NAME_LENGTH) + + def __str__(self): + return self.name + + class TicketType(models.Model): MAX_NAME_LENGTH = 255 name = models.CharField(max_length=MAX_NAME_LENGTH) + tags = models.ManyToManyField(TicketTypeTag) + def __str__(self): return self.name + def get_tags(self): + return ', '.join([x.name for x in self.tags.all().order_by('name')]) + + get_tags.short_description = 'tags' + class Ticket(models.Model): barcode = models.IntegerField(primary_key=True) diff --git a/wafer/tickets/tests/test_admin_helpers.py b/wafer/tickets/tests/test_admin_helpers.py new file mode 100644 index 00000000..1a77b881 --- /dev/null +++ b/wafer/tickets/tests/test_admin_helpers.py @@ -0,0 +1,70 @@ +from django.contrib.admin.sites import AdminSite + +from django.test import TestCase, RequestFactory + +from wafer.tickets.models import TicketType, TicketTypeTag, Ticket +from wafer.tickets.admin import TicketTypeAdmin + +from wafer.tests.utils import create_user + +class TicketTypeAdminTests(TestCase): + + def setUp(self): + self.user_emails = ["user%d@example.com" % num for num in range(5)] + # create some tags + self.tag_z = TicketTypeTag.objects.create(name='tag z') + self.tag_a = TicketTypeTag.objects.create(name='tag a') + self.tag_d = TicketTypeTag.objects.create(name='tag d') + # Create some type + self.type_sponsor = TicketType.objects.create(name='Sponsor') + self.type_student = TicketType.objects.create(name='Student') + self.admin_model = TicketTypeAdmin(model=TicketType, admin_site=AdminSite()) + + self.admin_user = create_user('ticket_admin', superuser=True) + self.request_factory = RequestFactory() + + + def test_tags(self): + """Test that get_tags works as expected""" + # Add tags + self.type_sponsor.tags.add(self.tag_z) + self.type_sponsor.tags.add(self.tag_a) + + self.type_student.tags.add(self.tag_d) + self.type_student.tags.add(self.tag_z) + self.type_student.tags.add(self.tag_a) + + self.assertEqual(self.type_sponsor.get_tags(), 'tag a, tag z') + self.assertEqual(self.type_student.get_tags(), 'tag a, tag d, tag z') + + def test_ticket_counts(self): + """Test the get_count query on the admin model""" + + # Buy some tickets + Ticket.objects.create(email=self.user_emails[0], type=self.type_sponsor, + barcode='1234') + Ticket.objects.create(email=self.user_emails[1], type=self.type_sponsor, + barcode='2345') + Ticket.objects.create(email=self.user_emails[2], type=self.type_sponsor, + barcode='3456') + + request = self.request_factory.get("/") + request.user = self.admin_user + qs = self.admin_model.get_queryset(request) + self.assertEqual(self.admin_model.get_ticket_count(qs[0]), 3) + self.assertEqual(self.admin_model.get_ticket_count(qs[1]), 0) + + Ticket.objects.create(email=self.user_emails[3], type=self.type_student, + barcode='4321') + ticket5 = Ticket.objects.create(email=self.user_emails[4], type=self.type_student, + barcode='1111') + + qs = self.admin_model.get_queryset(request) + self.assertEqual(self.admin_model.get_ticket_count(qs[0]), 3) + self.assertEqual(self.admin_model.get_ticket_count(qs[1]), 2) + + ticket5.delete() + + qs = self.admin_model.get_queryset(request) + self.assertEqual(self.admin_model.get_ticket_count(qs[0]), 3) + self.assertEqual(self.admin_model.get_ticket_count(qs[1]), 1)