forked from CGM_Public/pretix_original
Compare commits
11 Commits
dnspython2
...
guest-hand
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48c110b32b | ||
|
|
5587aebcd8 | ||
|
|
0b708067de | ||
|
|
e75dc74661 | ||
|
|
f0c5e54e34 | ||
|
|
eeb6e11934 | ||
|
|
1238165e7a | ||
|
|
bcf65603e4 | ||
|
|
c4aed04a18 | ||
|
|
8be09ee937 | ||
|
|
a1ec45daf6 |
@@ -140,7 +140,7 @@ error_messages = {
|
|||||||
'addon_min_count': _('You need to select at least %(min)s add-ons from the category %(cat)s for the '
|
'addon_min_count': _('You need to select at least %(min)s add-ons from the category %(cat)s for the '
|
||||||
'product %(base)s.'),
|
'product %(base)s.'),
|
||||||
'addon_no_multi': _('You can select every add-ons from the category %(cat)s for the product %(base)s at most once.'),
|
'addon_no_multi': _('You can select every add-ons from the category %(cat)s for the product %(base)s at most once.'),
|
||||||
'addon_only': _('One of the products you selected can only be bought as an add-on to another project.'),
|
'addon_only': _('One of the products you selected can only be bought as an add-on to another product.'),
|
||||||
'bundled_only': _('One of the products you selected can only be bought part of a bundle.'),
|
'bundled_only': _('One of the products you selected can only be bought part of a bundle.'),
|
||||||
'seat_required': _('You need to select a specific seat.'),
|
'seat_required': _('You need to select a specific seat.'),
|
||||||
'seat_invalid': _('Please select a valid seat.'),
|
'seat_invalid': _('Please select a valid seat.'),
|
||||||
|
|||||||
@@ -64,9 +64,9 @@ from pretix.base.i18n import (
|
|||||||
LazyLocaleException, get_language_without_region, language,
|
LazyLocaleException, get_language_without_region, language,
|
||||||
)
|
)
|
||||||
from pretix.base.models import (
|
from pretix.base.models import (
|
||||||
CartPosition, Device, Event, GiftCard, Item, ItemVariation, Membership,
|
CartPosition, Customer, Device, Event, GiftCard, Item, ItemVariation,
|
||||||
Order, OrderPayment, OrderPosition, Quota, Seat, SeatCategoryMapping, User,
|
Membership, Order, OrderPayment, OrderPosition, Quota, Seat,
|
||||||
Voucher,
|
SeatCategoryMapping, User, Voucher,
|
||||||
)
|
)
|
||||||
from pretix.base.models.event import SubEvent
|
from pretix.base.models.event import SubEvent
|
||||||
from pretix.base.models.orders import (
|
from pretix.base.models.orders import (
|
||||||
@@ -849,7 +849,7 @@ def _get_fees(positions: List[CartPosition], payment_requests: List[dict], addre
|
|||||||
def _create_order(event: Event, email: str, positions: List[CartPosition], now_dt: datetime,
|
def _create_order(event: Event, email: str, positions: List[CartPosition], now_dt: datetime,
|
||||||
payment_requests: List[dict], locale: str=None, address: InvoiceAddress=None,
|
payment_requests: List[dict], locale: str=None, address: InvoiceAddress=None,
|
||||||
meta_info: dict=None, sales_channel: str='web', shown_total=None,
|
meta_info: dict=None, sales_channel: str='web', shown_total=None,
|
||||||
customer=None):
|
customer=None, customer_attached=False):
|
||||||
payments = []
|
payments = []
|
||||||
sales_channel = get_all_sales_channels()[sales_channel]
|
sales_channel = get_all_sales_channels()[sales_channel]
|
||||||
|
|
||||||
@@ -931,6 +931,8 @@ def _create_order(event: Event, email: str, positions: List[CartPosition], now_d
|
|||||||
if meta_info:
|
if meta_info:
|
||||||
for msg in meta_info.get('confirm_messages', []):
|
for msg in meta_info.get('confirm_messages', []):
|
||||||
order.log_action('pretix.event.order.consent', data={'msg': msg})
|
order.log_action('pretix.event.order.consent', data={'msg': msg})
|
||||||
|
if customer and customer_attached:
|
||||||
|
customer.log_action('pretix.customer.order.attached', data={'order': order.code})
|
||||||
|
|
||||||
order_placed.send(event, order=order)
|
order_placed.send(event, order=order)
|
||||||
return order, payments
|
return order, payments
|
||||||
@@ -981,9 +983,6 @@ def _perform_order(event: Event, payment_requests: List[dict], position_ids: Lis
|
|||||||
if not p['pprov']:
|
if not p['pprov']:
|
||||||
raise OrderError(error_messages['internal'])
|
raise OrderError(error_messages['internal'])
|
||||||
|
|
||||||
if customer:
|
|
||||||
customer = event.organizer.customers.get(pk=customer)
|
|
||||||
|
|
||||||
if email == settings.PRETIX_EMAIL_NONE_VALUE:
|
if email == settings.PRETIX_EMAIL_NONE_VALUE:
|
||||||
email = None
|
email = None
|
||||||
|
|
||||||
@@ -995,6 +994,30 @@ def _perform_order(event: Event, payment_requests: List[dict], position_ids: Lis
|
|||||||
except InvoiceAddress.DoesNotExist:
|
except InvoiceAddress.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
customer_attached = False
|
||||||
|
if customer:
|
||||||
|
customer = event.organizer.customers.get(pk=customer)
|
||||||
|
elif event.settings.customer_accounts_link_by_email in ('attach', 'create') and email:
|
||||||
|
try:
|
||||||
|
customer = event.organizer.customers.get(email__iexact=email)
|
||||||
|
customer_attached = True
|
||||||
|
except Customer.MultipleObjectsReturned:
|
||||||
|
logger.warning(f'Multiple customer accounts found for {email}, not attaching.')
|
||||||
|
except Customer.DoesNotExist:
|
||||||
|
if event.settings.customer_accounts_link_by_email == 'create':
|
||||||
|
customer = event.organizer.customers.create(
|
||||||
|
email=email,
|
||||||
|
name_parts=addr.name_parts if addr else None,
|
||||||
|
phone=(meta_info or {}).get('contact_form_data', {}).get('phone'),
|
||||||
|
is_active=True,
|
||||||
|
is_verified=False,
|
||||||
|
locale=locale,
|
||||||
|
)
|
||||||
|
customer.set_unusable_password()
|
||||||
|
customer.save()
|
||||||
|
customer.log_action('pretix.customer.created.auto', {})
|
||||||
|
customer_attached = True
|
||||||
|
|
||||||
requires_seat = Exists(
|
requires_seat = Exists(
|
||||||
SeatCategoryMapping.objects.filter(
|
SeatCategoryMapping.objects.filter(
|
||||||
Q(product=OuterRef('item'))
|
Q(product=OuterRef('item'))
|
||||||
@@ -1018,7 +1041,7 @@ def _perform_order(event: Event, payment_requests: List[dict], position_ids: Lis
|
|||||||
locale=locale,
|
locale=locale,
|
||||||
invoice_address=addr,
|
invoice_address=addr,
|
||||||
meta_info=meta_info,
|
meta_info=meta_info,
|
||||||
customer=customer,
|
customer=customer if not customer_attached else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
lockfn = NoLockManager
|
lockfn = NoLockManager
|
||||||
@@ -1041,10 +1064,11 @@ def _perform_order(event: Event, payment_requests: List[dict], position_ids: Lis
|
|||||||
raise OrderError(error_messages['empty'])
|
raise OrderError(error_messages['empty'])
|
||||||
if len(position_ids) != len(positions):
|
if len(position_ids) != len(positions):
|
||||||
raise OrderError(error_messages['internal'])
|
raise OrderError(error_messages['internal'])
|
||||||
_check_positions(event, now_dt, positions, address=addr, sales_channel=sales_channel, customer=customer)
|
_check_positions(event, now_dt, positions, address=addr, sales_channel=sales_channel,
|
||||||
|
customer=customer if not customer_attached else None)
|
||||||
order, payment_objs = _create_order(event, email, positions, now_dt, payment_requests,
|
order, payment_objs = _create_order(event, email, positions, now_dt, payment_requests,
|
||||||
locale=locale, address=addr, meta_info=meta_info, sales_channel=sales_channel,
|
locale=locale, address=addr, meta_info=meta_info, sales_channel=sales_channel,
|
||||||
shown_total=shown_total, customer=customer)
|
shown_total=shown_total, customer=customer, customer_attached=customer_attached)
|
||||||
try:
|
try:
|
||||||
for p in payment_objs:
|
for p in payment_objs:
|
||||||
if p.provider == 'free':
|
if p.provider == 'free':
|
||||||
|
|||||||
@@ -159,13 +159,37 @@ DEFAULTS = {
|
|||||||
},
|
},
|
||||||
'customer_accounts_link_by_email': {
|
'customer_accounts_link_by_email': {
|
||||||
'default': 'False',
|
'default': 'False',
|
||||||
'type': bool,
|
'type': str,
|
||||||
'form_class': forms.BooleanField,
|
'form_class': forms.ChoiceField,
|
||||||
'serializer_class': serializers.BooleanField,
|
'serializer_class': serializers.ChoiceField,
|
||||||
|
'serializer_kwargs': dict(
|
||||||
|
choices=(
|
||||||
|
('False', _('Keep guest orders separate and do not link them to customer accounts.')),
|
||||||
|
('True', _('Do not attach guest orders to customer accounts, but show them in customer '
|
||||||
|
'accounts with a verified matching email address.')),
|
||||||
|
('attach', _('Attach guest orders to existing customer accounts with a matching email address if such '
|
||||||
|
'an account exists.')),
|
||||||
|
('create', _('Attach guest orders to existing customer accounts with a matching email and '
|
||||||
|
'automatically create a customer account for all others.')),
|
||||||
|
('forbidden', _('Do not allow guest orders.')),
|
||||||
|
),
|
||||||
|
),
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Match orders based on email address"),
|
label=_("Guest order handling"),
|
||||||
help_text=_("This will allow registered customers to access orders made with the same email address, even if the customer "
|
widget=forms.RadioSelect,
|
||||||
"was not logged in during the purchase.")
|
choices=(
|
||||||
|
('False', _('Keep guest orders separate and do not link them to customer accounts.')),
|
||||||
|
('True', _('Do not attach guest orders to customer accounts, but show them in customer '
|
||||||
|
'accounts with a verified matching email address.')),
|
||||||
|
('attach', _('Attach guest orders to existing customer accounts with a matching email address if '
|
||||||
|
'such an account exists.')),
|
||||||
|
('create', _('Attach guest orders to existing customer accounts with a matching email address and '
|
||||||
|
'automatically create a customer account for all others.')),
|
||||||
|
('forbidden', _('Do not allow guest orders.')),
|
||||||
|
),
|
||||||
|
help_text=_('Please be aware that creating customer accounts without a users consent might not be a '
|
||||||
|
'good idea for privacy reasons in some jurisdictions or situations. We always recommend to '
|
||||||
|
'let the user choose if they want an account.'),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
'max_items_per_order': {
|
'max_items_per_order': {
|
||||||
|
|||||||
@@ -331,7 +331,10 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
|
|||||||
'pretix.membershiptype.changed': _('The membership type has been changed.'),
|
'pretix.membershiptype.changed': _('The membership type has been changed.'),
|
||||||
'pretix.membershiptype.deleted': _('The membership type has been deleted.'),
|
'pretix.membershiptype.deleted': _('The membership type has been deleted.'),
|
||||||
'pretix.customer.created': _('The account has been created.'),
|
'pretix.customer.created': _('The account has been created.'),
|
||||||
|
'pretix.customer.created.auto': _('The account has been automatically created during an order.'),
|
||||||
|
'pretix.customer.claimed': _('The automatically created account has been claimed by the customer.'),
|
||||||
'pretix.customer.changed': _('The account has been changed.'),
|
'pretix.customer.changed': _('The account has been changed.'),
|
||||||
|
'pretix.customer.order.attached': _('An order was automatically attached to the customer.'),
|
||||||
'pretix.customer.membership.created': _('A membership for this account has been added.'),
|
'pretix.customer.membership.created': _('A membership for this account has been added.'),
|
||||||
'pretix.customer.membership.changed': _('A membership of this account has been changed.'),
|
'pretix.customer.membership.changed': _('A membership of this account has been changed.'),
|
||||||
'pretix.customer.membership.deleted': _('A membership of this account has been deleted.'),
|
'pretix.customer.membership.deleted': _('A membership of this account has been deleted.'),
|
||||||
|
|||||||
@@ -79,9 +79,9 @@
|
|||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if not c.is_verified %}<strike>{% endif %}
|
{% if not c.is_verified %}<span class="text-muted">{% endif %}
|
||||||
{{ c.email|default_if_none:"" }}
|
{{ c.email|default_if_none:"" }}
|
||||||
{% if not c.is_verified %}</strike>{% endif %}
|
{% if not c.is_verified %}</span>{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ c.name }}</td>
|
<td>{{ c.name }}</td>
|
||||||
<td>{% if c.external_identifier %}{{ c.external_identifier }}{% endif %}</td>
|
<td>{% if c.external_identifier %}{{ c.external_identifier }}{% endif %}</td>
|
||||||
|
|||||||
@@ -353,6 +353,12 @@ class Recover(TemplateView):
|
|||||||
user.save()
|
user.save()
|
||||||
messages.success(request, _('You can now login using your new password.'))
|
messages.success(request, _('You can now login using your new password.'))
|
||||||
user.log_action('pretix.control.auth.user.forgot_password.recovered')
|
user.log_action('pretix.control.auth.user.forgot_password.recovered')
|
||||||
|
|
||||||
|
has_redis = settings.HAS_REDIS
|
||||||
|
if has_redis:
|
||||||
|
from django_redis import get_redis_connection
|
||||||
|
rc = get_redis_connection("redis")
|
||||||
|
rc.delete('pretix_pwreset_%s' % user.id)
|
||||||
return redirect('control:auth.login')
|
return redirect('control:auth.login')
|
||||||
else:
|
else:
|
||||||
return self.get(request, *args, **kwargs)
|
return self.get(request, *args, **kwargs)
|
||||||
|
|||||||
@@ -2215,9 +2215,9 @@ class CustomerDetailView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMi
|
|||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
q = Q(customer=self.customer)
|
q = Q(customer=self.customer)
|
||||||
if self.request.organizer.settings.customer_accounts_link_by_email and self.customer.email:
|
if self.request.organizer.settings.customer_accounts_link_by_email == 'True' and self.customer.email:
|
||||||
# This is safe because we only let customers with verified emails log in
|
# This is safe because we only let customers with verified emails log in
|
||||||
q |= Q(email__iexact=self.customer.email)
|
q |= Q(customer__isnull=True, email__iexact=self.customer.email)
|
||||||
qs = Order.objects.filter(
|
qs = Order.objects.filter(
|
||||||
q
|
q
|
||||||
).select_related('event').order_by('-datetime')
|
).select_related('event').order_by('-datetime')
|
||||||
|
|||||||
@@ -698,7 +698,7 @@ class PaypalMethod(BasePaymentProvider):
|
|||||||
except ReferencedPayPalObject.MultipleObjectsReturned:
|
except ReferencedPayPalObject.MultipleObjectsReturned:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if capture.status == 'PENDING':
|
if capture.status != 'COMPLETED':
|
||||||
messages.warning(request, _('PayPal has not yet approved the payment. We will inform you as '
|
messages.warning(request, _('PayPal has not yet approved the payment. We will inform you as '
|
||||||
'soon as the payment completed.'))
|
'soon as the payment completed.'))
|
||||||
payment.info = json.dumps(pp_captured_order.dict())
|
payment.info = json.dumps(pp_captured_order.dict())
|
||||||
|
|||||||
@@ -73,12 +73,21 @@ var pretixpaypal = {
|
|||||||
pretixpaypal.locale = this.guessLocale();
|
pretixpaypal.locale = this.guessLocale();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no payment option is selected, and we're not on the paypage (which doesn't have a continue button),
|
$("input[name=payment][value^='paypal']").change(function () {
|
||||||
// disable the continue button
|
if (pretixpaypal.paypal !== null) {
|
||||||
if (!pretixpaypal.paypage) {
|
pretixpaypal.renderButton($(this).val());
|
||||||
if (!pretixpaypal.continue_button[0].form.elements['payment'].value) {
|
} else {
|
||||||
pretixpaypal.continue_button.prop("disabled", true);
|
pretixpaypal.continue_button.prop("disabled", true);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("input[name=payment]").not("[value^='paypal']").change(function () {
|
||||||
|
pretixpaypal.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
// If paypal is pre-selected, we must disable the continue button and handle it after SDK is loaded
|
||||||
|
if ($("input[name=payment][value^='paypal']").is(':checked')) {
|
||||||
|
pretixpaypal.continue_button.prop("disabled", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We are setting the cogwheel already here, as the renderAPM() method might take some time to get loaded.
|
// We are setting the cogwheel already here, as the renderAPM() method might take some time to get loaded.
|
||||||
@@ -139,14 +148,6 @@ var pretixpaypal = {
|
|||||||
pretixpaypal.renderAPMs();
|
pretixpaypal.renderAPMs();
|
||||||
}
|
}
|
||||||
|
|
||||||
$("input[name=payment][value^='paypal']").change(function () {
|
|
||||||
pretixpaypal.renderButton($(this).val());
|
|
||||||
});
|
|
||||||
|
|
||||||
$("input[name=payment]").not("[value^='paypal']").change(function () {
|
|
||||||
pretixpaypal.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
if ($("input[name=payment][value^='paypal']").is(':checked')) {
|
if ($("input[name=payment][value^='paypal']").is(':checked')) {
|
||||||
pretixpaypal.renderButton($("input[name=payment][value^='paypal']:checked").val());
|
pretixpaypal.renderButton($("input[name=payment][value^='paypal']:checked").val());
|
||||||
} else if ($(".payment-redo-form").length) {
|
} else if ($(".payment-redo-form").length) {
|
||||||
@@ -162,8 +163,8 @@ var pretixpaypal = {
|
|||||||
$('#paypal-button-container').empty()
|
$('#paypal-button-container').empty()
|
||||||
pretixpaypal.continue_button.text(gettext('Continue'));
|
pretixpaypal.continue_button.text(gettext('Continue'));
|
||||||
pretixpaypal.continue_button.show();
|
pretixpaypal.continue_button.show();
|
||||||
pretixpaypal.continue_button.prop("disabled", false);
|
|
||||||
}
|
}
|
||||||
|
pretixpaypal.continue_button.prop("disabled", false);
|
||||||
},
|
},
|
||||||
|
|
||||||
renderButton: function (method) {
|
renderButton: function (method) {
|
||||||
@@ -321,6 +322,15 @@ var pretixpaypal = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
$(function () {
|
$(function () {
|
||||||
|
// This script is always loaded if paypal is enabled as a payment method, regardless of
|
||||||
|
// whether it is available (it could e.g. be hidden or limited to certain countries).
|
||||||
|
// We do not want to unnecessarily load the sdk.
|
||||||
|
// If no paypal/paypal_apm payment option is present and we are not on
|
||||||
|
// the (APM) PayView, then we do not need the SDK.
|
||||||
|
if (!$("input[name=payment][value^='paypal']").length && !$('#paypal-button-container').data('paypage')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
pretixpaypal.load();
|
pretixpaypal.load();
|
||||||
|
|
||||||
(async() => {
|
(async() => {
|
||||||
|
|||||||
@@ -426,7 +426,10 @@ def webhook(request, *args, **kwargs):
|
|||||||
if not payment:
|
if not payment:
|
||||||
return HttpResponse('Payment not found', status=200)
|
return HttpResponse('Payment not found', status=200)
|
||||||
|
|
||||||
payment.order.log_action('pretix.plugins.paypal.event', data=event_json)
|
payment.order.log_action('pretix.plugins.paypal.event', data={
|
||||||
|
**event_json,
|
||||||
|
'_order_state': sale.dict(),
|
||||||
|
})
|
||||||
|
|
||||||
if payment.state == OrderPayment.PAYMENT_STATE_CONFIRMED and sale['status'] in ('PARTIALLY_REFUNDED', 'REFUNDED', 'COMPLETED'):
|
if payment.state == OrderPayment.PAYMENT_STATE_CONFIRMED and sale['status'] in ('PARTIALLY_REFUNDED', 'REFUNDED', 'COMPLETED'):
|
||||||
if event_json['resource_type'] == 'refund':
|
if event_json['resource_type'] == 'refund':
|
||||||
@@ -470,10 +473,27 @@ def webhook(request, *args, **kwargs):
|
|||||||
elif payment.state in (OrderPayment.PAYMENT_STATE_PENDING, OrderPayment.PAYMENT_STATE_CREATED,
|
elif payment.state in (OrderPayment.PAYMENT_STATE_PENDING, OrderPayment.PAYMENT_STATE_CREATED,
|
||||||
OrderPayment.PAYMENT_STATE_CANCELED, OrderPayment.PAYMENT_STATE_FAILED) \
|
OrderPayment.PAYMENT_STATE_CANCELED, OrderPayment.PAYMENT_STATE_FAILED) \
|
||||||
and sale['status'] == 'COMPLETED':
|
and sale['status'] == 'COMPLETED':
|
||||||
try:
|
any_captures = False
|
||||||
payment.confirm()
|
all_captures_completed = True
|
||||||
except Quota.QuotaExceededException:
|
for purchaseunit in sale['purchase_units']:
|
||||||
pass
|
for capture in purchaseunit['payments']['captures']:
|
||||||
|
try:
|
||||||
|
ReferencedPayPalObject.objects.get_or_create(order=payment.order, payment=payment,
|
||||||
|
reference=capture['id'])
|
||||||
|
except ReferencedPayPalObject.MultipleObjectsReturned:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if capture['status'] not in ('COMPLETED', 'REFUNDED', 'PARTIALLY_REFUNDED'):
|
||||||
|
all_captures_completed = False
|
||||||
|
else:
|
||||||
|
any_captures = True
|
||||||
|
if any_captures and all_captures_completed:
|
||||||
|
try:
|
||||||
|
payment.info = json.dumps(sale.dict())
|
||||||
|
payment.save(update_fields=['info'])
|
||||||
|
payment.confirm()
|
||||||
|
except Quota.QuotaExceededException:
|
||||||
|
pass
|
||||||
|
|
||||||
return HttpResponse(status=200)
|
return HttpResponse(status=200)
|
||||||
|
|
||||||
|
|||||||
@@ -269,7 +269,7 @@ class CustomerStep(CartMixin, TemplateFlowStep):
|
|||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def guest_allowed(self):
|
def guest_allowed(self):
|
||||||
return not any(
|
return self.request.event.settings.customer_accounts_link_by_email != 'forbidden' and not any(
|
||||||
p.item.require_membership or
|
p.item.require_membership or
|
||||||
(p.variation and p.variation.require_membership) or
|
(p.variation and p.variation.require_membership) or
|
||||||
p.item.grant_membership_type_id
|
p.item.grant_membership_type_id
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ class RegistrationForm(forms.Form):
|
|||||||
self.standalone = kwargs.pop('standalone')
|
self.standalone = kwargs.pop('standalone')
|
||||||
self.signer = signing.TimestampSigner(salt=f'customer-registration-captcha-{get_client_ip(request)}')
|
self.signer = signing.TimestampSigner(salt=f'customer-registration-captcha-{get_client_ip(request)}')
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
self.instance = None
|
||||||
|
|
||||||
event = getattr(request, "event", None)
|
event = getattr(request, "event", None)
|
||||||
if event and event.settings.order_phone_asked:
|
if event and event.settings.order_phone_asked:
|
||||||
@@ -214,14 +215,17 @@ class RegistrationForm(forms.Form):
|
|||||||
|
|
||||||
if email is not None:
|
if email is not None:
|
||||||
try:
|
try:
|
||||||
self.request.organizer.customers.get(email=email.lower())
|
existing = self.request.organizer.customers.get(email=email.lower())
|
||||||
except Customer.DoesNotExist:
|
except Customer.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
raise forms.ValidationError(
|
if not existing.is_active or existing.is_verified or existing.has_usable_password():
|
||||||
{'email': self.error_messages['duplicate']},
|
raise forms.ValidationError(
|
||||||
code='duplicate',
|
{'email': self.error_messages['duplicate']},
|
||||||
)
|
code='duplicate',
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.instance = existing
|
||||||
|
|
||||||
if self.standalone:
|
if self.standalone:
|
||||||
expect = -1
|
expect = -1
|
||||||
@@ -256,19 +260,29 @@ class RegistrationForm(forms.Form):
|
|||||||
return self.cleaned_data
|
return self.cleaned_data
|
||||||
|
|
||||||
def create(self):
|
def create(self):
|
||||||
customer = self.request.organizer.customers.create(
|
if self.instance:
|
||||||
email=self.cleaned_data['email'],
|
self.instance.name_parts = self.cleaned_data['name_parts']
|
||||||
name_parts=self.cleaned_data['name_parts'],
|
if self.cleaned_data.get('phone'):
|
||||||
phone=self.cleaned_data.get('phone'),
|
self.instance.phone = self.cleaned_data.get('phone')
|
||||||
is_active=True,
|
self.instance.locale = get_language_without_region()
|
||||||
is_verified=False,
|
self.instance.save()
|
||||||
locale=get_language_without_region(),
|
self.instance.log_action('pretix.customer.claimed', {})
|
||||||
)
|
self.instance.send_activation_mail()
|
||||||
customer.set_unusable_password()
|
return self.instance
|
||||||
customer.save()
|
else:
|
||||||
customer.log_action('pretix.customer.created', {})
|
customer = self.request.organizer.customers.create(
|
||||||
customer.send_activation_mail()
|
email=self.cleaned_data['email'],
|
||||||
return customer
|
name_parts=self.cleaned_data['name_parts'],
|
||||||
|
phone=self.cleaned_data.get('phone'),
|
||||||
|
is_active=True,
|
||||||
|
is_verified=False,
|
||||||
|
locale=get_language_without_region(),
|
||||||
|
)
|
||||||
|
customer.set_unusable_password()
|
||||||
|
customer.save()
|
||||||
|
customer.log_action('pretix.customer.created', {})
|
||||||
|
customer.send_activation_mail()
|
||||||
|
return customer
|
||||||
|
|
||||||
|
|
||||||
class SetPasswordForm(forms.Form):
|
class SetPasswordForm(forms.Form):
|
||||||
|
|||||||
@@ -352,9 +352,9 @@ class ProfileView(CustomerRequiredMixin, ListView):
|
|||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
q = Q(customer=self.request.customer)
|
q = Q(customer=self.request.customer)
|
||||||
if self.request.organizer.settings.customer_accounts_link_by_email and self.request.customer.email:
|
if self.request.organizer.settings.customer_accounts_link_by_email == 'True' and self.request.customer.email:
|
||||||
# This is safe because we only let customers with verified emails log in
|
# This is safe because we only let customers with verified emails log in
|
||||||
q |= Q(email__iexact=self.request.customer.email)
|
q |= Q(customer__isnull=True, email__iexact=self.request.customer.email)
|
||||||
qs = Order.objects.filter(
|
qs = Order.objects.filter(
|
||||||
q
|
q
|
||||||
).prefetch_related(
|
).prefetch_related(
|
||||||
|
|||||||
@@ -243,6 +243,7 @@ class WidgetAPIProductList(EventListMixin, View):
|
|||||||
voucher=self.voucher,
|
voucher=self.voucher,
|
||||||
channel=self.request.sales_channel.identifier,
|
channel=self.request.sales_channel.identifier,
|
||||||
base_qs=qs,
|
base_qs=qs,
|
||||||
|
require_seat=None,
|
||||||
memberships=(
|
memberships=(
|
||||||
self.request.customer.usable_memberships(
|
self.request.customer.usable_memberships(
|
||||||
for_event=self.subevent or self.request.event,
|
for_event=self.subevent or self.request.event,
|
||||||
@@ -252,7 +253,7 @@ class WidgetAPIProductList(EventListMixin, View):
|
|||||||
)
|
)
|
||||||
|
|
||||||
grps = []
|
grps = []
|
||||||
for cat, g in item_group_by_category(items):
|
for cat, g in item_group_by_category([i for i in items if not i.requires_seat]):
|
||||||
grps.append({
|
grps.append({
|
||||||
'id': cat.pk if cat else None,
|
'id': cat.pk if cat else None,
|
||||||
'name': str(cat.name) if cat else None,
|
'name': str(cat.name) if cat else None,
|
||||||
@@ -312,7 +313,7 @@ class WidgetAPIProductList(EventListMixin, View):
|
|||||||
} for item in g
|
} for item in g
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
return grps, display_add_to_cart, len(items)
|
return grps, display_add_to_cart, len(items), items
|
||||||
|
|
||||||
def post_process(self, data):
|
def post_process(self, data):
|
||||||
data['poweredby'] = get_powered_by(self.request, safelink=False)
|
data['poweredby'] = get_powered_by(self.request, safelink=False)
|
||||||
@@ -711,14 +712,30 @@ class WidgetAPIProductList(EventListMixin, View):
|
|||||||
fail = True
|
fail = True
|
||||||
|
|
||||||
if not fail and (ev.presale_is_running or request.event.settings.show_items_outside_presale_period):
|
if not fail and (ev.presale_is_running or request.event.settings.show_items_outside_presale_period):
|
||||||
data['items_by_category'], data['display_add_to_cart'], data['itemnum'] = self._get_items()
|
data['items_by_category'], data['display_add_to_cart'], data['itemnum'], items = self._get_items()
|
||||||
data['display_add_to_cart'] = data['display_add_to_cart'] and ev.presale_is_running
|
data['display_add_to_cart'] = data['display_add_to_cart'] and ev.presale_is_running
|
||||||
else:
|
else:
|
||||||
|
items = []
|
||||||
data['items_by_category'] = []
|
data['items_by_category'] = []
|
||||||
data['display_add_to_cart'] = False
|
data['display_add_to_cart'] = False
|
||||||
data['itemnum'] = 0
|
data['itemnum'] = 0
|
||||||
|
|
||||||
data['has_seating_plan'] = ev.seating_plan is not None
|
data['has_seating_plan'] = ev.seating_plan is not None
|
||||||
|
data['has_seating_plan_waitinglist'] = False
|
||||||
|
if request.event.settings.waiting_list_enabled and ev.presale_is_running:
|
||||||
|
for i in items:
|
||||||
|
if not i.allow_waitinglist or not i.requires_seat:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if i.has_variations:
|
||||||
|
for v in i.available_variations:
|
||||||
|
if v.cached_availability[0] != Quota.AVAILABILITY_OK:
|
||||||
|
data['has_seating_plan_waitinglist'] = True
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
if i.cached_availability[0] != Quota.AVAILABILITY_OK:
|
||||||
|
data['has_seating_plan_waitinglist'] = True
|
||||||
|
break
|
||||||
|
|
||||||
vouchers_exist = self.request.event.get_cache().get('vouchers_exist')
|
vouchers_exist = self.request.event.get_cache().get('vouchers_exist')
|
||||||
if vouchers_exist is None:
|
if vouchers_exist is None:
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ DATABASES = {
|
|||||||
'HOST': config.get('database', 'host', fallback=''),
|
'HOST': config.get('database', 'host', fallback=''),
|
||||||
'PORT': config.get('database', 'port', fallback=''),
|
'PORT': config.get('database', 'port', fallback=''),
|
||||||
'CONN_MAX_AGE': 0 if db_backend == 'sqlite3' else 120,
|
'CONN_MAX_AGE': 0 if db_backend == 'sqlite3' else 120,
|
||||||
|
'CONN_HEALTH_CHECKS': db_backend != 'sqlite3', # Will only be used from Django 4.1 onwards
|
||||||
'OPTIONS': db_options,
|
'OPTIONS': db_options,
|
||||||
'TEST': {
|
'TEST': {
|
||||||
'CHARSET': 'utf8mb4',
|
'CHARSET': 'utf8mb4',
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ var strings = {
|
|||||||
'next_week': django.pgettext('widget', 'Next week'),
|
'next_week': django.pgettext('widget', 'Next week'),
|
||||||
'previous_week': django.pgettext('widget', 'Previous week'),
|
'previous_week': django.pgettext('widget', 'Previous week'),
|
||||||
'show_seating': django.pgettext('widget', 'Open seat selection'),
|
'show_seating': django.pgettext('widget', 'Open seat selection'),
|
||||||
|
'seating_plan_waiting_list': django.pgettext('widget', 'Some or all ticket categories are currently sold out. If you want, you can add yourself to the waiting list. We will then notify if seats are available again.'),
|
||||||
'load_more': django.pgettext('widget', 'Load more'),
|
'load_more': django.pgettext('widget', 'Load more'),
|
||||||
'days': {
|
'days': {
|
||||||
'MO': django.gettext('Mo'),
|
'MO': django.gettext('Mo'),
|
||||||
@@ -793,6 +794,17 @@ Vue.component('pretix-widget-event-form', {
|
|||||||
+ strings['show_seating']
|
+ strings['show_seating']
|
||||||
+ '</button>'
|
+ '</button>'
|
||||||
+ '</div>'
|
+ '</div>'
|
||||||
|
+ '<div class="pretix-widget-seating-waitinglist" v-if="this.$root.has_seating_plan && this.$root.has_seating_plan_waitinglist">'
|
||||||
|
+ '<div class="pretix-widget-seating-waitinglist-text">'
|
||||||
|
+ strings['seating_plan_waiting_list']
|
||||||
|
+ '</div>'
|
||||||
|
+ '<div class="pretix-widget-seating-waitinglist-button-wrap">'
|
||||||
|
+ '<button class="pretix-widget-seating-waitinglist-button" @click.prevent.stop="$root.startseating">'
|
||||||
|
+ strings['waiting_list']
|
||||||
|
+ '</button>'
|
||||||
|
+ '</div>'
|
||||||
|
+ '<div class="pretix-widget-clear"></div>'
|
||||||
|
+ '</div>'
|
||||||
+ '<category v-for="category in this.$root.categories" :category="category" :key="category.id"></category>'
|
+ '<category v-for="category in this.$root.categories" :category="category" :key="category.id"></category>'
|
||||||
+ '<div class="pretix-widget-action" v-if="$root.display_add_to_cart">'
|
+ '<div class="pretix-widget-action" v-if="$root.display_add_to_cart">'
|
||||||
+ '<button @click="$parent.buy" type="submit" :disabled="buy_disabled">{{ this.buy_label }}</button>'
|
+ '<button @click="$parent.buy" type="submit" :disabled="buy_disabled">{{ this.buy_label }}</button>'
|
||||||
@@ -812,6 +824,7 @@ Vue.component('pretix-widget-event-form', {
|
|||||||
+ '<div class="pretix-widget-voucher-button-wrap">'
|
+ '<div class="pretix-widget-voucher-button-wrap">'
|
||||||
+ '<button @click="$parent.redeem">' + strings.redeem + '</button>'
|
+ '<button @click="$parent.redeem">' + strings.redeem + '</button>'
|
||||||
+ '</div>'
|
+ '</div>'
|
||||||
|
+ '<div class="pretix-widget-clear"></div>'
|
||||||
+ '</div>'
|
+ '</div>'
|
||||||
+ '</form>'
|
+ '</form>'
|
||||||
+ '</div>'
|
+ '</div>'
|
||||||
@@ -1472,6 +1485,7 @@ var shared_root_methods = {
|
|||||||
root.cart_exists = data.cart_exists;
|
root.cart_exists = data.cart_exists;
|
||||||
root.vouchers_exist = data.vouchers_exist;
|
root.vouchers_exist = data.vouchers_exist;
|
||||||
root.has_seating_plan = data.has_seating_plan;
|
root.has_seating_plan = data.has_seating_plan;
|
||||||
|
root.has_seating_plan_waitinglist = data.has_seating_plan_waitinglist;
|
||||||
root.itemnum = data.itemnum;
|
root.itemnum = data.itemnum;
|
||||||
}
|
}
|
||||||
root.poweredby = data.poweredby;
|
root.poweredby = data.poweredby;
|
||||||
@@ -1479,7 +1493,7 @@ var shared_root_methods = {
|
|||||||
root.loading--;
|
root.loading--;
|
||||||
root.trigger_load_callback();
|
root.trigger_load_callback();
|
||||||
}
|
}
|
||||||
if (root.parent_stack.length > 0 && root.has_seating_plan && root.categories.length === 0 && !root.frame_dismissed && root.useIframe && !root.error) {
|
if (root.parent_stack.length > 0 && root.has_seating_plan && root.categories.length === 0 && !root.frame_dismissed && root.useIframe && !root.error && !root.has_seating_plan_waitinglist) {
|
||||||
// If we're on desktop and someone selects a seating-only event in a calendar, let's open it right away,
|
// If we're on desktop and someone selects a seating-only event in a calendar, let's open it right away,
|
||||||
// but only if the person didn't close it before.
|
// but only if the person didn't close it before.
|
||||||
root.startseating()
|
root.startseating()
|
||||||
@@ -1727,7 +1741,8 @@ var create_widget = function (element) {
|
|||||||
itemcount: 0,
|
itemcount: 0,
|
||||||
overlay: null,
|
overlay: null,
|
||||||
poweredby: "",
|
poweredby: "",
|
||||||
has_seating_plan: false
|
has_seating_plan: false,
|
||||||
|
has_seating_plan_waitinglist: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created: function () {
|
created: function () {
|
||||||
|
|||||||
@@ -331,6 +331,28 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pretix-widget-seating-waitinglist {
|
||||||
|
margin: 15px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pretix-widget-seating-waitinglist-text {
|
||||||
|
padding: 0 15px;
|
||||||
|
width: 75%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pretix-widget-seating-waitinglist-button-wrap {
|
||||||
|
padding: 0 15px;
|
||||||
|
width: 25%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pretix-widget-seating-waitinglist-button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.pretix-widget-item-with-picture .pretix-widget-main-item-row .pretix-widget-item-title-and-description {
|
.pretix-widget-item-with-picture .pretix-widget-main-item-row .pretix-widget-item-title-and-description {
|
||||||
margin-left: 70px;
|
margin-left: 70px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ from decimal import Decimal
|
|||||||
import pytest
|
import pytest
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django_scopes import scopes_disabled
|
from django_scopes import scopes_disabled
|
||||||
|
from paypalhttp.http_response import Result
|
||||||
|
|
||||||
from pretix.base.models import (
|
from pretix.base.models import (
|
||||||
Event, Order, OrderPayment, OrderRefund, Organizer, Team, User,
|
Event, Order, OrderPayment, OrderRefund, Organizer, Team, User,
|
||||||
@@ -262,7 +263,7 @@ def init_api(self):
|
|||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_webhook_all_good(env, client, monkeypatch):
|
def test_webhook_all_good(env, client, monkeypatch):
|
||||||
order = env[1]
|
order = env[1]
|
||||||
pp_order = get_test_order()
|
pp_order = Result(get_test_order())
|
||||||
monkeypatch.setattr("paypalcheckoutsdk.orders.OrdersGetRequest", lambda *args: pp_order)
|
monkeypatch.setattr("paypalcheckoutsdk.orders.OrdersGetRequest", lambda *args: pp_order)
|
||||||
monkeypatch.setattr("pretix.plugins.paypal2.payment.PaypalMethod.init_api", init_api)
|
monkeypatch.setattr("pretix.plugins.paypal2.payment.PaypalMethod.init_api", init_api)
|
||||||
|
|
||||||
@@ -406,7 +407,7 @@ def test_webhook_mark_paid(env, client, monkeypatch):
|
|||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
order.payments.update(state=OrderPayment.PAYMENT_STATE_PENDING)
|
order.payments.update(state=OrderPayment.PAYMENT_STATE_PENDING)
|
||||||
|
|
||||||
pp_order = get_test_order()
|
pp_order = Result(get_test_order())
|
||||||
monkeypatch.setattr("paypalcheckoutsdk.orders.OrdersGetRequest", lambda *args: pp_order)
|
monkeypatch.setattr("paypalcheckoutsdk.orders.OrdersGetRequest", lambda *args: pp_order)
|
||||||
monkeypatch.setattr("pretix.plugins.paypal2.payment.PaypalMethod.init_api", init_api)
|
monkeypatch.setattr("pretix.plugins.paypal2.payment.PaypalMethod.init_api", init_api)
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
@@ -503,8 +504,8 @@ def test_webhook_mark_paid(env, client, monkeypatch):
|
|||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_webhook_refund1(env, client, monkeypatch):
|
def test_webhook_refund1(env, client, monkeypatch):
|
||||||
order = env[1]
|
order = env[1]
|
||||||
pp_order = get_test_order()
|
pp_order = Result(get_test_order())
|
||||||
pp_refund = get_test_refund()
|
pp_refund = Result(get_test_refund())
|
||||||
|
|
||||||
monkeypatch.setattr("paypalcheckoutsdk.orders.OrdersGetRequest", lambda *args: pp_order)
|
monkeypatch.setattr("paypalcheckoutsdk.orders.OrdersGetRequest", lambda *args: pp_order)
|
||||||
monkeypatch.setattr("paypalcheckoutsdk.payments.RefundsGetRequest", lambda *args: pp_refund)
|
monkeypatch.setattr("paypalcheckoutsdk.payments.RefundsGetRequest", lambda *args: pp_refund)
|
||||||
@@ -597,8 +598,8 @@ def test_webhook_refund1(env, client, monkeypatch):
|
|||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_webhook_refund2(env, client, monkeypatch):
|
def test_webhook_refund2(env, client, monkeypatch):
|
||||||
order = env[1]
|
order = env[1]
|
||||||
pp_order = get_test_order()
|
pp_order = Result(get_test_order())
|
||||||
pp_refund = get_test_refund()
|
pp_refund = Result(get_test_refund())
|
||||||
|
|
||||||
monkeypatch.setattr("paypalcheckoutsdk.orders.OrdersGetRequest", lambda *args: pp_order)
|
monkeypatch.setattr("paypalcheckoutsdk.orders.OrdersGetRequest", lambda *args: pp_order)
|
||||||
monkeypatch.setattr("paypalcheckoutsdk.payments.RefundsGetRequest", lambda *args: pp_refund)
|
monkeypatch.setattr("paypalcheckoutsdk.payments.RefundsGetRequest", lambda *args: pp_refund)
|
||||||
|
|||||||
@@ -4315,6 +4315,56 @@ class CustomerCheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
|||||||
assert order.email == 'admin@localhost'
|
assert order.email == 'admin@localhost'
|
||||||
assert not order.customer
|
assert not order.customer
|
||||||
|
|
||||||
|
def test_guest_not_allowed(self):
|
||||||
|
self.orga.settings.customer_accounts_link_by_email = 'forbidden'
|
||||||
|
response = self.client.get('/%s/%s/checkout/start' % (self.orga.slug, self.event.slug), follow=True)
|
||||||
|
self.assertRedirects(response, '/%s/%s/checkout/customer/' % (self.orga.slug, self.event.slug),
|
||||||
|
target_status_code=200)
|
||||||
|
|
||||||
|
response = self.client.post('/%s/%s/checkout/customer/' % (self.orga.slug, self.event.slug), {
|
||||||
|
'customer_mode': 'guest'
|
||||||
|
}, follow=True)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_guest_attach(self):
|
||||||
|
self.orga.settings.customer_accounts_link_by_email = 'attach'
|
||||||
|
response = self.client.get('/%s/%s/checkout/start' % (self.orga.slug, self.event.slug), follow=True)
|
||||||
|
self.assertRedirects(response, '/%s/%s/checkout/customer/' % (self.orga.slug, self.event.slug),
|
||||||
|
target_status_code=200)
|
||||||
|
|
||||||
|
self.client.post('/%s/%s/checkout/customer/' % (self.orga.slug, self.event.slug), {
|
||||||
|
'customer_mode': 'guest'
|
||||||
|
}, follow=True)
|
||||||
|
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||||
|
'email': 'john@example.org'
|
||||||
|
}, follow=True)
|
||||||
|
|
||||||
|
order = self._finish()
|
||||||
|
assert order.email == 'john@example.org'
|
||||||
|
assert order.customer == self.customer
|
||||||
|
|
||||||
|
def test_guest_create(self):
|
||||||
|
self.orga.settings.customer_accounts_link_by_email = 'create'
|
||||||
|
response = self.client.get('/%s/%s/checkout/start' % (self.orga.slug, self.event.slug), follow=True)
|
||||||
|
self.assertRedirects(response, '/%s/%s/checkout/customer/' % (self.orga.slug, self.event.slug),
|
||||||
|
target_status_code=200)
|
||||||
|
|
||||||
|
self.client.post('/%s/%s/checkout/customer/' % (self.orga.slug, self.event.slug), {
|
||||||
|
'customer_mode': 'guest'
|
||||||
|
}, follow=True)
|
||||||
|
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||||
|
'email': 'jack@example.org'
|
||||||
|
}, follow=True)
|
||||||
|
|
||||||
|
order = self._finish()
|
||||||
|
assert order.email == 'jack@example.org'
|
||||||
|
assert order.customer
|
||||||
|
c = order.customer
|
||||||
|
assert c.email == order.email
|
||||||
|
assert not c.has_usable_password()
|
||||||
|
assert c.is_active
|
||||||
|
assert not c.is_verified
|
||||||
|
|
||||||
def test_guest_even_if_logged_in(self):
|
def test_guest_even_if_logged_in(self):
|
||||||
self.client.post('/%s/account/login' % self.orga.slug, {
|
self.client.post('/%s/account/login' % self.orga.slug, {
|
||||||
'email': 'john@example.org',
|
'email': 'john@example.org',
|
||||||
@@ -4410,6 +4460,24 @@ class CustomerCheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
|||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert b'alert-danger' in response.content
|
assert b'alert-danger' in response.content
|
||||||
|
|
||||||
|
def test_register_valid_account_previously_autocrated(self):
|
||||||
|
with scopes_disabled():
|
||||||
|
c = self.orga.customers.create(email='foo@example.com', is_active=True, is_verified=False)
|
||||||
|
c.set_unusable_password()
|
||||||
|
c.save()
|
||||||
|
response = self.client.get('/%s/%s/checkout/start' % (self.orga.slug, self.event.slug), follow=True)
|
||||||
|
self.assertRedirects(response, '/%s/%s/checkout/customer/' % (self.orga.slug, self.event.slug),
|
||||||
|
target_status_code=200)
|
||||||
|
|
||||||
|
response = self.client.post('/%s/%s/checkout/customer/' % (self.orga.slug, self.event.slug), {
|
||||||
|
'customer_mode': 'register',
|
||||||
|
'register-email': 'foo@example.com',
|
||||||
|
'register-name_parts_0': 'John Doe',
|
||||||
|
}, follow=False)
|
||||||
|
self.assertRedirects(response, '/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug),
|
||||||
|
target_status_code=200)
|
||||||
|
assert len(djmail.outbox) == 1
|
||||||
|
|
||||||
def test_register_valid(self):
|
def test_register_valid(self):
|
||||||
response = self.client.get('/%s/%s/checkout/start' % (self.orga.slug, self.event.slug), follow=True)
|
response = self.client.get('/%s/%s/checkout/start' % (self.orga.slug, self.event.slug), follow=True)
|
||||||
self.assertRedirects(response, '/%s/%s/checkout/customer/' % (self.orga.slug, self.event.slug),
|
self.assertRedirects(response, '/%s/%s/checkout/customer/' % (self.orga.slug, self.event.slug),
|
||||||
|
|||||||
@@ -124,16 +124,40 @@ def test_org_register(env, client):
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_org_register_duplicate_email(env, client):
|
def test_org_register_duplicate_email(env, client):
|
||||||
|
signer = signing.TimestampSigner(salt='customer-registration-captcha-127.0.0.1')
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
env[0].customers.create(email='john@example.org')
|
env[0].customers.create(email='john@example.org')
|
||||||
r = client.post('/bigevents/account/register', {
|
r = client.post('/bigevents/account/register', {
|
||||||
'email': 'john@example.org',
|
'email': 'john@example.org',
|
||||||
'name_parts_0': 'John Doe',
|
'name_parts_0': 'John Doe',
|
||||||
})
|
'challenge': signer.sign('1+2'),
|
||||||
|
'response': '3',
|
||||||
|
}, REMOTE_ADDR='127.0.0.1')
|
||||||
assert b'already registered' in r.content
|
assert b'already registered' in r.content
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_org_register_duplicate_email_ignored_if_previously_autocreated(env, client):
|
||||||
|
signer = signing.TimestampSigner(salt='customer-registration-captcha-127.0.0.1')
|
||||||
|
with scopes_disabled():
|
||||||
|
c = env[0].customers.create(email='john@example.org', is_active=True, is_verified=False)
|
||||||
|
c.set_unusable_password()
|
||||||
|
c.save()
|
||||||
|
r = client.post('/bigevents/account/register', {
|
||||||
|
'email': 'john@example.org',
|
||||||
|
'name_parts_0': 'John Doe',
|
||||||
|
'challenge': signer.sign('1+2'),
|
||||||
|
'response': '3',
|
||||||
|
}, REMOTE_ADDR='127.0.0.1')
|
||||||
|
assert r.status_code == 302
|
||||||
|
assert len(djmail.outbox) == 1
|
||||||
|
with scopes_disabled():
|
||||||
|
customer = env[0].customers.get(email='john@example.org')
|
||||||
|
assert not customer.is_verified
|
||||||
|
assert customer.is_active
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_org_resetpw(env, client):
|
def test_org_resetpw(env, client):
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
@@ -479,7 +503,7 @@ def test_org_order_list(env, client):
|
|||||||
assert o2.code not in content
|
assert o2.code not in content
|
||||||
assert o3.code in content
|
assert o3.code in content
|
||||||
|
|
||||||
env[0].settings.customer_accounts_link_by_email = True
|
env[0].settings.customer_accounts_link_by_email = 'True'
|
||||||
|
|
||||||
r = client.get('/bigevents/account/')
|
r = client.get('/bigevents/account/')
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
|
|||||||
@@ -172,6 +172,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
|
|||||||
"waiting_list_enabled": False,
|
"waiting_list_enabled": False,
|
||||||
"error": None,
|
"error": None,
|
||||||
"has_seating_plan": False,
|
"has_seating_plan": False,
|
||||||
|
"has_seating_plan_waitinglist": False,
|
||||||
'poweredby': '<a href="https://pretix.eu" target="_blank" rel="noopener">ticketing powered by pretix</a>',
|
'poweredby': '<a href="https://pretix.eu" target="_blank" rel="noopener">ticketing powered by pretix</a>',
|
||||||
"items_by_category": [
|
"items_by_category": [
|
||||||
{
|
{
|
||||||
@@ -348,6 +349,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
|
|||||||
"show_variations_expanded": False,
|
"show_variations_expanded": False,
|
||||||
"display_net_prices": False,
|
"display_net_prices": False,
|
||||||
"has_seating_plan": False,
|
"has_seating_plan": False,
|
||||||
|
"has_seating_plan_waitinglist": False,
|
||||||
"vouchers_exist": True,
|
"vouchers_exist": True,
|
||||||
"waiting_list_enabled": False,
|
"waiting_list_enabled": False,
|
||||||
"error": None,
|
"error": None,
|
||||||
@@ -401,6 +403,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
|
|||||||
"display_net_prices": False,
|
"display_net_prices": False,
|
||||||
"vouchers_exist": True,
|
"vouchers_exist": True,
|
||||||
"has_seating_plan": False,
|
"has_seating_plan": False,
|
||||||
|
"has_seating_plan_waitinglist": False,
|
||||||
"waiting_list_enabled": False,
|
"waiting_list_enabled": False,
|
||||||
"error": None,
|
"error": None,
|
||||||
'poweredby': '<a href="https://pretix.eu" target="_blank" rel="noopener">ticketing powered by pretix</a>',
|
'poweredby': '<a href="https://pretix.eu" target="_blank" rel="noopener">ticketing powered by pretix</a>',
|
||||||
@@ -469,6 +472,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
|
|||||||
"show_variations_expanded": False,
|
"show_variations_expanded": False,
|
||||||
"display_net_prices": False,
|
"display_net_prices": False,
|
||||||
"has_seating_plan": False,
|
"has_seating_plan": False,
|
||||||
|
"has_seating_plan_waitinglist": False,
|
||||||
"vouchers_exist": True,
|
"vouchers_exist": True,
|
||||||
"waiting_list_enabled": False,
|
"waiting_list_enabled": False,
|
||||||
"error": "This voucher is expired.",
|
"error": "This voucher is expired.",
|
||||||
|
|||||||
Reference in New Issue
Block a user