From 0373816355b124cf1407472e8035134e3e3b4659 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=AD=C3=B0ir=20Valberg=20Gu=C3=B0mundsson?= Date: Tue, 30 Jul 2019 22:51:01 +0200 Subject: [PATCH] Add "single_ticket_per_product" boolean to ticket types so we can control ticket creation. --- src/camps/factories.py | 36 ++++++++++ src/shop/factories.py | 1 + src/shop/models.py | 68 +++++++++++++------ src/shop/tests.py | 26 +++++++ src/tickets/admin.py | 13 ++++ src/tickets/factories.py | 18 +++++ ...13_tickettype_single_ticket_per_product.py | 18 +++++ src/tickets/models.py | 7 ++ 8 files changed, 166 insertions(+), 21 deletions(-) create mode 100644 src/camps/factories.py create mode 100644 src/tickets/factories.py create mode 100644 src/tickets/migrations/0013_tickettype_single_ticket_per_product.py diff --git a/src/camps/factories.py b/src/camps/factories.py new file mode 100644 index 00000000..50139993 --- /dev/null +++ b/src/camps/factories.py @@ -0,0 +1,36 @@ +import factory +from django.utils import timezone + +from factory.django import DjangoModelFactory +from psycopg2._range import DateTimeTZRange + + +class CampFactory(DjangoModelFactory): + class Meta: + model = "camps.Camp" + + read_only = False + + title = factory.Faker("word") + tagline = factory.Faker("sentence") + slug = factory.Faker("slug") + shortslug = factory.Faker("slug") + + buildup = factory.LazyFunction( + lambda: DateTimeTZRange( + lower=timezone.now() - timezone.timedelta(3), upper=timezone.now() + ) + ) + + camp = factory.LazyFunction( + lambda: DateTimeTZRange(lower=timezone.now(), upper=timezone.now()) + ) + + teardown = factory.LazyFunction( + lambda: DateTimeTZRange( + lower=timezone.now() + timezone.timedelta(8), + upper=timezone.now() + timezone.timedelta(11), + ) + ) + + colour = factory.Faker("hex_color") diff --git a/src/shop/factories.py b/src/shop/factories.py index 6bc42936..7cbb5752 100644 --- a/src/shop/factories.py +++ b/src/shop/factories.py @@ -30,6 +30,7 @@ class ProductFactory(DjangoModelFactory): lower=timezone.now(), upper=timezone.now() + timezone.timedelta(31) ) ) + ticket_type = factory.SubFactory("tickets.factories.TicketTypeFactory") class OrderFactory(DjangoModelFactory): diff --git a/src/shop/models.py b/src/shop/models.py index ea7b3f26..929acd99 100644 --- a/src/shop/models.py +++ b/src/shop/models.py @@ -208,6 +208,7 @@ class Order(CreatedUpdatedModel): return str(reverse_lazy("shop:order_detail", kwargs={"pk": self.pk})) def create_tickets(self, request=None): + tickets = [] for order_product in self.orderproductrelation_set.all(): # if this is a Ticket product? if order_product.product.ticket_type: @@ -216,32 +217,57 @@ class Order(CreatedUpdatedModel): ticket_type=order_product.product.ticket_type, ) - already_created_tickets = self.shoptickets.filter( - **query_kwargs - ).count() - tickets_to_create = max( - 0, order_product.quantity - already_created_tickets - ) + if order_product.product.ticket_type.single_ticket_per_product: + # This ticket type is one where we only create one ticket + ticket, created = self.shoptickets.get_or_create(**query_kwargs) - # create the number of tickets required - if tickets_to_create > 0: - for _ in range( - 0, (order_product.quantity - already_created_tickets) - ): - self.shoptickets.create(**query_kwargs) + if created: + msg = ( + "Created ticket for product %s on order %s (quantity: %s)" + % ( + order_product.product, + order_product.order.pk, + order_product.quantity, + ) + ) + tickets.append(ticket) + else: + msg = "Ticket already created for product %s on order %s" % ( + order_product.product, + order_product.order.pk, + ) - msg = "Created %s tickets of type: %s" % ( - order_product.quantity, - order_product.product.ticket_type.name, - ) if request: messages.success(request, msg) - else: - print(msg) + else: + # We should create a number of tickets equal to OrderProductRelation quantity + already_created_tickets = self.shoptickets.filter( + **query_kwargs + ).count() + tickets_to_create = max( + 0, order_product.quantity - already_created_tickets + ) - # and mark the OPR as ticket_generated=True - order_product.ticket_generated = True - order_product.save() + # create the number of tickets required + if tickets_to_create > 0: + for _ in range( + 0, (order_product.quantity - already_created_tickets) + ): + ticket = self.shoptickets.create(**query_kwargs) + tickets.append(ticket) + + msg = "Created %s tickets of type: %s" % ( + order_product.quantity, + order_product.product.ticket_type.name, + ) + if request: + messages.success(request, msg) + + # and mark the OPR as ticket_generated=True + order_product.ticket_generated = True + order_product.save() + + return tickets def mark_as_paid(self, request=None): self.paid = True diff --git a/src/shop/tests.py b/src/shop/tests.py index 9efa0070..ba238451 100644 --- a/src/shop/tests.py +++ b/src/shop/tests.py @@ -4,6 +4,8 @@ from django.utils import timezone from psycopg2.extras import DateTimeTZRange from shop.forms import OrderProductRelationForm +from tickets.factories import TicketTypeFactory +from tickets.models import ShopTicket from utils.factories import UserFactory from .factories import ProductFactory, OrderProductRelationFactory, OrderFactory @@ -371,3 +373,27 @@ class TestOrderListView(TestCase): path = reverse("shop:order_list") response = self.client.get(path) self.assertEquals(response.status_code, 200) + + +class TestTicketCreation(TestCase): + def test_multiple_tickets_created(self): + user = UserFactory() + ticket_type = TicketTypeFactory(single_ticket_per_product=False) + product = ProductFactory(ticket_type=ticket_type) + order = OrderFactory(user=user) + OrderProductRelationFactory(order=order, product=product, quantity=5) + order.mark_as_paid() + self.assertEquals( + ShopTicket.objects.filter(product=product, order=order).count(), 5 + ) + + def test_single_ticket_created(self): + user = UserFactory() + ticket_type = TicketTypeFactory(single_ticket_per_product=True) + product = ProductFactory(ticket_type=ticket_type) + order = OrderFactory(user=user) + OrderProductRelationFactory(order=order, product=product, quantity=5) + order.mark_as_paid() + self.assertEquals( + ShopTicket.objects.filter(product=product, order=order).count(), 1 + ) diff --git a/src/tickets/admin.py b/src/tickets/admin.py index 478bfca6..e56fe93f 100644 --- a/src/tickets/admin.py +++ b/src/tickets/admin.py @@ -1,5 +1,6 @@ from django.contrib import admin +from shop.models import OrderProductRelation from .models import TicketType, SponsorTicket, DiscountTicket, ShopTicket @@ -51,12 +52,24 @@ class ShopTicketAdmin(BaseTicketAdmin): "order", "product", "used", + "product_quantity", ] list_filter = ["ticket_type__camp", "used", "ticket_type", "order", "product"] search_fields = ["uuid", "order__id", "order__user__email", "name", "email"] + def product_quantity(self, ticket): + orp = OrderProductRelation.objects.get( + product=ticket.product, order=ticket.order + ) + + return ( + str(orp.quantity) if ticket.ticket_type.single_ticket_per_product else "1" + ) + + product_quantity.short_description = "Quantity" + class ShopTicketInline(admin.TabularInline): model = ShopTicket diff --git a/src/tickets/factories.py b/src/tickets/factories.py new file mode 100644 index 00000000..4e106b9a --- /dev/null +++ b/src/tickets/factories.py @@ -0,0 +1,18 @@ +import factory + +from factory.django import DjangoModelFactory + + +class TicketTypeFactory(DjangoModelFactory): + class Meta: + model = "tickets.TicketType" + + name = factory.Faker("sentence") + camp = factory.SubFactory("camps.factories.CampFactory") + + +class ShopTicketFactory(DjangoModelFactory): + class Meta: + model = "tickets.ShopTicket" + + ticket_type = factory.SubFactory(TicketTypeFactory) diff --git a/src/tickets/migrations/0013_tickettype_single_ticket_per_product.py b/src/tickets/migrations/0013_tickettype_single_ticket_per_product.py new file mode 100644 index 00000000..baca801d --- /dev/null +++ b/src/tickets/migrations/0013_tickettype_single_ticket_per_product.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.3 on 2019-07-30 20:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tickets', '0012_auto_20190724_2037'), + ] + + operations = [ + migrations.AddField( + model_name='tickettype', + name='single_ticket_per_product', + field=models.BooleanField(default=False, help_text='Only create one ticket for a product/order pair no matter the quantity. Useful for products which are bought in larger quantity (ie. village chairs)'), + ), + ] diff --git a/src/tickets/models.py b/src/tickets/models.py index 36b4d969..ff3cff26 100644 --- a/src/tickets/models.py +++ b/src/tickets/models.py @@ -18,6 +18,13 @@ class TicketType(CampRelatedModel, UUIDModel): name = models.TextField() camp = models.ForeignKey("camps.Camp", on_delete=models.PROTECT) includes_badge = models.BooleanField(default=False) + single_ticket_per_product = models.BooleanField( + default=False, + help_text=( + "Only create one ticket for a product/order pair no matter the quantity. " + "Useful for products which are bought in larger quantity (ie. village chairs)" + ), + ) def __str__(self): return "{} ({})".format(self.name, self.camp.title)