Merge pull request #370 from bornhack/ticket_type_create_single_ticket_option
Add "single_ticket_per_product" boolean to ticket types
This commit is contained in:
commit
7dc82ee4ee
36
src/camps/factories.py
Normal file
36
src/camps/factories.py
Normal 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")
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
||||||
|
|
|
@ -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
18
src/tickets/factories.py
Normal 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)
|
|
@ -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)'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue