forked from CGM_Public/pretix_original
Customer accounts & Memberships (#2024)
This commit is contained in:
@@ -40,6 +40,7 @@ from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import EmailValidator
|
||||
from django.db.models import F, Q
|
||||
from django.http import HttpResponseNotAllowed, JsonResponse
|
||||
from django.shortcuts import redirect
|
||||
from django.utils import translation
|
||||
@@ -50,26 +51,29 @@ from django.utils.translation import (
|
||||
from django.views.generic.base import TemplateResponseMixin
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.base.models import Order
|
||||
from pretix.base.models import Customer, Order
|
||||
from pretix.base.models.orders import InvoiceAddress, OrderPayment
|
||||
from pretix.base.models.tax import TaxedPrice, TaxRule
|
||||
from pretix.base.services.cart import (
|
||||
CartError, error_messages, get_fees, set_cart_addons, update_tax_rates,
|
||||
)
|
||||
from pretix.base.services.memberships import validate_memberships_in_order
|
||||
from pretix.base.services.orders import perform_order
|
||||
from pretix.base.signals import validate_cart_addons
|
||||
from pretix.base.templatetags.rich_text import rich_text_snippet
|
||||
from pretix.base.views.tasks import AsyncAction
|
||||
from pretix.multidomain.urlreverse import eventreverse
|
||||
from pretix.presale.forms.checkout import (
|
||||
ContactForm, InvoiceAddressForm, InvoiceNameForm,
|
||||
ContactForm, InvoiceAddressForm, InvoiceNameForm, MembershipForm,
|
||||
)
|
||||
from pretix.presale.forms.customer import AuthenticationForm, RegistrationForm
|
||||
from pretix.presale.signals import (
|
||||
checkout_all_optional, checkout_confirm_messages, checkout_flow_steps,
|
||||
contact_form_fields, contact_form_fields_overrides,
|
||||
order_meta_from_request, question_form_fields,
|
||||
question_form_fields_overrides,
|
||||
)
|
||||
from pretix.presale.utils import customer_login
|
||||
from pretix.presale.views import (
|
||||
CartMixin, get_cart, get_cart_is_free, get_cart_total,
|
||||
)
|
||||
@@ -222,6 +226,200 @@ class TemplateFlowStep(TemplateResponseMixin, BaseCheckoutFlowStep):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class CustomerStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
|
||||
priority = 45
|
||||
identifier = "customer"
|
||||
template_name = "pretixpresale/event/checkout_customer.html"
|
||||
label = pgettext_lazy('checkoutflow', 'Customer account')
|
||||
icon = 'user'
|
||||
|
||||
def is_applicable(self, request):
|
||||
return request.organizer.settings.customer_accounts
|
||||
|
||||
@cached_property
|
||||
def login_form(self):
|
||||
f = AuthenticationForm(
|
||||
data=(
|
||||
self.request.POST
|
||||
if self.request.method == "POST" and self.request.POST.get('customer_mode') == 'login'
|
||||
else None
|
||||
),
|
||||
prefix='login',
|
||||
request=self.request.event,
|
||||
)
|
||||
for field in f.fields.values():
|
||||
field._show_required = field.required
|
||||
field.required = False
|
||||
field.widget.is_required = False
|
||||
return f
|
||||
|
||||
@cached_property
|
||||
def guest_allowed(self):
|
||||
return not any(
|
||||
p.item.require_membership or
|
||||
(p.variation and p.variation.require_membership) or
|
||||
p.item.grant_membership_type_id
|
||||
for p in self.positions
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def register_form(self):
|
||||
f = RegistrationForm(
|
||||
data=(
|
||||
self.request.POST
|
||||
if self.request.method == "POST" and self.request.POST.get('customer_mode') == 'register'
|
||||
else None
|
||||
),
|
||||
prefix='register',
|
||||
request=self.request,
|
||||
)
|
||||
for field in f.fields.values():
|
||||
field._show_required = field.required
|
||||
field.required = False
|
||||
field.widget.is_required = False
|
||||
return f
|
||||
|
||||
def post(self, request):
|
||||
self.request = request
|
||||
|
||||
if request.POST.get("customer_mode") == 'login':
|
||||
if 'customer' in self.cart_session:
|
||||
return redirect(self.get_next_url(request))
|
||||
elif request.customer:
|
||||
self.cart_session['customer_mode'] = 'login'
|
||||
self.cart_session['customer'] = request.customer.pk
|
||||
return redirect(self.get_next_url(request))
|
||||
elif self.login_form.is_valid():
|
||||
customer_login(self.request, self.login_form.get_customer())
|
||||
self.cart_session['customer_mode'] = 'login'
|
||||
self.cart_session['customer'] = self.login_form.get_customer().pk
|
||||
return redirect(self.get_next_url(request))
|
||||
else:
|
||||
return self.render()
|
||||
elif request.POST.get("customer_mode") == 'register':
|
||||
if self.register_form.is_valid():
|
||||
customer = self.register_form.create()
|
||||
self.cart_session['customer_mode'] = 'login'
|
||||
self.cart_session['customer'] = customer.pk
|
||||
return redirect(self.get_next_url(request))
|
||||
else:
|
||||
return self.render()
|
||||
elif request.POST.get("customer_mode") == 'guest' and self.guest_allowed:
|
||||
self.cart_session['customer'] = None
|
||||
self.cart_session['customer_mode'] = 'guest'
|
||||
return redirect(self.get_next_url(request))
|
||||
else:
|
||||
return self.render()
|
||||
|
||||
def is_completed(self, request, warn=False):
|
||||
self.request = request
|
||||
if self.guest_allowed:
|
||||
return 'customer_mode' in self.cart_session
|
||||
else:
|
||||
return self.cart_session.get('customer_mode') == 'login'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['cart'] = self.get_cart()
|
||||
ctx['cart_session'] = self.cart_session
|
||||
ctx['login_form'] = self.login_form
|
||||
ctx['register_form'] = self.register_form
|
||||
ctx['selected'] = self.request.POST.get(
|
||||
'customer_mode',
|
||||
self.cart_session.get('customer_mode', 'login' if self.request.customer else '')
|
||||
)
|
||||
ctx['guest_allowed'] = self.guest_allowed
|
||||
|
||||
if 'customer' in self.cart_session:
|
||||
try:
|
||||
ctx['customer'] = self.request.organizer.customers.get(pk=self.cart_session.get('customer', -1))
|
||||
except Customer.DoesNotExist:
|
||||
self.cart_session['customer'] = None
|
||||
self.cart_session['customer_mode'] = None
|
||||
elif self.request.customer:
|
||||
ctx['customer'] = self.request.customer
|
||||
|
||||
return ctx
|
||||
|
||||
|
||||
class MembershipStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
|
||||
priority = 47
|
||||
identifier = "membership"
|
||||
template_name = "pretixpresale/event/checkout_membership.html"
|
||||
label = pgettext_lazy('checkoutflow', 'Membership')
|
||||
icon = 'id-card'
|
||||
|
||||
def is_applicable(self, request):
|
||||
self.request = request
|
||||
return bool(self.applicable_positions)
|
||||
|
||||
@cached_property
|
||||
def applicable_positions(self):
|
||||
return [
|
||||
p for p in self.positions
|
||||
if p.item.require_membership or (p.variation and p.variation.require_membership)
|
||||
]
|
||||
|
||||
@cached_property
|
||||
def forms(self):
|
||||
forms = []
|
||||
|
||||
memberships = list(self.cart_customer.memberships.with_usages().filter(
|
||||
Q(Q(membership_type__max_usages__isnull=True) | Q(usages__lt=F('membership_type__max_usages'))),
|
||||
).select_related('membership_type'))
|
||||
|
||||
for p in self.applicable_positions:
|
||||
form = MembershipForm(
|
||||
event=self.request.event,
|
||||
memberships=memberships,
|
||||
position=p,
|
||||
prefix=f"membership-{p.id}",
|
||||
initial={
|
||||
'membership': str(p.used_membership_id)
|
||||
},
|
||||
data=self.request.POST if self.request.method == "POST" else None,
|
||||
)
|
||||
forms.append(form)
|
||||
|
||||
return forms
|
||||
|
||||
def post(self, request):
|
||||
self.request = request
|
||||
|
||||
for f in self.forms:
|
||||
if not f.is_valid():
|
||||
messages.error(request, _('Your cart includes a product that requires an active membership to be selected.'))
|
||||
return self.render()
|
||||
|
||||
f.position.used_membership = f.cleaned_data['membership']
|
||||
|
||||
try:
|
||||
validate_memberships_in_order(self.cart_customer, self.positions, self.request.event, lock=False)
|
||||
except ValidationError as e:
|
||||
messages.error(self.request, e.message)
|
||||
self.render()
|
||||
else:
|
||||
for f in self.forms:
|
||||
f.position.save(update_fields=['used_membership'])
|
||||
|
||||
return redirect(self.get_next_url(request))
|
||||
|
||||
def is_completed(self, request, warn=False):
|
||||
self.request = request
|
||||
ok = all([p.used_membership_id for p in self.applicable_positions])
|
||||
if not ok and warn:
|
||||
messages.error(request, _('Your cart includes a product that requires an active membership to be selected.'))
|
||||
return ok
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['cart'] = self.get_cart()
|
||||
ctx['cart_session'] = self.cart_session
|
||||
ctx['forms'] = self.forms
|
||||
|
||||
return ctx
|
||||
|
||||
|
||||
class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
|
||||
priority = 40
|
||||
identifier = "addons"
|
||||
@@ -486,12 +684,14 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
|
||||
initial.update({
|
||||
k: v['initial'] for k, v in overrides.items() if 'initial' in v
|
||||
})
|
||||
if self.cart_customer:
|
||||
initial['email'] = self.cart_customer.email
|
||||
|
||||
f = ContactForm(data=self.request.POST if self.request.method == "POST" else None,
|
||||
event=self.request.event,
|
||||
request=self.request,
|
||||
initial=initial, all_optional=self.all_optional)
|
||||
if wd.get('email', '') and wd.get('fix', '') == "true":
|
||||
if wd.get('email', '') and wd.get('fix', '') == "true" or self.cart_customer:
|
||||
f.fields['email'].disabled = True
|
||||
|
||||
for overrides in override_sets:
|
||||
@@ -504,13 +704,31 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
|
||||
return f
|
||||
|
||||
def get_question_override_sets(self, cart_position):
|
||||
return [
|
||||
o = []
|
||||
if self.cart_customer:
|
||||
o.append({
|
||||
'attendee_name_parts': {
|
||||
'initial': self.cart_customer.name_parts
|
||||
}
|
||||
})
|
||||
o += [
|
||||
resp for recv, resp in question_form_fields_overrides.send(
|
||||
self.request.event,
|
||||
position=cart_position,
|
||||
request=self.request
|
||||
)
|
||||
]
|
||||
if cart_position.used_membership:
|
||||
d = {
|
||||
'initial': cart_position.used_membership.attendee_name_parts
|
||||
}
|
||||
if not cart_position.used_membership.membership_type.transferable:
|
||||
d['disabled'] = True
|
||||
o.append({
|
||||
'attendee_name_parts': d
|
||||
})
|
||||
|
||||
return o
|
||||
|
||||
@cached_property
|
||||
def eu_reverse_charge_relevant(self):
|
||||
@@ -538,6 +756,11 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
|
||||
wd_initial = {}
|
||||
initial = dict(wd_initial)
|
||||
|
||||
if self.cart_customer:
|
||||
initial.update({
|
||||
'name_parts': self.cart_customer.name_parts
|
||||
})
|
||||
|
||||
override_sets = self._contact_override_sets
|
||||
for overrides in override_sets:
|
||||
initial.update({
|
||||
@@ -852,6 +1075,7 @@ class ConfirmStep(CartMixin, AsyncAction, TemplateFlowStep):
|
||||
ctx['confirm_messages'] = self.confirm_messages
|
||||
ctx['cart_session'] = self.cart_session
|
||||
ctx['invoice_address_asked'] = self.address_asked
|
||||
ctx['customer'] = self.cart_customer
|
||||
|
||||
self.cart_session['shown_total'] = str(ctx['cart']['total'])
|
||||
|
||||
@@ -928,11 +1152,19 @@ class ConfirmStep(CartMixin, AsyncAction, TemplateFlowStep):
|
||||
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 if self.payment_provider else None,
|
||||
[p.id for p in self.positions], self.cart_session.get('email'),
|
||||
translation.get_language(), self.invoice_address.pk, meta_info,
|
||||
request.sales_channel.identifier, self.cart_session.get('gift_cards'),
|
||||
self.cart_session.get('shown_total'))
|
||||
return self.do(
|
||||
self.request.event.id,
|
||||
payment_provider=self.payment_provider.identifier if self.payment_provider else None,
|
||||
positions=[p.id for p in self.positions],
|
||||
email=self.cart_session.get('email'),
|
||||
locale=translation.get_language(),
|
||||
address=self.invoice_address.pk,
|
||||
meta_info=meta_info,
|
||||
sales_channel=request.sales_channel.identifier,
|
||||
gift_cards=self.cart_session.get('gift_cards'),
|
||||
shown_total=self.cart_session.get('shown_total'),
|
||||
customer=self.cart_session.get('customer'),
|
||||
)
|
||||
|
||||
def get_success_message(self, value):
|
||||
create_empty_cart_id(self.request)
|
||||
@@ -966,6 +1198,8 @@ class ConfirmStep(CartMixin, AsyncAction, TemplateFlowStep):
|
||||
|
||||
DEFAULT_FLOW = (
|
||||
AddOnsStep,
|
||||
CustomerStep,
|
||||
MembershipStep,
|
||||
QuestionsStep,
|
||||
PaymentStep,
|
||||
ConfirmStep
|
||||
|
||||
Reference in New Issue
Block a user