Fixed #106 -- added pay-what-you-want tickets

This commit is contained in:
Raphael Michel
2016-03-24 18:01:09 +01:00
parent abf5af4253
commit 112a309a0e
12 changed files with 249 additions and 50 deletions

View File

@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.2 on 2016-03-24 16:15
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0016_voucher_variation'),
]
operations = [
migrations.AlterModelOptions(
name='logentry',
options={'ordering': ('-datetime',)},
),
migrations.AddField(
model_name='item',
name='free_price',
field=models.BooleanField(default=False, help_text='If this option is active, your users can choose the price themselves. The price configured above is then interpreted as the minimum price a user has to enter. You could use this e.g. to collect additional donations for your event.', verbose_name='Free price'),
),
]

View File

@@ -3,7 +3,7 @@ from datetime import datetime
from decimal import Decimal
from django.db import models
from django.db.models import Q, Case, Count, Sum, When
from django.db.models import Q
from django.utils.functional import cached_property
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
@@ -132,6 +132,13 @@ class Item(LoggedModel):
verbose_name=_("Default price"),
max_digits=7, decimal_places=2, null=True
)
free_price = models.BooleanField(
default=False,
verbose_name=_("Free price input"),
help_text=_("If this option is active, your users can choose the price themselves. The price configured above "
"is then interpreted as the minimum price a user has to enter. You could use this e.g. to collect "
"additional donations for your event.")
)
tax_rate = models.DecimalField(
verbose_name=_("Taxes included in percent"),
max_digits=7, decimal_places=2,

View File

@@ -1,4 +1,5 @@
from datetime import datetime, timedelta
from decimal import Decimal
from django.conf import settings
from django.db.models import Q
@@ -51,7 +52,7 @@ def _re_add_expired_positions(items: List[CartPosition], event: Event, cart_id:
Q(cart_id=cart_id) & Q(event=event) & Q(expires__lte=now())
)
for cp in expired:
items.insert(0, (cp.item_id, cp.variation_id, 1, cp))
items.insert(0, (cp.item_id, cp.variation_id, 1, cp.price, cp))
positions.add(cp)
return positions
@@ -69,7 +70,7 @@ def _check_date(event: Event) -> None:
raise CartError(error_messages['ended'])
def _add_new_items(event: Event, items: List[Tuple[int, Optional[int], int]],
def _add_new_items(event: Event, items: List[Tuple[int, Optional[int], int, Optional[str]]],
cart_id: str, expiry: datetime) -> Optional[str]:
err = None
@@ -114,20 +115,24 @@ def _add_new_items(event: Event, items: List[Tuple[int, Optional[int], int]],
err = err or error_messages['in_part']
quota_ok = min(quota_ok, avail[1])
price = item.default_price if variation is None else (
variation.default_price if variation.default_price is not None else item.default_price)
if item.free_price and len(i) > 3 and i[3]:
custom_price = Decimal(i[3].replace(",", "."))
price = max(custom_price, price)
# Create a CartPosition for as much items as we can
for k in range(quota_ok):
if len(i) > 3 and i[2] == 1:
if len(i) > 4 and i[2] == 1:
# Recreating
cp = i[3]
cp = i[4]
cp.expires = expiry
cp.price = item.default_price if variation is None else (
variation.default_price if variation.default_price is not None else item.default_price)
cp.price = price
cp.save()
else:
CartPosition.objects.create(
event=event, item=item, variation=variation,
price=item.default_price if variation is None else (
variation.default_price if variation.default_price is not None else item.default_price),
price=price,
expires=expiry,
cart_id=cart_id
)
@@ -161,7 +166,7 @@ def _add_voucher(event: Event, voucher: str, expiry: datetime, cart_id: str):
raise CartError(error_messages['voucher_invalid'])
def _add_items_to_cart(event: Event, items: List[Tuple[int, Optional[int], int]], cart_id: str=None,
def _add_items_to_cart(event: Event, items: List[Tuple[int, Optional[int], int, Optional[str]]], cart_id: str=None,
voucher: str=None) -> None:
with event.lock():
_check_date(event)
@@ -186,12 +191,12 @@ def _add_items_to_cart(event: Event, items: List[Tuple[int, Optional[int], int]]
_add_voucher(event, voucher, expiry, cart_id)
def add_items_to_cart(event: int, items: List[Tuple[int, Optional[int], int]], cart_id: str=None,
def add_items_to_cart(event: int, items: List[Tuple[int, Optional[int], int, Optional[str]]], cart_id: str=None,
voucher: str=None) -> None:
"""
Adds a list of items to a user's cart.
:param event: The event ID in question
:param items: A list of tuple of the form (item id, variation id or None, number)
:param items: A list of tuple of the form (item id, variation id or None, number, custom_price)
:param session: Session ID of a guest
:param coupon: A coupon that should also be reeemed
:raises CartError: On any error that occured
@@ -203,19 +208,29 @@ def add_items_to_cart(event: int, items: List[Tuple[int, Optional[int], int]], c
raise CartError(error_messages['busy'])
def _remove_items_from_cart(event: int, items: List[Tuple[int, Optional[int], int]], cart_id: str) -> None:
def _remove_items_from_cart(event: int, items: List[Tuple[int, Optional[int], int, Optional[str]]],
cart_id: str) -> None:
with event.lock():
for item, variation, cnt in items:
for item, variation, cnt, price in items:
cw = Q(cart_id=cart_id) & Q(item_id=item) & Q(event=event)
if variation:
cw &= Q(variation_id=variation)
else:
cw &= Q(variation__isnull=True)
for cp in CartPosition.objects.filter(cw).order_by("-price")[:cnt]:
cp.delete()
# Prefer to delete positions that have the same price as the one the user clicked on, after thet
# prefer the most expensive ones.
if price:
correctprice = CartPosition.objects.filter(cw).filter(price=Decimal(price.replace(",", ".")))[:cnt]
for cp in correctprice:
cp.delete()
cnt -= len(correctprice)
if cnt > 0:
for cp in CartPosition.objects.filter(cw).order_by("-price")[:cnt]:
cp.delete()
def remove_items_from_cart(event: int, items: List[Tuple[int, Optional[int], int]], cart_id: str=None) -> None:
def remove_items_from_cart(event: int, items: List[Tuple[int, Optional[int], int, Optional[str]]],
cart_id: str=None) -> None:
"""
Removes a list of items from a user's cart.
:param event: The event ID in question
@@ -233,8 +248,8 @@ if settings.HAS_CELERY:
from pretix.celery import app
@app.task(bind=True, max_retries=5, default_retry_delay=1)
def add_items_to_cart_task(self, event: int, items: List[Tuple[int, Optional[int], int]], cart_id: str,
voucher: str=None):
def add_items_to_cart_task(self, event: int, items: List[Tuple[int, Optional[int], int, Optional[str]]],
cart_id: str, voucher: str=None):
event = Event.objects.get(id=event)
try:
try:
@@ -245,7 +260,8 @@ if settings.HAS_CELERY:
return e
@app.task(bind=True, max_retries=5, default_retry_delay=1)
def remove_items_from_cart_task(self, event: int, items: List[Tuple[int, Optional[int], int]], cart_id: str):
def remove_items_from_cart_task(self, event: int, items: List[Tuple[int, Optional[int], int]],
cart_id: str):
event = Event.objects.get(id=event)
try:
try:

View File

@@ -189,7 +189,7 @@ def _check_positions(event: Event, dt: datetime, positions: List[CartPosition]):
if cp.voucher.price is not None:
price = cp.voucher.price
if price != cp.price:
if price != cp.price and not (cp.item.free_price and cp.price > price):
positions[i] = cp
cp.price = price
cp.save()