Isolate cart sessions

This commit is contained in:
Raphael Michel
2017-10-02 17:00:35 +02:00
parent f15a72e59d
commit 26f258c6cf
10 changed files with 126 additions and 50 deletions
+2 -1
View File
@@ -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:
"""
+27 -19
View File
@@ -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):
+13 -4
View File
@@ -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
+42 -7
View File
@@ -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:
+3 -1
View 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
View 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, {})
+3 -3
View File
@@ -1,12 +1,12 @@
import datetime
import pytest
from django.conf import settings
from django.utils.timezone import now
from pretix.base.models import (
CartPosition, Event, Item, ItemCategory, Organizer, Quota,
)
from pretix.testutils.sessions import add_cart_session, get_cart_session_key
@pytest.fixture
@@ -29,7 +29,7 @@ def env(client):
event.settings.set('payment_paypal_endpoint', 'sandbox')
event.settings.set('payment_paypal_client_id', '12345')
event.settings.set('payment_paypal_secret', '12345')
client.session.email = 'admin@localhost'
add_cart_session(client, event, {'email': 'admin@localhost'})
return client, ticket
@@ -44,7 +44,7 @@ def test_payment(env, monkeypatch):
monkeypatch.setattr("pretix.plugins.paypal.payment.Paypal._create_payment", create_payment)
client, ticket = env
session_key = client.cookies.get(settings.SESSION_COOKIE_NAME).value
session_key = get_cart_session_key(client, ticket.event)
CartPosition.objects.create(
event=ticket.event, cart_id=session_key, item=ticket,
price=23, expires=now() + datetime.timedelta(minutes=10)
+3 -3
View File
@@ -1,12 +1,12 @@
import datetime
import pytest
from django.conf import settings
from django.utils.timezone import now
from pretix.base.models import (
CartPosition, Event, Item, ItemCategory, Organizer, Quota,
)
from pretix.testutils.sessions import add_cart_session, get_cart_session_key
class MockedCharge():
@@ -35,7 +35,7 @@ def env(client):
quota_tickets.items.add(ticket)
event.settings.set('attendee_names_asked', False)
event.settings.set('payment_stripe__enabled', True)
client.session.email = 'admin@localhost'
add_cart_session(client, event, {'email': 'admin@localhost'})
return client, ticket
@@ -53,7 +53,7 @@ def test_payment(env, monkeypatch):
monkeypatch.setattr("stripe.Charge.create", charge_create)
client, ticket = env
session_key = client.cookies.get(settings.SESSION_COOKIE_NAME).value
session_key = get_cart_session_key(client, ticket.event)
CartPosition.objects.create(
event=ticket.event, cart_id=session_key, item=ticket,
price=13.37, expires=now() + datetime.timedelta(minutes=10)
+4 -4
View File
@@ -3,7 +3,6 @@ from datetime import timedelta
from decimal import Decimal
from bs4 import BeautifulSoup
from django.conf import settings
from django.test import TestCase
from django.utils.timezone import now
from django_countries.fields import Country
@@ -17,6 +16,7 @@ from pretix.base.models.items import (
ItemAddOn, SubEventItem, SubEventItemVariation,
)
from pretix.base.services.cart import CartError, CartManager
from pretix.testutils.sessions import get_cart_session_key
class CartTestMixin:
@@ -51,7 +51,7 @@ class CartTestMixin:
self.quota_all.variations.add(self.shirt_red)
self.client.get('/%s/%s/' % (self.orga.slug, self.event.slug))
self.session_key = self.client.cookies.get(settings.SESSION_COOKIE_NAME).value
self.session_key = get_cart_session_key(self.client, self.event)
class CartTest(CartTestMixin, TestCase):
@@ -97,7 +97,7 @@ class CartTest(CartTestMixin, TestCase):
def _set_session(self, key, value):
session = self.client.session
session[key] = value
session['carts'][get_cart_session_key(self.client, self.event)][key] = value
session.save()
def _enable_reverse_charge(self):
@@ -108,7 +108,7 @@ class CartTest(CartTestMixin, TestCase):
is_business=True, vat_id='ATU1234567', vat_id_validated=True,
country=Country('AT')
)
self._set_session('invoice_address_{}'.format(self.event.pk), ia.pk)
self._set_session('invoice_address', ia.pk)
return ia
def test_reverse_charge(self):
+9 -8
View File
@@ -17,6 +17,7 @@ from pretix.base.models import (
OrderPosition, Organizer, Question, Quota, Voucher,
)
from pretix.base.models.items import ItemAddOn, ItemVariation, SubEventItem
from pretix.testutils.sessions import get_cart_session_key
class CheckoutTestCase(TestCase):
@@ -40,7 +41,7 @@ class CheckoutTestCase(TestCase):
self.event.settings.set('payment_banktransfer__enabled', True)
self.client.get('/%s/%s/' % (self.orga.slug, self.event.slug))
self.session_key = self.client.cookies.get(settings.SESSION_COOKIE_NAME).value
self.session_key = get_cart_session_key(self.client, self.event)
self._set_session('email', 'admin@localhost')
self.workshopcat = ItemCategory.objects.create(name="Workshops", is_addon=True, event=self.event)
@@ -64,7 +65,7 @@ class CheckoutTestCase(TestCase):
is_business=True, vat_id='ATU1234567', vat_id_validated=True,
country=Country('AT')
)
self._set_session('invoice_address_{}'.format(self.event.pk), ia.pk)
self._set_session('invoice_address', ia.pk)
return ia
def test_empty_cart(self):
@@ -156,7 +157,7 @@ class CheckoutTestCase(TestCase):
cr1.refresh_from_db()
assert cr1.price == round_decimal(Decimal('23.00') / Decimal('1.19'))
ia = InvoiceAddress.objects.get(pk=self.client.session.get('invoice_address_{}'.format(self.event.pk)))
ia = InvoiceAddress.objects.get(pk=self.client.session['carts'][self.session_key].get('invoice_address'))
assert ia.vat_id_validated
def test_reverse_charge_enable_then_disable(self):
@@ -178,7 +179,7 @@ class CheckoutTestCase(TestCase):
cr = CartPosition.objects.get(cart_id=self.session_key)
assert cr.price == Decimal('23.00')
ia = InvoiceAddress.objects.get(pk=self.client.session.get('invoice_address_{}'.format(self.event.pk)))
ia = InvoiceAddress.objects.get(pk=self.client.session['carts'][self.session_key].get('invoice_address'))
assert not ia.vat_id_validated
def test_reverse_charge_invalid_vatid(self):
@@ -242,7 +243,7 @@ class CheckoutTestCase(TestCase):
cr1.refresh_from_db()
assert cr1.price == round_decimal(Decimal('23.00') / Decimal('1.19'))
ia = InvoiceAddress.objects.get(pk=self.client.session.get('invoice_address_{}'.format(self.event.pk)))
ia = InvoiceAddress.objects.get(pk=self.client.session['carts'][self.session_key].get('invoice_address'))
assert not ia.vat_id_validated
def test_reverse_charge_vatid_same_country(self):
@@ -273,7 +274,7 @@ class CheckoutTestCase(TestCase):
cr1.refresh_from_db()
assert cr1.price == Decimal('23.00')
ia = InvoiceAddress.objects.get(pk=self.client.session.get('invoice_address_{}'.format(self.event.pk)))
ia = InvoiceAddress.objects.get(pk=self.client.session['carts'][self.session_key].get('invoice_address'))
assert ia.vat_id_validated
def test_reverse_charge_vatid_check_invalid_country(self):
@@ -337,7 +338,7 @@ class CheckoutTestCase(TestCase):
cr1.refresh_from_db()
assert cr1.price == Decimal('23.00')
ia = InvoiceAddress.objects.get(pk=self.client.session.get('invoice_address_{}'.format(self.event.pk)))
ia = InvoiceAddress.objects.get(pk=self.client.session['carts'][self.session_key].get('invoice_address'))
assert not ia.vat_id_validated
def test_question_file_upload(self):
@@ -525,7 +526,7 @@ class CheckoutTestCase(TestCase):
def _set_session(self, key, value):
session = self.client.session
session[key] = value
session['carts'][get_cart_session_key(self.client, self.event)][key] = value
session.save()
def test_subevent(self):