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
@@ -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'),
),
]
+8 -1
View File
@@ -3,7 +3,7 @@ from datetime import datetime
from decimal import Decimal from decimal import Decimal
from django.db import models 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.functional import cached_property
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@@ -132,6 +132,13 @@ class Item(LoggedModel):
verbose_name=_("Default price"), verbose_name=_("Default price"),
max_digits=7, decimal_places=2, null=True 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( tax_rate = models.DecimalField(
verbose_name=_("Taxes included in percent"), verbose_name=_("Taxes included in percent"),
max_digits=7, decimal_places=2, max_digits=7, decimal_places=2,
+35 -19
View File
@@ -1,4 +1,5 @@
from datetime import datetime, timedelta from datetime import datetime, timedelta
from decimal import Decimal
from django.conf import settings from django.conf import settings
from django.db.models import Q 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()) Q(cart_id=cart_id) & Q(event=event) & Q(expires__lte=now())
) )
for cp in expired: 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) positions.add(cp)
return positions return positions
@@ -69,7 +70,7 @@ def _check_date(event: Event) -> None:
raise CartError(error_messages['ended']) 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]: cart_id: str, expiry: datetime) -> Optional[str]:
err = None 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'] err = err or error_messages['in_part']
quota_ok = min(quota_ok, avail[1]) 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 # Create a CartPosition for as much items as we can
for k in range(quota_ok): for k in range(quota_ok):
if len(i) > 3 and i[2] == 1: if len(i) > 4 and i[2] == 1:
# Recreating # Recreating
cp = i[3] cp = i[4]
cp.expires = expiry cp.expires = expiry
cp.price = item.default_price if variation is None else ( cp.price = price
variation.default_price if variation.default_price is not None else item.default_price)
cp.save() cp.save()
else: else:
CartPosition.objects.create( CartPosition.objects.create(
event=event, item=item, variation=variation, event=event, item=item, variation=variation,
price=item.default_price if variation is None else ( price=price,
variation.default_price if variation.default_price is not None else item.default_price),
expires=expiry, expires=expiry,
cart_id=cart_id 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']) 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: voucher: str=None) -> None:
with event.lock(): with event.lock():
_check_date(event) _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) _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: voucher: str=None) -> None:
""" """
Adds a list of items to a user's cart. Adds a list of items to a user's cart.
:param event: The event ID in question :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 session: Session ID of a guest
:param coupon: A coupon that should also be reeemed :param coupon: A coupon that should also be reeemed
:raises CartError: On any error that occured :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']) 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(): 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) cw = Q(cart_id=cart_id) & Q(item_id=item) & Q(event=event)
if variation: if variation:
cw &= Q(variation_id=variation) cw &= Q(variation_id=variation)
else: else:
cw &= Q(variation__isnull=True) cw &= Q(variation__isnull=True)
for cp in CartPosition.objects.filter(cw).order_by("-price")[:cnt]: # Prefer to delete positions that have the same price as the one the user clicked on, after thet
cp.delete() # 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. Removes a list of items from a user's cart.
:param event: The event ID in question :param event: The event ID in question
@@ -233,8 +248,8 @@ if settings.HAS_CELERY:
from pretix.celery import app from pretix.celery import app
@app.task(bind=True, max_retries=5, default_retry_delay=1) @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, def add_items_to_cart_task(self, event: int, items: List[Tuple[int, Optional[int], int, Optional[str]]],
voucher: str=None): cart_id: str, voucher: str=None):
event = Event.objects.get(id=event) event = Event.objects.get(id=event)
try: try:
try: try:
@@ -245,7 +260,8 @@ if settings.HAS_CELERY:
return e return e
@app.task(bind=True, max_retries=5, default_retry_delay=1) @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) event = Event.objects.get(id=event)
try: try:
try: try:
+1 -1
View File
@@ -189,7 +189,7 @@ def _check_positions(event: Event, dt: datetime, positions: List[CartPosition]):
if cp.voucher.price is not None: if cp.voucher.price is not None:
price = cp.voucher.price 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 positions[i] = cp
cp.price = price cp.price = price
cp.save() cp.save()
+1
View File
@@ -95,6 +95,7 @@ class ItemFormGeneral(I18nModelForm):
'description', 'description',
'picture', 'picture',
'default_price', 'default_price',
'free_price',
'tax_rate', 'tax_rate',
'available_from', 'available_from',
'available_until', 'available_until',
@@ -2,34 +2,35 @@
{% load i18n %} {% load i18n %}
{% load bootstrap3 %} {% load bootstrap3 %}
{% block inside %} {% block inside %}
<form action="" method="post" class="form-horizontal" enctype="multipart/form-data"> <form action="" method="post" class="form-horizontal" enctype="multipart/form-data">
{% csrf_token %} {% csrf_token %}
<fieldset> <fieldset>
<legend>{% trans "General information" %}</legend> <legend>{% trans "General information" %}</legend>
{% bootstrap_field form.name layout="horizontal" %} {% bootstrap_field form.name layout="horizontal" %}
{% bootstrap_field form.active layout="horizontal" %} {% bootstrap_field form.active layout="horizontal" %}
{% if form.has_variations %} {% if form.has_variations %}
{% bootstrap_field form.has_variations layout="horizontal" %} {% bootstrap_field form.has_variations layout="horizontal" %}
{% endif %} {% endif %}
{% bootstrap_field form.category layout="horizontal" %} {% bootstrap_field form.category layout="horizontal" %}
{% bootstrap_field form.admission layout="horizontal" %} {% bootstrap_field form.admission layout="horizontal" %}
{% bootstrap_field form.description layout="horizontal" %} {% bootstrap_field form.description layout="horizontal" %}
{% bootstrap_field form.picture layout="horizontal" %} {% bootstrap_field form.picture layout="horizontal" %}
</fieldset> </fieldset>
<fieldset> <fieldset>
<legend>{% trans "Price settings" %}</legend> <legend>{% trans "Price settings" %}</legend>
{% bootstrap_field form.default_price layout="horizontal" %} {% bootstrap_field form.default_price layout="horizontal" %}
{% bootstrap_field form.tax_rate layout="horizontal" %} {% bootstrap_field form.tax_rate layout="horizontal" %}
</fieldset> {% bootstrap_field form.free_price layout="horizontal" %}
<fieldset> </fieldset>
<legend>{% trans "Availability" %}</legend> <fieldset>
{% bootstrap_field form.available_from layout="horizontal" %} <legend>{% trans "Availability" %}</legend>
{% bootstrap_field form.available_until layout="horizontal" %} {% bootstrap_field form.available_from layout="horizontal" %}
</fieldset> {% bootstrap_field form.available_until layout="horizontal" %}
<div class="form-group submit-group"> </fieldset>
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save"> <button type="submit" class="btn btn-primary btn-save">
{% trans "Save" %} {% trans "Save" %}
</button> </button>
</div> </div>
</form> </form>
{% endblock %} {% endblock %}
@@ -28,12 +28,17 @@
<form action="{% eventurl event "presale:event.cart.remove" %}" <form action="{% eventurl event "presale:event.cart.remove" %}"
method="post" data-asynctask> method="post" data-asynctask>
{% csrf_token %} {% csrf_token %}
{% if line.variation %} {% if line.variation %}
<input type="hidden" name="variation_{{ line.item.id }}_{{ line.variation.id }}" <input type="hidden" name="variation_{{ line.item.id }}_{{ line.variation.id }}"
value="1" /> value="1" />
<input type="hidden" name="price_{{ line.item.id }}_{{ line.variation.id }}"
value="{{ line.price }}" />
{% else %} {% else %}
<input type="hidden" name="item_{{ line.item.id }}" <input type="hidden" name="item_{{ line.item.id }}"
value="1" /> value="1" />
<input type="hidden" name="price_{{ line.item.id }}"
value="{{ line.price }}" />
{% endif %} {% endif %}
<button class="btn btn-mini btn-link"><i class="fa fa-minus"></i></button> <button class="btn btn-mini btn-link"><i class="fa fa-minus"></i></button>
</form> </form>
@@ -46,9 +51,13 @@
{% if line.variation %} {% if line.variation %}
<input type="hidden" name="variation_{{ line.item.id }}_{{ line.variation.id }}" <input type="hidden" name="variation_{{ line.item.id }}_{{ line.variation.id }}"
value="1" /> value="1" />
<input type="hidden" name="price_{{ line.item.id }}_{{ line.variation.id }}"
value="{{ line.price }}" />
{% else %} {% else %}
<input type="hidden" name="item_{{ line.item.id }}" <input type="hidden" name="item_{{ line.item.id }}"
value="1" /> value="1" />
<input type="hidden" name="price_{{ line.item.id }}"
value="{{ line.price }}" />
{% endif %} {% endif %}
<button class="btn btn-mini btn-link"><i class="fa fa-plus"></i></button> <button class="btn btn-mini btn-link"><i class="fa fa-plus"></i></button>
</form> </form>
@@ -76,7 +76,7 @@
{% if item.description %}<p>{{ item.description }}</p>{% endif %} {% if item.description %}<p>{{ item.description }}</p>{% endif %}
</div> </div>
<div class="col-md-2 col-xs-6 price"> <div class="col-md-2 col-xs-6 price">
{% if item.min_price != item.max_price %} {% if item.min_price != item.max_price or item.free_price %}
{% blocktrans trimmed with minprice=item.min_price|floatformat:2 currency=event.currency %} {% blocktrans trimmed with minprice=item.min_price|floatformat:2 currency=event.currency %}
from {{ currency }} {{ minprice }} from {{ currency }} {{ minprice }}
{% endblocktrans %} {% endblocktrans %}
@@ -101,7 +101,18 @@
{% endif %} {% endif %}
</div> </div>
<div class="col-md-2 col-xs-6 price"> <div class="col-md-2 col-xs-6 price">
{{ event.currency }} {{ var.price|floatformat:2 }} {% if item.free_price %}
<div class="input-group">
<span class="input-group-addon">{{ event.currency }}</span>
<input type="number" class="form-control input-item-price"
placeholder="0"
min="{{ var.price|stringformat:"0.2f" }}"
name="price_{{ item.id }}_{{ var.id }}"
step="0.01" value="{{ var.price|stringformat:"0.2f" }}">
</div>
{% else %}
{{ event.currency }} {{ var.price|floatformat:2 }}
{% endif %}
{% if item.tax_rate %} {% if item.tax_rate %}
<br/> <br/>
<small>{% blocktrans trimmed with rate=item.tax_rate %} <small>{% blocktrans trimmed with rate=item.tax_rate %}
@@ -135,13 +146,24 @@
</a> </a>
{% endif %} {% endif %}
<strong>{{ item.name }}</strong> <strong>{{ item.name }}</strong>
{% if item.description %}<p class="description">{{ item.description }}</p>{% endif %} {% if item.description %}
<p class="description">{{ item.description }}</p>{% endif %}
{% if event.settings.show_quota_left %} {% if event.settings.show_quota_left %}
{% include "pretixpresale/event/fragment_quota_left.html" with avail=item.cached_availability %} {% include "pretixpresale/event/fragment_quota_left.html" with avail=item.cached_availability %}
{% endif %} {% endif %}
</div> </div>
<div class="col-md-2 col-xs-6 price"> <div class="col-md-2 col-xs-6 price">
{{ event.currency }} {{ item.price|floatformat:2 }} {% if item.free_price %}
<div class="input-group">
<span class="input-group-addon">{{ event.currency }}</span>
<input type="number" class="form-control input-item-price" placeholder="0"
min="{{ item.price|stringformat:"0.2f" }}"
name="price_{{ item.id }}"
step="0.01" value="{{ item.price|stringformat:"0.2f" }}">
</div>
{% else %}
{{ event.currency }} {{ item.price|floatformat:2 }}
{% endif %}
{% if item.tax_rate %} {% if item.tax_rate %}
<br/> <br/>
<small>{% blocktrans trimmed with rate=item.tax_rate %} <small>{% blocktrans trimmed with rate=item.tax_rate %}
+5 -3
View File
@@ -33,17 +33,19 @@ class CartActionMixin:
""" """
items = [] items = []
for key, value in self.request.POST.items(): for key, value in self.request.POST.items():
if value.strip() == '': if value.strip() == '' or '_' not in key:
continue continue
price = self.request.POST.get('price_' + key.split("_", 1)[1], "")
if key.startswith('item_'): if key.startswith('item_'):
try: try:
items.append((int(key.split("_")[1]), None, int(value))) items.append((int(key.split("_")[1]), None, int(value), price))
except ValueError: except ValueError:
messages.error(self.request, _('Please enter numbers only.')) messages.error(self.request, _('Please enter numbers only.'))
return [] return []
elif key.startswith('variation_'): elif key.startswith('variation_'):
try: try:
items.append((int(key.split("_")[1]), int(key.split("_")[2]), int(value))) items.append((int(key.split("_")[1]), int(key.split("_")[2]), int(value), price))
except ValueError: except ValueError:
messages.error(self.request, _('Please enter numbers only.')) messages.error(self.request, _('Please enter numbers only.'))
return [] return []
+3
View File
@@ -10,6 +10,9 @@
.input-item-count { .input-item-count {
text-align: center; text-align: center;
} }
.input-item-price {
text-align: right;
}
.availability-box { .availability-box {
text-align: center; text-align: center;
+81
View File
@@ -57,6 +57,66 @@ class CartTest(CartTestMixin, TestCase):
self.assertIsNone(objs[0].variation) self.assertIsNone(objs[0].variation)
self.assertEqual(objs[0].price, 23) self.assertEqual(objs[0].price, 23)
def test_free_price(self):
self.ticket.free_price = True
self.ticket.save()
response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
'item_%d' % self.ticket.id: '1',
'price_%d' % self.ticket.id: '24.00'
}, follow=True)
self.assertRedirects(response, '/%s/%s/' % (self.orga.slug, self.event.slug),
target_status_code=200)
doc = BeautifulSoup(response.rendered_content)
self.assertIn('Early-bird', doc.select('.cart .cart-row')[0].select('strong')[0].text)
self.assertIn('1', doc.select('.cart .cart-row')[0].select('.count')[0].text)
self.assertIn('24', doc.select('.cart .cart-row')[0].select('.price')[0].text)
self.assertIn('24', doc.select('.cart .cart-row')[0].select('.price')[1].text)
objs = list(CartPosition.objects.filter(cart_id=self.session_key, event=self.event))
self.assertEqual(len(objs), 1)
self.assertEqual(objs[0].item, self.ticket)
self.assertIsNone(objs[0].variation)
self.assertEqual(objs[0].price, 24)
def test_free_price_only_if_allowed(self):
self.ticket.free_price = False
self.ticket.save()
response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
'item_%d' % self.ticket.id: '1',
'price_%d' % self.ticket.id: '24.00'
}, follow=True)
self.assertRedirects(response, '/%s/%s/' % (self.orga.slug, self.event.slug),
target_status_code=200)
doc = BeautifulSoup(response.rendered_content)
self.assertIn('Early-bird', doc.select('.cart .cart-row')[0].select('strong')[0].text)
self.assertIn('1', doc.select('.cart .cart-row')[0].select('.count')[0].text)
self.assertIn('23', doc.select('.cart .cart-row')[0].select('.price')[0].text)
self.assertIn('23', doc.select('.cart .cart-row')[0].select('.price')[1].text)
objs = list(CartPosition.objects.filter(cart_id=self.session_key, event=self.event))
self.assertEqual(len(objs), 1)
self.assertEqual(objs[0].item, self.ticket)
self.assertIsNone(objs[0].variation)
self.assertEqual(objs[0].price, 23)
def test_free_price_lower_bound(self):
self.ticket.free_price = False
self.ticket.save()
response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
'item_%d' % self.ticket.id: '1',
'price_%d' % self.ticket.id: '12.00'
}, follow=True)
self.assertRedirects(response, '/%s/%s/' % (self.orga.slug, self.event.slug),
target_status_code=200)
doc = BeautifulSoup(response.rendered_content)
self.assertIn('Early-bird', doc.select('.cart .cart-row')[0].select('strong')[0].text)
self.assertIn('1', doc.select('.cart .cart-row')[0].select('.count')[0].text)
self.assertIn('23', doc.select('.cart .cart-row')[0].select('.price')[0].text)
self.assertIn('23', doc.select('.cart .cart-row')[0].select('.price')[1].text)
objs = list(CartPosition.objects.filter(cart_id=self.session_key, event=self.event))
self.assertEqual(len(objs), 1)
self.assertEqual(objs[0].item, self.ticket)
self.assertIsNone(objs[0].variation)
self.assertEqual(objs[0].price, 23)
def test_variation(self): def test_variation(self):
response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
'variation_%d_%d' % (self.shirt.id, self.shirt_red.id): '1' 'variation_%d_%d' % (self.shirt.id, self.shirt_red.id): '1'
@@ -75,6 +135,27 @@ class CartTest(CartTestMixin, TestCase):
self.assertEqual(objs[0].variation, self.shirt_red) self.assertEqual(objs[0].variation, self.shirt_red)
self.assertEqual(objs[0].price, 14) self.assertEqual(objs[0].price, 14)
def test_variation_free_price(self):
self.shirt.free_price = True
self.shirt.save()
response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
'variation_%d_%d' % (self.shirt.id, self.shirt_red.id): '1',
'price_%d_%d' % (self.shirt.id, self.shirt_red.id): '16',
}, follow=True)
self.assertRedirects(response, '/%s/%s/' % (self.orga.slug, self.event.slug),
target_status_code=200)
doc = BeautifulSoup(response.rendered_content)
self.assertIn('Shirt', doc.select('.cart .cart-row')[0].select('strong')[0].text)
self.assertIn('Red', doc.select('.cart .cart-row')[0].text)
self.assertIn('1', doc.select('.cart .cart-row')[0].select('.count')[0].text)
self.assertIn('16', doc.select('.cart .cart-row')[0].select('.price')[0].text)
self.assertIn('16', doc.select('.cart .cart-row')[0].select('.price')[1].text)
objs = list(CartPosition.objects.filter(cart_id=self.session_key, event=self.event))
self.assertEqual(len(objs), 1)
self.assertEqual(objs[0].item, self.shirt)
self.assertEqual(objs[0].variation, self.shirt_red)
self.assertEqual(objs[0].price, 16)
def test_count(self): def test_count(self):
response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
'item_%d' % self.ticket.id: '2' 'item_%d' % self.ticket.id: '2'
+33
View File
@@ -217,6 +217,23 @@ class CheckoutTestCase(TestCase):
session[key] = value session[key] = value
session.save() session.save()
def test_free_price(self):
self.ticket.free_price = True
self.ticket.save()
cr1 = CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.ticket,
price=42, expires=now() + timedelta(minutes=10)
)
self._set_session('payment', 'banktransfer')
response = self.client.post('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True)
doc = BeautifulSoup(response.rendered_content)
self.assertEqual(len(doc.select(".thank-you")), 1)
self.assertFalse(CartPosition.objects.filter(id=cr1.id).exists())
self.assertEqual(Order.objects.count(), 1)
self.assertEqual(OrderPosition.objects.count(), 1)
self.assertEqual(OrderPosition.objects.first().price, 42)
def test_confirm_in_time(self): def test_confirm_in_time(self):
cr1 = CartPosition.objects.create( cr1 = CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.ticket, event=self.event, cart_id=self.session_key, item=self.ticket,
@@ -260,6 +277,22 @@ class CheckoutTestCase(TestCase):
cr1 = CartPosition.objects.get(id=cr1.id) cr1 = CartPosition.objects.get(id=cr1.id)
self.assertEqual(cr1.price, 24) self.assertEqual(cr1.price, 24)
def test_confirm_free_price_increased(self):
self.ticket.default_price = 24
self.ticket.free_price = True
self.ticket.save()
cr1 = CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.ticket,
price=23, expires=now() - timedelta(minutes=10)
)
self._set_session('payment', 'banktransfer')
response = self.client.post('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True)
doc = BeautifulSoup(response.rendered_content)
self.assertEqual(len(doc.select(".alert-danger")), 1)
cr1 = CartPosition.objects.get(id=cr1.id)
self.assertEqual(cr1.price, 24)
def test_voucher(self): def test_voucher(self):
v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event, v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event,
valid_until=now() + timedelta(days=2)) valid_until=now() + timedelta(days=2))