mirror of
https://github.com/pretix/pretix.git
synced 2026-05-04 15:04:03 +00:00
Fixed #106 -- added pay-what-you-want tickets
This commit is contained in:
24
src/pretix/base/migrations/0017_auto_20160324_1615.py
Normal file
24
src/pretix/base/migrations/0017_auto_20160324_1615.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -95,6 +95,7 @@ class ItemFormGeneral(I18nModelForm):
|
||||
'description',
|
||||
'picture',
|
||||
'default_price',
|
||||
'free_price',
|
||||
'tax_rate',
|
||||
'available_from',
|
||||
'available_until',
|
||||
|
||||
@@ -2,34 +2,35 @@
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% block inside %}
|
||||
<form action="" method="post" class="form-horizontal" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<fieldset>
|
||||
<legend>{% trans "General information" %}</legend>
|
||||
{% bootstrap_field form.name layout="horizontal" %}
|
||||
{% bootstrap_field form.active layout="horizontal" %}
|
||||
<form action="" method="post" class="form-horizontal" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<fieldset>
|
||||
<legend>{% trans "General information" %}</legend>
|
||||
{% bootstrap_field form.name layout="horizontal" %}
|
||||
{% bootstrap_field form.active layout="horizontal" %}
|
||||
{% if form.has_variations %}
|
||||
{% bootstrap_field form.has_variations layout="horizontal" %}
|
||||
{% endif %}
|
||||
{% bootstrap_field form.category layout="horizontal" %}
|
||||
{% bootstrap_field form.category 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" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Price settings" %}</legend>
|
||||
{% bootstrap_field form.default_price layout="horizontal" %}
|
||||
{% bootstrap_field form.tax_rate layout="horizontal" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Availability" %}</legend>
|
||||
{% bootstrap_field form.available_from layout="horizontal" %}
|
||||
{% bootstrap_field form.available_until layout="horizontal" %}
|
||||
</fieldset>
|
||||
<div class="form-group submit-group">
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Price settings" %}</legend>
|
||||
{% bootstrap_field form.default_price layout="horizontal" %}
|
||||
{% bootstrap_field form.tax_rate layout="horizontal" %}
|
||||
{% bootstrap_field form.free_price layout="horizontal" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Availability" %}</legend>
|
||||
{% bootstrap_field form.available_from layout="horizontal" %}
|
||||
{% bootstrap_field form.available_until layout="horizontal" %}
|
||||
</fieldset>
|
||||
<div class="form-group submit-group">
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
@@ -28,12 +28,17 @@
|
||||
<form action="{% eventurl event "presale:event.cart.remove" %}"
|
||||
method="post" data-asynctask>
|
||||
{% csrf_token %}
|
||||
|
||||
{% if line.variation %}
|
||||
<input type="hidden" name="variation_{{ line.item.id }}_{{ line.variation.id }}"
|
||||
value="1" />
|
||||
<input type="hidden" name="price_{{ line.item.id }}_{{ line.variation.id }}"
|
||||
value="{{ line.price }}" />
|
||||
{% else %}
|
||||
<input type="hidden" name="item_{{ line.item.id }}"
|
||||
value="1" />
|
||||
<input type="hidden" name="price_{{ line.item.id }}"
|
||||
value="{{ line.price }}" />
|
||||
{% endif %}
|
||||
<button class="btn btn-mini btn-link"><i class="fa fa-minus"></i></button>
|
||||
</form>
|
||||
@@ -46,9 +51,13 @@
|
||||
{% if line.variation %}
|
||||
<input type="hidden" name="variation_{{ line.item.id }}_{{ line.variation.id }}"
|
||||
value="1" />
|
||||
<input type="hidden" name="price_{{ line.item.id }}_{{ line.variation.id }}"
|
||||
value="{{ line.price }}" />
|
||||
{% else %}
|
||||
<input type="hidden" name="item_{{ line.item.id }}"
|
||||
value="1" />
|
||||
<input type="hidden" name="price_{{ line.item.id }}"
|
||||
value="{{ line.price }}" />
|
||||
{% endif %}
|
||||
<button class="btn btn-mini btn-link"><i class="fa fa-plus"></i></button>
|
||||
</form>
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
{% if item.description %}<p>{{ item.description }}</p>{% endif %}
|
||||
</div>
|
||||
<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 %}
|
||||
from {{ currency }} {{ minprice }}
|
||||
{% endblocktrans %}
|
||||
@@ -101,7 +101,18 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
<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 %}
|
||||
<br/>
|
||||
<small>{% blocktrans trimmed with rate=item.tax_rate %}
|
||||
@@ -135,13 +146,24 @@
|
||||
</a>
|
||||
{% endif %}
|
||||
<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 %}
|
||||
{% include "pretixpresale/event/fragment_quota_left.html" with avail=item.cached_availability %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<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 %}
|
||||
<br/>
|
||||
<small>{% blocktrans trimmed with rate=item.tax_rate %}
|
||||
|
||||
@@ -33,17 +33,19 @@ class CartActionMixin:
|
||||
"""
|
||||
items = []
|
||||
for key, value in self.request.POST.items():
|
||||
if value.strip() == '':
|
||||
if value.strip() == '' or '_' not in key:
|
||||
continue
|
||||
|
||||
price = self.request.POST.get('price_' + key.split("_", 1)[1], "")
|
||||
if key.startswith('item_'):
|
||||
try:
|
||||
items.append((int(key.split("_")[1]), None, int(value)))
|
||||
items.append((int(key.split("_")[1]), None, int(value), price))
|
||||
except ValueError:
|
||||
messages.error(self.request, _('Please enter numbers only.'))
|
||||
return []
|
||||
elif key.startswith('variation_'):
|
||||
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:
|
||||
messages.error(self.request, _('Please enter numbers only.'))
|
||||
return []
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
.input-item-count {
|
||||
text-align: center;
|
||||
}
|
||||
.input-item-price {
|
||||
text-align: right;
|
||||
}
|
||||
.availability-box {
|
||||
text-align: center;
|
||||
|
||||
|
||||
@@ -57,6 +57,66 @@ class CartTest(CartTestMixin, TestCase):
|
||||
self.assertIsNone(objs[0].variation)
|
||||
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):
|
||||
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'
|
||||
@@ -75,6 +135,27 @@ class CartTest(CartTestMixin, TestCase):
|
||||
self.assertEqual(objs[0].variation, self.shirt_red)
|
||||
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):
|
||||
response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
|
||||
'item_%d' % self.ticket.id: '2'
|
||||
|
||||
@@ -217,6 +217,23 @@ class CheckoutTestCase(TestCase):
|
||||
session[key] = value
|
||||
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):
|
||||
cr1 = CartPosition.objects.create(
|
||||
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)
|
||||
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):
|
||||
v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event,
|
||||
valid_until=now() + timedelta(days=2))
|
||||
|
||||
Reference in New Issue
Block a user