Files
pretix_original/src/pretix/presale/views/checkout.py
2015-10-05 11:24:00 +02:00

276 lines
12 KiB
Python

from django.contrib import messages
from django.core.urlresolvers import reverse
from django.db.models import Q, Sum
from django.http import HttpRequest
from django.shortcuts import redirect
from django.utils import translation
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from django.views.generic import TemplateView
from pretix.base.models import CartPosition, OrderPosition, QuestionAnswer
from pretix.base.services.orders import OrderError, perform_order
from pretix.base.signals import register_payment_providers
from pretix.presale.forms.checkout import QuestionsForm
from pretix.presale.views import CartDisplayMixin, EventViewMixin
class CheckoutView(TemplateView):
def get_payment_url(self):
return reverse('presale:event.checkout.payment', kwargs={
'event': self.request.event.slug,
'organizer': self.request.event.organizer.slug,
})
def get_confirm_url(self):
return reverse('presale:event.checkout.confirm', kwargs={
'event': self.request.event.slug,
'organizer': self.request.event.organizer.slug,
})
def get_questions_url(self):
return reverse('presale:event.checkout.start', kwargs={
'event': self.request.event.slug,
'organizer': self.request.event.organizer.slug,
})
def get_index_url(self):
return reverse('presale:event.index', kwargs={
'event': self.request.event.slug,
'organizer': self.request.event.organizer.slug
})
def get_order_url(self, order):
return reverse('presale:event.order', kwargs={
'event': self.request.event.slug,
'organizer': self.request.event.organizer.slug,
'order': order.code,
'secret': order.secret
}) + '?thanks=yes'
class QuestionsViewMixin:
@cached_property
def forms(self):
"""
A list of forms with one form for each cart cart position that has questions
the user can answer. All forms have a custom prefix, so that they can all be
submitted at once.
"""
formlist = []
for cr in self.positions:
cartpos = cr if isinstance(cr, CartPosition) else None
orderpos = cr if isinstance(cr, OrderPosition) else None
form = QuestionsForm(event=self.request.event,
prefix=cr.identity,
cartpos=cartpos,
orderpos=orderpos,
data=(self.request.POST if self.request.method == 'POST' else None))
form.pos = cartpos or orderpos
if len(form.fields) > 0:
formlist.append(form)
return formlist
def save(self):
failed = False
for form in self.forms:
# Every form represents a CartPosition or OrderPosition with questions attached
if not form.is_valid():
failed = True
else:
# This form was correctly filled, so we store the data as
# answers to the questions / in the CartPosition object
for k, v in form.cleaned_data.items():
if k == 'attendee_name':
form.pos = form.pos.clone()
form.pos.attendee_name = v if v != '' else None
form.pos.save()
elif k.startswith('question_') and v is not None:
field = form.fields[k]
if hasattr(field, 'answer'):
# We already have a cached answer object, so we don't
# have to create a new one
if v == '':
field.answer.delete()
else:
field.answer = field.answer.clone()
field.answer.answer = v
field.answer.save()
elif v != '':
QuestionAnswer.objects.create(
cartposition=(form.pos if isinstance(form.pos, CartPosition) else None),
orderposition=(form.pos if isinstance(form.pos, OrderPosition) else None),
question=field.question,
answer=v
)
return not failed
class CheckoutStart(EventViewMixin, CartDisplayMixin, QuestionsViewMixin, CheckoutView):
template_name = "pretixpresale/event/checkout_questions.html"
def post(self, *args, **kwargs):
failed = not self.save()
if failed:
messages.error(self.request,
_("We had difficulties processing your input. Please review the errors below."))
return self.get(*args, **kwargs)
return redirect(self.get_payment_url())
def get(self, *args, **kwargs):
if not self.positions:
messages.error(self.request,
_("Your cart is empty"))
return redirect(self.get_index_url())
if not self.forms:
# Nothing to do here
if self.request.GET.get('back', 'false') == 'true':
return redirect(self.get_index_url())
return redirect(self.get_payment_url())
return super().get(*args, **kwargs)
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['forms'] = self.forms
return ctx
class PaymentDetails(EventViewMixin, CartDisplayMixin, CheckoutView):
template_name = "pretixpresale/event/checkout_payment.html"
@cached_property
def _total_order_value(self):
return CartPosition.objects.current.filter(
Q(session=self.request.session.session_key) & Q(event=self.request.event)
).aggregate(sum=Sum('price'))['sum']
@cached_property
def provider_forms(self):
providers = []
responses = register_payment_providers.send(self.request.event)
for receiver, response in responses:
provider = response(self.request.event)
if not provider.is_enabled or not provider.is_allowed(self.request):
continue
fee = provider.calculate_fee(self._total_order_value)
providers.append({
'provider': provider,
'fee': fee,
'form': provider.payment_form_render(self.request),
})
return providers
def post(self, request, *args, **kwargs):
for p in self.provider_forms:
if p['provider'].identifier == request.POST.get('payment', ''):
request.session['payment'] = p['provider'].identifier
resp = p['provider'].checkout_prepare(
request, self.get_cart(payment_fee=p['provider'].calculate_fee(self._total_order_value)))
if isinstance(resp, str):
return redirect(resp)
elif resp is True:
return redirect(self.get_confirm_url())
else:
return self.get(request, *args, **kwargs)
messages.error(self.request, _("Please select a payment method."))
return self.get(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
if self._total_order_value == 0:
request.session['payment'] = 'free'
return redirect(self.get_confirm_url())
return super().get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['providers'] = self.provider_forms
ctx['selected'] = self.request.POST.get('payment', self.request.session.get('payment', ''))
return ctx
def get_previous_url(self):
return self.get_questions_url() + "?back=true"
class OrderConfirm(EventViewMixin, CartDisplayMixin, CheckoutView):
template_name = "pretixpresale/event/checkout_confirm.html"
def __init__(self, *args, **kwargs):
self.msg_some_unavailable = False
super().__init__(*args, **kwargs)
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['cart'] = self.get_cart(answers=True)
ctx['payment'] = self.payment_provider.checkout_confirm_render(self.request)
ctx['payment_provider'] = self.payment_provider
return ctx
@cached_property
def payment_provider(self):
responses = register_payment_providers.send(self.request.event)
for receiver, response in responses:
provider = response(self.request.event)
if provider.identifier == self.request.session['payment']:
return provider
def check_process(self, request):
if len(self.positions) == 0:
messages.warning(request, _('Your cart is empty.'))
return redirect(self.get_index_url())
if 'payment' not in request.session or not self.payment_provider:
messages.error(request, _('The payment information you entered was incomplete.'))
return redirect(self.get_payment_url())
if not self.payment_provider.payment_is_valid_session(request) or \
not self.payment_provider.is_enabled or \
not self.payment_provider.is_allowed(request):
messages.error(request, _('The payment information you entered was incomplete.'))
return redirect(self.get_payment_url())
for cp in self.positions:
answ = {
aw.question_id: aw.answer for aw in cp.answers.all()
}
for q in cp.item.questions.all():
if q.required and q.identity not in answ:
messages.warning(request, _('Please fill in answers to all required questions.'))
return redirect(self.get_questions_url())
if cp.item.admission and self.request.event.settings.get('attendee_names_required', as_type=bool) \
and cp.attendee_name is None:
messages.warning(request, _('Please fill in answers to all required questions.'))
return redirect(self.get_questions_url())
def get(self, request, *args, **kwargs):
self.request = request
return self.check_process(request) or super().get(request, *args, **kwargs)
def error_message(self, msg, important=False):
if not self.msg_some_unavailable or important:
self.msg_some_unavailable = True
messages.error(self.request, msg)
def post(self, request, *args, **kwargs):
self.request = request
return self.check_process(request) or self.perform_order(request)
def perform_order(self, request: HttpRequest):
try:
order = perform_order(self.request.event, self.payment_provider, self.positions,
email=request.session.get('guest_email', None),
locale=translation.get_language())
except OrderError as e:
messages.error(request, str(e))
return redirect(self.get_confirm_url())
else:
# Message is delivered via GET parameter
# messages.success(request, _('Your order has been placed.'))
resp = self.payment_provider.payment_perform(request, order)
return redirect(resp or self.get_order_url(order))
def get_previous_url(self):
if self.payment_provider.identifier != "free":
return self.get_payment_url()
else:
return self.get_questions_url() + "?back=true"