Add "single_ticket_per_product" boolean to ticket types so we can control ticket creation.

This commit is contained in:
Víðir Valberg Guðmundsson 2019-07-30 22:51:01 +02:00
parent 024f5dd093
commit 0373816355
8 changed files with 166 additions and 21 deletions

36
src/camps/factories.py Normal file
View file

@ -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")

View file

@ -30,6 +30,7 @@ class ProductFactory(DjangoModelFactory):
lower=timezone.now(), upper=timezone.now() + timezone.timedelta(31) lower=timezone.now(), upper=timezone.now() + timezone.timedelta(31)
) )
) )
ticket_type = factory.SubFactory("tickets.factories.TicketTypeFactory")
class OrderFactory(DjangoModelFactory): class OrderFactory(DjangoModelFactory):

View file

@ -208,6 +208,7 @@ class Order(CreatedUpdatedModel):
return str(reverse_lazy("shop:order_detail", kwargs={"pk": self.pk})) return str(reverse_lazy("shop:order_detail", kwargs={"pk": self.pk}))
def create_tickets(self, request=None): def create_tickets(self, request=None):
tickets = []
for order_product in self.orderproductrelation_set.all(): for order_product in self.orderproductrelation_set.all():
# if this is a Ticket product? # if this is a Ticket product?
if order_product.product.ticket_type: if order_product.product.ticket_type:
@ -216,32 +217,57 @@ class Order(CreatedUpdatedModel):
ticket_type=order_product.product.ticket_type, ticket_type=order_product.product.ticket_type,
) )
already_created_tickets = self.shoptickets.filter( if order_product.product.ticket_type.single_ticket_per_product:
**query_kwargs # This ticket type is one where we only create one ticket
).count() ticket, created = self.shoptickets.get_or_create(**query_kwargs)
tickets_to_create = max(
0, order_product.quantity - already_created_tickets
)
# create the number of tickets required if created:
if tickets_to_create > 0: msg = (
for _ in range( "Created ticket for product %s on order %s (quantity: %s)"
0, (order_product.quantity - already_created_tickets) % (
): order_product.product,
self.shoptickets.create(**query_kwargs) 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: if request:
messages.success(request, msg) messages.success(request, msg)
else: else:
print(msg) # 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 # create the number of tickets required
order_product.ticket_generated = True if tickets_to_create > 0:
order_product.save() 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): def mark_as_paid(self, request=None):
self.paid = True self.paid = True

View file

@ -4,6 +4,8 @@ from django.utils import timezone
from psycopg2.extras import DateTimeTZRange from psycopg2.extras import DateTimeTZRange
from shop.forms import OrderProductRelationForm from shop.forms import OrderProductRelationForm
from tickets.factories import TicketTypeFactory
from tickets.models import ShopTicket
from utils.factories import UserFactory from utils.factories import UserFactory
from .factories import ProductFactory, OrderProductRelationFactory, OrderFactory from .factories import ProductFactory, OrderProductRelationFactory, OrderFactory
@ -371,3 +373,27 @@ class TestOrderListView(TestCase):
path = reverse("shop:order_list") path = reverse("shop:order_list")
response = self.client.get(path) response = self.client.get(path)
self.assertEquals(response.status_code, 200) 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
)

View file

@ -1,5 +1,6 @@
from django.contrib import admin from django.contrib import admin
from shop.models import OrderProductRelation
from .models import TicketType, SponsorTicket, DiscountTicket, ShopTicket from .models import TicketType, SponsorTicket, DiscountTicket, ShopTicket
@ -51,12 +52,24 @@ class ShopTicketAdmin(BaseTicketAdmin):
"order", "order",
"product", "product",
"used", "used",
"product_quantity",
] ]
list_filter = ["ticket_type__camp", "used", "ticket_type", "order", "product"] list_filter = ["ticket_type__camp", "used", "ticket_type", "order", "product"]
search_fields = ["uuid", "order__id", "order__user__email", "name", "email"] 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): class ShopTicketInline(admin.TabularInline):
model = ShopTicket model = ShopTicket

18
src/tickets/factories.py Normal file
View file

@ -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)

View file

@ -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)'),
),
]

View file

@ -18,6 +18,13 @@ class TicketType(CampRelatedModel, UUIDModel):
name = models.TextField() name = models.TextField()
camp = models.ForeignKey("camps.Camp", on_delete=models.PROTECT) camp = models.ForeignKey("camps.Camp", on_delete=models.PROTECT)
includes_badge = models.BooleanField(default=False) 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): def __str__(self):
return "{} ({})".format(self.name, self.camp.title) return "{} ({})".format(self.name, self.camp.title)