mirror of
https://github.com/pretix/pretix.git
synced 2026-09-13 16:14:40 +00:00
Fixed #106 -- added pay-what-you-want tickets
This commit is contained in:
@@ -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 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,
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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 %}
|
||||||
|
|||||||
@@ -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 []
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
Reference in New Issue
Block a user