mirror of
https://github.com/pretix/pretix.git
synced 2026-05-07 15:34:02 +00:00
Isolate cart sessions
This commit is contained in:
@@ -21,6 +21,7 @@ from pretix.base.reldate import RelativeDateField, RelativeDateWrapper
|
||||
from pretix.base.settings import SettingsSandbox
|
||||
from pretix.base.signals import register_payment_providers
|
||||
from pretix.presale.views import get_cart_total
|
||||
from pretix.presale.views.cart import get_or_create_cart_id
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -275,7 +276,7 @@ class BasePaymentProvider:
|
||||
|
||||
The default implementation checks for the _availability_date setting to be either unset or in the future.
|
||||
"""
|
||||
return self._is_still_available(cart_id=request.session.session_key)
|
||||
return self._is_still_available(cart_id=get_or_create_cart_id(request))
|
||||
|
||||
def payment_form_render(self, request: HttpRequest) -> str:
|
||||
"""
|
||||
|
||||
@@ -25,6 +25,9 @@ from pretix.presale.signals import (
|
||||
)
|
||||
from pretix.presale.views import CartMixin, get_cart, get_cart_total
|
||||
from pretix.presale.views.async import AsyncAction
|
||||
from pretix.presale.views.cart import (
|
||||
cart_session, create_empty_cart_id, get_or_create_cart_id,
|
||||
)
|
||||
from pretix.presale.views.questions import QuestionsViewMixin
|
||||
|
||||
|
||||
@@ -82,9 +85,13 @@ class BaseCheckoutFlowStep:
|
||||
if n:
|
||||
return n.get_step_url()
|
||||
|
||||
@cached_property
|
||||
def cart_session(self):
|
||||
return cart_session(self.request)
|
||||
|
||||
@cached_property
|
||||
def invoice_address(self):
|
||||
iapk = self.request.session.get('invoice_address_{}'.format(self.request.event.pk))
|
||||
iapk = self.cart_session.get('invoice_address')
|
||||
if not iapk:
|
||||
return InvoiceAddress()
|
||||
|
||||
@@ -257,7 +264,7 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
|
||||
if not is_valid:
|
||||
return self.get(request, *args, **kwargs)
|
||||
|
||||
return self.do(self.request.event.id, data, self.request.session.session_key,
|
||||
return self.do(self.request.event.id, data, get_or_create_cart_id(self.request),
|
||||
invoice_address=self.invoice_address.pk)
|
||||
|
||||
|
||||
@@ -272,9 +279,9 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
|
||||
@cached_property
|
||||
def contact_form(self):
|
||||
initial = {
|
||||
'email': self.request.session.get('email', '')
|
||||
'email': self.cart_session.get('email', '')
|
||||
}
|
||||
initial.update(self.request.session.get('contact_form_data', {}))
|
||||
initial.update(self.cart_session.get('contact_form_data', {}))
|
||||
return ContactForm(data=self.request.POST if self.request.method == "POST" else None,
|
||||
event=self.request.event,
|
||||
initial=initial)
|
||||
@@ -301,15 +308,15 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
|
||||
messages.error(request,
|
||||
_("We had difficulties processing your input. Please review the errors below."))
|
||||
return self.render()
|
||||
request.session['email'] = self.contact_form.cleaned_data['email']
|
||||
self.cart_session['email'] = self.contact_form.cleaned_data['email']
|
||||
if request.event.settings.invoice_address_asked:
|
||||
addr = self.invoice_form.save()
|
||||
request.session['invoice_address_{}'.format(request.event.pk)] = addr.pk
|
||||
request.session['contact_form_data'] = self.contact_form.cleaned_data
|
||||
self.cart_session['invoice_address'] = addr.pk
|
||||
self.cart_session['contact_form_data'] = self.contact_form.cleaned_data
|
||||
|
||||
update_tax_rates(
|
||||
event=request.event,
|
||||
cart_id=request.session.session_key,
|
||||
cart_id=get_or_create_cart_id(request),
|
||||
invoice_address=self.invoice_form.instance
|
||||
)
|
||||
|
||||
@@ -319,11 +326,11 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
|
||||
self.request = request
|
||||
try:
|
||||
emailval = EmailValidator()
|
||||
if 'email' not in request.session:
|
||||
if 'email' not in self.cart_session:
|
||||
if warn:
|
||||
messages.warning(request, _('Please enter a valid email address.'))
|
||||
return False
|
||||
emailval(request.session.get('email'))
|
||||
emailval(self.cart_session.get('email'))
|
||||
except ValidationError:
|
||||
if warn:
|
||||
messages.warning(request, _('Please enter a valid email address.'))
|
||||
@@ -404,7 +411,7 @@ class PaymentStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
|
||||
self.request = request
|
||||
for p in self.provider_forms:
|
||||
if p['provider'].identifier == request.POST.get('payment', ''):
|
||||
request.session['payment'] = p['provider'].identifier
|
||||
self.cart_session['payment'] = p['provider'].identifier
|
||||
resp = p['provider'].checkout_prepare(
|
||||
request,
|
||||
self.get_cart()
|
||||
@@ -422,16 +429,16 @@ class PaymentStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['providers'] = self.provider_forms
|
||||
ctx['show_fees'] = any(p['fee'] for p in self.provider_forms)
|
||||
ctx['selected'] = self.request.POST.get('payment', self.request.session.get('payment', ''))
|
||||
ctx['selected'] = self.request.POST.get('payment', self.cart_session.get('payment', ''))
|
||||
return ctx
|
||||
|
||||
@cached_property
|
||||
def payment_provider(self):
|
||||
return self.request.event.get_payment_providers().get(self.request.session['payment'])
|
||||
return self.request.event.get_payment_providers().get(self.cart_session['payment'])
|
||||
|
||||
def is_completed(self, request, warn=False):
|
||||
self.request = request
|
||||
if 'payment' not in request.session or not self.payment_provider:
|
||||
if 'payment' not in self.cart_session or not self.payment_provider:
|
||||
if warn:
|
||||
messages.error(request, _('The payment information you entered was incomplete.'))
|
||||
return False
|
||||
@@ -446,7 +453,7 @@ class PaymentStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
|
||||
def is_applicable(self, request):
|
||||
self.request = request
|
||||
if self._total_order_value == 0:
|
||||
request.session['payment'] = 'free'
|
||||
self.cart_session['payment'] = 'free'
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -476,7 +483,7 @@ class ConfirmStep(CartMixin, AsyncAction, TemplateFlowStep):
|
||||
responses = contact_form_fields.send(self.event)
|
||||
for r, response in sorted(responses, key=lambda r: str(r[0])):
|
||||
for key, value in response.items():
|
||||
v = self.request.session.get('contact_form_data', {}).get(key)
|
||||
v = self.cart_session.get('contact_form_data', {}).get(key)
|
||||
if v is True:
|
||||
v = _('Yes')
|
||||
elif v is False:
|
||||
@@ -495,7 +502,7 @@ class ConfirmStep(CartMixin, AsyncAction, TemplateFlowStep):
|
||||
|
||||
@cached_property
|
||||
def payment_provider(self):
|
||||
return self.request.event.get_payment_providers().get(self.request.session['payment'])
|
||||
return self.request.event.get_payment_providers().get(self.cart_session['payment'])
|
||||
|
||||
def get(self, request):
|
||||
self.request = request
|
||||
@@ -520,16 +527,17 @@ class ConfirmStep(CartMixin, AsyncAction, TemplateFlowStep):
|
||||
return redirect(self.get_error_url())
|
||||
|
||||
meta_info = {
|
||||
'contact_form_data': self.request.session.get('contact_form_data', {})
|
||||
'contact_form_data': self.cart_session.get('contact_form_data', {})
|
||||
}
|
||||
for receiver, response in order_meta_from_request.send(sender=request.event, request=request):
|
||||
meta_info.update(response)
|
||||
|
||||
return self.do(self.request.event.id, self.payment_provider.identifier,
|
||||
[p.id for p in self.positions], request.session.get('email'),
|
||||
[p.id for p in self.positions], self.cart_session.get('email'),
|
||||
translation.get_language(), self.invoice_address.pk, meta_info)
|
||||
|
||||
def get_success_message(self, value):
|
||||
create_empty_cart_id(self.request)
|
||||
return None
|
||||
|
||||
def get_success_url(self, value):
|
||||
|
||||
@@ -19,6 +19,11 @@ class CartMixin:
|
||||
"""
|
||||
return list(get_cart(self.request))
|
||||
|
||||
@cached_property
|
||||
def cart_session(self):
|
||||
from pretix.presale.views.cart import cart_session
|
||||
return cart_session(self.request)
|
||||
|
||||
def get_cart(self, answers=False, queryset=None, order=None, downloads=False):
|
||||
if queryset:
|
||||
prefetch = []
|
||||
@@ -102,7 +107,7 @@ class CartMixin:
|
||||
if order:
|
||||
fees = order.fees.all()
|
||||
else:
|
||||
iapk = self.request.session.get('invoice_address_{}'.format(self.request.event.pk))
|
||||
iapk = self.cart_session.get('invoice_address')
|
||||
ia = None
|
||||
if iapk:
|
||||
try:
|
||||
@@ -110,7 +115,7 @@ class CartMixin:
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
pass
|
||||
|
||||
fees = get_fees(self.request.event, self.request, total, ia, self.request.session.get('payment'))
|
||||
fees = get_fees(self.request.event, self.request, total, ia, self.cart_session.get('payment'))
|
||||
|
||||
total += sum([f.value for f in fees])
|
||||
net_total += sum([f.net_value for f in fees])
|
||||
@@ -137,9 +142,11 @@ class CartMixin:
|
||||
|
||||
|
||||
def get_cart(request):
|
||||
from pretix.presale.views.cart import get_or_create_cart_id
|
||||
|
||||
if not hasattr(request, '_cart_cache'):
|
||||
request._cart_cache = CartPosition.objects.filter(
|
||||
cart_id=request.session.session_key, event=request.event
|
||||
cart_id=get_or_create_cart_id(request), event=request.event
|
||||
).order_by(
|
||||
'item', 'variation'
|
||||
).select_related(
|
||||
@@ -152,12 +159,14 @@ def get_cart(request):
|
||||
|
||||
|
||||
def get_cart_total(request):
|
||||
from pretix.presale.views.cart import get_or_create_cart_id
|
||||
|
||||
if not hasattr(request, '_cart_total_cache'):
|
||||
if hasattr(request, '_cart_cache'):
|
||||
request._cart_total_cache = sum(i.price for i in request._cart_cache)
|
||||
else:
|
||||
request._cart_total_cache = CartPosition.objects.filter(
|
||||
cart_id=request.session.session_key, event=request.event
|
||||
cart_id=get_or_create_cart_id(request), event=request.event
|
||||
).aggregate(sum=Sum('price'))['sum'] or 0
|
||||
return request._cart_total_cache
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ from django.db.models import Count, Prefetch, Q
|
||||
from django.http import FileResponse, Http404, JsonResponse
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.utils import translation
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import ugettext as _
|
||||
@@ -39,9 +40,13 @@ class CartActionMixin:
|
||||
def get_error_url(self):
|
||||
return self.get_next_url()
|
||||
|
||||
@cached_property
|
||||
def cart_session(self):
|
||||
return cart_session(self.request)
|
||||
|
||||
@cached_property
|
||||
def invoice_address(self):
|
||||
iapk = self.request.session.get('invoice_address_{}'.format(self.request.event.pk))
|
||||
iapk = self.cart_session.get('invoice_address')
|
||||
if not iapk:
|
||||
return InvoiceAddress()
|
||||
|
||||
@@ -124,19 +129,49 @@ class CartActionMixin:
|
||||
return items
|
||||
|
||||
|
||||
def create_empty_cart_id(request):
|
||||
current_id = request.session.get('current_cart_event_{}'.format(request.event.pk))
|
||||
if current_id and current_id in request.session.get('carts', {}):
|
||||
del request.session['carts'][current_id]
|
||||
del request.session['current_cart_event_{}'.format(request.event.pk)]
|
||||
return get_or_create_cart_id(request)
|
||||
|
||||
|
||||
def get_or_create_cart_id(request):
|
||||
current_id = request.session.get('current_cart_event_{}'.format(request.event.pk))
|
||||
if current_id and current_id in request.session.get('carts', {}):
|
||||
return current_id
|
||||
else:
|
||||
while True:
|
||||
new_id = get_random_string(length=32)
|
||||
if not CartPosition.objects.filter(cart_id=new_id).exists():
|
||||
break
|
||||
|
||||
if 'carts' not in request.session:
|
||||
request.session['carts'] = {}
|
||||
request.session['carts'][new_id] = {}
|
||||
request.session['current_cart_event_{}'.format(request.event.pk)] = new_id
|
||||
return new_id
|
||||
|
||||
|
||||
def cart_session(request):
|
||||
request.session.modified = True
|
||||
return request.session['carts'][get_or_create_cart_id(request)]
|
||||
|
||||
|
||||
class CartRemove(EventViewMixin, CartActionMixin, AsyncAction, View):
|
||||
task = remove_cart_position
|
||||
known_errortypes = ['CartError']
|
||||
|
||||
def get_success_message(self, value):
|
||||
if CartPosition.objects.filter(cart_id=self.request.session.session_key).exists():
|
||||
if CartPosition.objects.filter(cart_id=get_or_create_cart_id(self.request)).exists():
|
||||
return _('Your cart has been updated.')
|
||||
else:
|
||||
return _('Your cart is now empty.')
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
if 'id' in request.POST:
|
||||
return self.do(self.request.event.id, request.POST.get('id'), self.request.session.session_key, translation.get_language())
|
||||
return self.do(self.request.event.id, request.POST.get('id'), get_or_create_cart_id(self.request), translation.get_language())
|
||||
else:
|
||||
if 'ajax' in self.request.GET or 'ajax' in self.request.POST:
|
||||
return JsonResponse({
|
||||
@@ -154,7 +189,7 @@ class CartClear(EventViewMixin, CartActionMixin, AsyncAction, View):
|
||||
return _('Your cart is now empty.')
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
return self.do(self.request.event.id, self.request.session.session_key, translation.get_language())
|
||||
return self.do(self.request.event.id, get_or_create_cart_id(self.request), translation.get_language())
|
||||
|
||||
|
||||
class CartAdd(EventViewMixin, CartActionMixin, AsyncAction, View):
|
||||
@@ -167,7 +202,7 @@ class CartAdd(EventViewMixin, CartActionMixin, AsyncAction, View):
|
||||
def post(self, request, *args, **kwargs):
|
||||
items = self._items_from_post_data()
|
||||
if items:
|
||||
return self.do(self.request.event.id, items, self.request.session.session_key, translation.get_language(),
|
||||
return self.do(self.request.event.id, items, get_or_create_cart_id(self.request), translation.get_language(),
|
||||
self.invoice_address.pk)
|
||||
else:
|
||||
if 'ajax' in self.request.GET or 'ajax' in self.request.POST:
|
||||
@@ -299,7 +334,7 @@ class RedeemView(NoSearchIndexViewMixin, EventViewMixin, TemplateView):
|
||||
|
||||
redeemed_in_carts = CartPosition.objects.filter(
|
||||
Q(voucher=self.voucher) & Q(event=request.event) &
|
||||
(Q(expires__gte=now()) | Q(cart_id=request.session.session_key))
|
||||
(Q(expires__gte=now()) | Q(cart_id=get_or_create_cart_id(request)))
|
||||
)
|
||||
v_avail = self.voucher.max_usages - self.voucher.redeemed - redeemed_in_carts.count()
|
||||
if v_avail < 1:
|
||||
@@ -337,7 +372,7 @@ class AnswerDownload(EventViewMixin, View):
|
||||
answid = kwargs.get('answer')
|
||||
answer = get_object_or_404(
|
||||
QuestionAnswer,
|
||||
cartposition__cart_id=self.request.session.session_key,
|
||||
cartposition__cart_id=get_or_create_cart_id(self.request),
|
||||
id=answid
|
||||
)
|
||||
if not answer.file:
|
||||
|
||||
@@ -9,13 +9,15 @@ from pretix.base.services.cart import CartError
|
||||
from pretix.base.signals import validate_cart
|
||||
from pretix.multidomain.urlreverse import eventreverse
|
||||
from pretix.presale.checkoutflow import get_checkout_flow
|
||||
from pretix.presale.views.cart import get_or_create_cart_id
|
||||
|
||||
|
||||
class CheckoutView(View):
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
|
||||
self.request = request
|
||||
cart_pos = CartPosition.objects.filter(
|
||||
cart_id=self.request.session.session_key, event=self.request.event
|
||||
cart_id=get_or_create_cart_id(request), event=self.request.event
|
||||
)
|
||||
|
||||
if not cart_pos.exists() and "async_id" not in request.GET:
|
||||
|
||||
20
src/pretix/testutils/sessions.py
Normal file
20
src/pretix/testutils/sessions.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from django.utils.crypto import get_random_string
|
||||
|
||||
|
||||
def add_cart_session(client, event, data):
|
||||
new_id = get_random_string(length=32)
|
||||
session = client.session
|
||||
session['current_cart_event_{}'.format(event.pk)] = new_id
|
||||
if 'carts' not in session:
|
||||
session['carts'] = {}
|
||||
session['carts'][new_id] = data
|
||||
session.save()
|
||||
return new_id
|
||||
|
||||
|
||||
def get_cart_session_key(client, event):
|
||||
cart_id = client.session.get('current_cart_event_{}'.format(event.pk))
|
||||
if cart_id:
|
||||
return cart_id
|
||||
else:
|
||||
return add_cart_session(client, event, {})
|
||||
Reference in New Issue
Block a user