forked from CGM_Public/pretix_original
* Vendor vue.js * Refactor item_group_by_category to support vouchers * Widget: Show product list * Widget: free prices * Widget: pictures and loading indicator * Widget: First iframe steps * Widget: Do not rerender iframe * Widget: Error handling * Improve widget * Widget: localization tech * Fix invoice style * Voucher attribute and waiting list * Add some iframe chrome * First step to namespaced carts * More isolation steps * More cart isolation things * More cart isolation things * Mobile stuff * Show cart on checkout pages * PayPal and Stripe support * Enable downloads * Locale handling * change text "save URL to this exact page" * Widget: voucher redemption * Widget: CSS * CSS: Responsive * Widget: CSS improvements * Widget: Add embedding code generator * Widget: Error messages and SSL check * First tests * Widget: tests * Don't use IDs in widgets * Widget: static files caching
This commit is contained in:
@@ -2,37 +2,47 @@ import mimetypes
|
||||
import os
|
||||
|
||||
from django.contrib import messages
|
||||
from django.db.models import Count, Prefetch, Q
|
||||
from django.db.models import 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.decorators import method_decorator
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.http import is_safe_url
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.decorators.clickjacking import xframe_options_exempt
|
||||
from django.views.generic import TemplateView, View
|
||||
|
||||
from pretix.base.models import (
|
||||
CartPosition, InvoiceAddress, ItemVariation, QuestionAnswer, Quota,
|
||||
SubEvent, Voucher,
|
||||
CartPosition, InvoiceAddress, QuestionAnswer, SubEvent, Voucher,
|
||||
)
|
||||
from pretix.base.services.cart import (
|
||||
CartError, add_items_to_cart, clear_cart, remove_cart_position,
|
||||
)
|
||||
from pretix.multidomain.urlreverse import eventreverse
|
||||
from pretix.presale.views import EventViewMixin
|
||||
from pretix.presale.views import (
|
||||
EventViewMixin, allow_cors_if_namespaced, allow_frame_if_namespaced,
|
||||
iframe_entry_view_wrapper,
|
||||
)
|
||||
from pretix.presale.views.async import AsyncAction
|
||||
from pretix.presale.views.event import item_group_by_category
|
||||
from pretix.presale.views.event import (
|
||||
get_grouped_items, item_group_by_category,
|
||||
)
|
||||
from pretix.presale.views.robots import NoSearchIndexViewMixin
|
||||
|
||||
|
||||
class CartActionMixin:
|
||||
|
||||
def get_next_url(self):
|
||||
if "next" in self.request.GET and '://' not in self.request.GET.get('next'):
|
||||
if "next" in self.request.GET and is_safe_url(self.request.GET.get("next")):
|
||||
return self.request.GET.get('next')
|
||||
else:
|
||||
return eventreverse(self.request.event, 'presale:event.index')
|
||||
kwargs = {}
|
||||
if 'cart_namespace' in self.kwargs:
|
||||
kwargs['cart_namespace'] = self.kwargs['cart_namespace']
|
||||
return eventreverse(self.request.event, 'presale:event.index', kwargs=kwargs)
|
||||
|
||||
def get_success_url(self, value=None):
|
||||
return self.get_next_url()
|
||||
@@ -129,25 +139,54 @@ 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 generate_cart_id(prefix=''):
|
||||
while True:
|
||||
new_id = prefix + get_random_string(length=32 - len(prefix))
|
||||
if not CartPosition.objects.filter(cart_id=new_id).exists():
|
||||
return new_id
|
||||
|
||||
|
||||
def create_empty_cart_id(request, replace_current=True):
|
||||
session_keyname = 'current_cart_event_{}'.format(request.event.pk)
|
||||
prefix = ''
|
||||
if request.resolver_match and request.resolver_match.kwargs.get('cart_namespace'):
|
||||
session_keyname += '_' + request.resolver_match.kwargs.get('cart_namespace')
|
||||
prefix = request.resolver_match.kwargs.get('cart_namespace')
|
||||
|
||||
if 'carts' not in request.session:
|
||||
request.session['carts'] = {}
|
||||
|
||||
new_id = generate_cart_id(prefix=prefix)
|
||||
request.session['carts'][new_id] = {}
|
||||
|
||||
if replace_current:
|
||||
current_id = request.session.get(session_keyname)
|
||||
if current_id and current_id in request.session.get('carts', {}):
|
||||
del request.session['carts'][current_id]
|
||||
del request.session[session_keyname]
|
||||
request.session[session_keyname] = new_id
|
||||
return new_id
|
||||
|
||||
|
||||
def get_or_create_cart_id(request):
|
||||
current_id = request.session.get('current_cart_event_{}'.format(request.event.pk))
|
||||
session_keyname = 'current_cart_event_{}'.format(request.event.pk)
|
||||
prefix = ''
|
||||
if request.resolver_match and request.resolver_match.kwargs.get('cart_namespace'):
|
||||
session_keyname += '_' + request.resolver_match.kwargs.get('cart_namespace')
|
||||
prefix = request.resolver_match.kwargs.get('cart_namespace')
|
||||
|
||||
current_id = request.session.get(session_keyname)
|
||||
if current_id and current_id in request.session.get('carts', {}):
|
||||
return current_id
|
||||
else:
|
||||
cart_data = {}
|
||||
|
||||
while True:
|
||||
new_id = get_random_string(length=32)
|
||||
if not CartPosition.objects.filter(cart_id=new_id).exists():
|
||||
break
|
||||
if prefix and 'take_cart_id' in request.GET:
|
||||
if CartPosition.objects.filter(event=request.event, cart_id=request.GET.get('take_cart_id')).exists():
|
||||
new_id = request.GET.get('take_cart_id')
|
||||
else:
|
||||
new_id = generate_cart_id(prefix=prefix)
|
||||
else:
|
||||
new_id = generate_cart_id(prefix=prefix)
|
||||
|
||||
# Migrate legacy data
|
||||
# TODO: This is for the upgrade 1.7→1.8. We should remove this around April 2018
|
||||
@@ -165,8 +204,9 @@ def get_or_create_cart_id(request):
|
||||
|
||||
if 'carts' not in request.session:
|
||||
request.session['carts'] = {}
|
||||
request.session['carts'][new_id] = cart_data
|
||||
request.session['current_cart_event_{}'.format(request.event.pk)] = new_id
|
||||
if new_id not in request.session['carts']:
|
||||
request.session['carts'][new_id] = cart_data
|
||||
request.session[session_keyname] = new_id
|
||||
return new_id
|
||||
|
||||
|
||||
@@ -176,6 +216,7 @@ def cart_session(request):
|
||||
return request.session['carts'][cart_id]
|
||||
|
||||
|
||||
@method_decorator(allow_frame_if_namespaced, 'dispatch')
|
||||
class CartRemove(EventViewMixin, CartActionMixin, AsyncAction, View):
|
||||
task = remove_cart_position
|
||||
known_errortypes = ['CartError']
|
||||
@@ -198,6 +239,7 @@ class CartRemove(EventViewMixin, CartActionMixin, AsyncAction, View):
|
||||
return redirect(self.get_error_url())
|
||||
|
||||
|
||||
@method_decorator(allow_frame_if_namespaced, 'dispatch')
|
||||
class CartClear(EventViewMixin, CartActionMixin, AsyncAction, View):
|
||||
task = clear_cart
|
||||
known_errortypes = ['CartError']
|
||||
@@ -209,6 +251,9 @@ class CartClear(EventViewMixin, CartActionMixin, AsyncAction, View):
|
||||
return self.do(self.request.event.id, get_or_create_cart_id(self.request), translation.get_language())
|
||||
|
||||
|
||||
@method_decorator(allow_cors_if_namespaced, 'dispatch')
|
||||
@method_decorator(allow_frame_if_namespaced, 'dispatch')
|
||||
@method_decorator(iframe_entry_view_wrapper, 'dispatch')
|
||||
class CartAdd(EventViewMixin, CartActionMixin, AsyncAction, View):
|
||||
task = add_items_to_cart
|
||||
known_errortypes = ['CartError']
|
||||
@@ -216,6 +261,13 @@ class CartAdd(EventViewMixin, CartActionMixin, AsyncAction, View):
|
||||
def get_success_message(self, value):
|
||||
return _('The products have been successfully added to your cart.')
|
||||
|
||||
def _ajax_response_data(self):
|
||||
cart_id = get_or_create_cart_id(self.request)
|
||||
return {
|
||||
'cart_id': cart_id,
|
||||
'has_cart': CartPosition.objects.filter(cart_id=cart_id, event=self.request.event).exists()
|
||||
}
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
items = self._items_from_post_data()
|
||||
if items:
|
||||
@@ -230,6 +282,8 @@ class CartAdd(EventViewMixin, CartActionMixin, AsyncAction, View):
|
||||
return redirect(self.get_error_url())
|
||||
|
||||
|
||||
@method_decorator(allow_frame_if_namespaced, 'dispatch')
|
||||
@method_decorator(iframe_entry_view_wrapper, 'dispatch')
|
||||
class RedeemView(NoSearchIndexViewMixin, EventViewMixin, TemplateView):
|
||||
template_name = "pretixpresale/event/voucher.html"
|
||||
|
||||
@@ -240,90 +294,11 @@ class RedeemView(NoSearchIndexViewMixin, EventViewMixin, TemplateView):
|
||||
context['max_times'] = self.voucher.max_usages - self.voucher.redeemed
|
||||
|
||||
# Fetch all items
|
||||
items = self.request.event.items.all().filter(
|
||||
Q(active=True)
|
||||
& Q(Q(available_from__isnull=True) | Q(available_from__lte=now()))
|
||||
& Q(Q(available_until__isnull=True) | Q(available_until__gte=now()))
|
||||
& ~Q(category__is_addon=True)
|
||||
)
|
||||
items, display_add_to_cart = get_grouped_items(self.request.event, self.subevent,
|
||||
voucher=self.voucher)
|
||||
|
||||
vouchq = Q(hide_without_voucher=False)
|
||||
|
||||
if self.voucher.item_id:
|
||||
vouchq |= Q(pk=self.voucher.item_id)
|
||||
items = items.filter(pk=self.voucher.item_id)
|
||||
elif self.voucher.quota_id:
|
||||
items = items.filter(quotas__in=[self.voucher.quota_id])
|
||||
|
||||
items = items.filter(vouchq).select_related(
|
||||
'category', 'tax_rule', # for re-grouping
|
||||
).prefetch_related(
|
||||
Prefetch('quotas',
|
||||
to_attr='_subevent_quotas',
|
||||
queryset=self.request.event.quotas.filter(subevent=self.subevent)),
|
||||
Prefetch('variations', to_attr='available_variations',
|
||||
queryset=ItemVariation.objects.filter(active=True, quotas__isnull=False).prefetch_related(
|
||||
Prefetch('quotas',
|
||||
to_attr='_subevent_quotas',
|
||||
queryset=self.request.event.quotas.filter(subevent=self.subevent))
|
||||
).distinct()),
|
||||
).annotate(
|
||||
quotac=Count('quotas'),
|
||||
has_variations=Count('variations')
|
||||
).filter(
|
||||
quotac__gt=0
|
||||
).distinct().order_by('category__position', 'category_id', 'position', 'name')
|
||||
quota_cache = {}
|
||||
|
||||
if self.subevent:
|
||||
item_price_override = self.subevent.item_price_overrides
|
||||
var_price_override = self.subevent.var_price_overrides
|
||||
else:
|
||||
item_price_override = {}
|
||||
var_price_override = {}
|
||||
|
||||
for item in items:
|
||||
if self.voucher.item_id and self.voucher.variation_id:
|
||||
item.available_variations = [v for v in item.available_variations if v.pk == self.voucher.variation_id]
|
||||
|
||||
item.order_max = item.max_per_order or int(self.request.event.settings.max_items_per_order)
|
||||
|
||||
if not item.has_variations:
|
||||
item._remove = not bool(item._subevent_quotas)
|
||||
if self.voucher.allow_ignore_quota or self.voucher.block_quota:
|
||||
item.cached_availability = (Quota.AVAILABILITY_OK, 1)
|
||||
else:
|
||||
item.cached_availability = item.check_quotas(subevent=self.subevent, _cache=quota_cache)
|
||||
|
||||
price = item_price_override.get(item.pk, item.default_price)
|
||||
price = self.voucher.calculate_price(price)
|
||||
item.display_price = item.tax(price)
|
||||
else:
|
||||
item._remove = False
|
||||
for var in item.available_variations:
|
||||
if self.voucher.allow_ignore_quota or self.voucher.block_quota:
|
||||
var.cached_availability = (Quota.AVAILABILITY_OK, 1)
|
||||
else:
|
||||
var.cached_availability = list(var.check_quotas(subevent=self.subevent, _cache=quota_cache))
|
||||
|
||||
price = var_price_override.get(var.pk, var.price)
|
||||
price = self.voucher.calculate_price(price)
|
||||
var.display_price = item.tax(price)
|
||||
|
||||
item.available_variations = [
|
||||
v for v in item.available_variations if v._subevent_quotas
|
||||
]
|
||||
if self.voucher.variation_id:
|
||||
item.available_variations = [v for v in item.available_variations
|
||||
if v.pk == self.voucher.variation_id]
|
||||
if len(item.available_variations) > 0:
|
||||
item.min_price = min([v.display_price.net if self.request.event.settings.display_net_prices else
|
||||
v.display_price.gross for v in item.available_variations])
|
||||
item.max_price = max([v.display_price.net if self.request.event.settings.display_net_prices else
|
||||
v.display_price.gross for v in item.available_variations])
|
||||
|
||||
items = [item for item in items
|
||||
if (len(item.available_variations) > 0 or not item.has_variations) and not item._remove]
|
||||
# Calculate how many options the user still has. If there is only one option, we can
|
||||
# check the box right away ;)
|
||||
context['options'] = sum([(len(item.available_variations) if item.has_variations else 1)
|
||||
for item in items])
|
||||
|
||||
@@ -359,7 +334,7 @@ class RedeemView(NoSearchIndexViewMixin, EventViewMixin, TemplateView):
|
||||
except Voucher.DoesNotExist:
|
||||
err = error_messages['voucher_invalid']
|
||||
else:
|
||||
return redirect(eventreverse(request.event, 'presale:event.index'))
|
||||
return redirect(self.get_index_url())
|
||||
|
||||
if request.event.presale_start and now() < request.event.presale_start:
|
||||
err = error_messages['not_started']
|
||||
@@ -379,11 +354,12 @@ class RedeemView(NoSearchIndexViewMixin, EventViewMixin, TemplateView):
|
||||
|
||||
if err:
|
||||
messages.error(request, _(err))
|
||||
return redirect(eventreverse(request.event, 'presale:event.index'))
|
||||
return redirect(self.get_index_url())
|
||||
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
@method_decorator(xframe_options_exempt, 'dispatch')
|
||||
class AnswerDownload(EventViewMixin, View):
|
||||
def get(self, request, *args, **kwargs):
|
||||
answid = kwargs.get('answer')
|
||||
|
||||
Reference in New Issue
Block a user