Merge branch 'new-user-concept'

This commit is contained in:
Raphael Michel
2015-09-17 22:47:56 +02:00
49 changed files with 581 additions and 787 deletions

View File

@@ -25,7 +25,6 @@ Example::
[pretix] [pretix]
instance_name=pretix.de instance_name=pretix.de
global_registration=off
url=http://localhost url=http://localhost
currency=EUR currency=EUR
cookiedomain=.pretix.de cookiedomain=.pretix.de
@@ -36,10 +35,6 @@ Example::
``instance_name`` ``instance_name``
The name of this installation. Default: ``pretix.de`` The name of this installation. Default: ``pretix.de``
``global_registration``
Whether or not this installation supports global user accounts (in addition to
event-bound accounts). Defaults to ``True``.
``url`` ``url``
The installation's full URL, without a trailing slash. The installation's full URL, without a trailing slash.

View File

@@ -30,28 +30,7 @@ Every event is managed by the **organizer**, an abstract entity running the even
Pretix is used by **users**. We want to enable global users who can just login into Pretix is used by **users**. We want to enable global users who can just login into
pretix and buy tickets for as many events as they like but at the same time it pretix and buy tickets for as many events as they like but at the same time it
should be possible to create some kind of local user to have a temporary account should be possible to order products **without** needing an user account.
just to buy tickets for one single event.
The problem is, we cannot use usernames as primary keys for our users, as we
do not want one username to be blocked forever just because of one temporary
account using it (people would have to think of a new username for every temporary
account they create). On the other hand, we can not use e-mail addresses either,
as those are not unique (imagine one person having multiple temporary accounts)
and they should not be required for temporary account (to enable anonymity).
Therefore, we split our users into two groups and use an internal **identifier**
as our primary key:
**Local users**
Local users do only exist inside the scope of one event. They are identified by
usernames, which are only valid for exactly one event. Internally, their identifier
is "{username}@{event.id}.event.pretix"
**Global users**
Global users exist everywhere in the installation of Tixl. They can buy tickets
for multiple events and they can be managers of one or more Organizers/Events.
Global users are identified by e-mail addresses.
Items and variations Items and variations

View File

@@ -121,7 +121,7 @@ class JSONExporter(BaseExporter):
{ {
'code': o.code, 'code': o.code,
'status': o.status, 'status': o.status,
'user': o.user.identifier, 'user': o.user.email,
'datetime': o.datetime, 'datetime': o.datetime,
'payment_fee': o.payment_fee, 'payment_fee': o.payment_fee,
'total': o.total, 'total': o.total,

View File

@@ -0,0 +1,158 @@
from django import forms
from django.contrib.auth import authenticate
from django.utils.translation import ugettext_lazy as _
from pretix.base.models import User
class LoginForm(forms.Form):
"""
Base class for authenticating users. Extend this to get a form that accepts
username/password logins.
"""
email = forms.EmailField(label=_("E-mail"), max_length=254)
password = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
error_messages = {
'invalid_login': _("Please enter a correct e-mail address and password."),
'inactive': _("This account is inactive.")
}
def __init__(self, request=None, *args, **kwargs):
"""
The 'request' parameter is set for custom auth use by subclasses.
The form data comes in via the standard 'data' kwarg.
"""
self.request = request
self.user_cache = None
super().__init__(*args, **kwargs)
def clean(self):
email = self.cleaned_data.get('email')
password = self.cleaned_data.get('password')
if email and password:
self.user_cache = authenticate(email=email.lower(), password=password)
if self.user_cache is None:
raise forms.ValidationError(
self.error_messages['invalid_login'],
code='invalid_login'
)
else:
self.confirm_login_allowed(self.user_cache)
return self.cleaned_data
def confirm_login_allowed(self, user):
"""
Controls whether the given User may log in. This is a policy setting,
independent of end-user authentication. This default behavior is to
allow login by active users, and reject login by inactive users.
If the given user cannot log in, this method should raise a
``forms.ValidationError``.
If the given user may log in, this method should return None.
"""
if not user.is_active:
raise forms.ValidationError(
self.error_messages['inactive'],
code='inactive',
)
def get_user(self):
return self.user_cache
class RegistrationForm(forms.Form):
error_messages = {
'duplicate_email': _("You already registered with that e-mail address, please use the login form."),
'pw_mismatch': _("Please enter the same password twice"),
}
email = forms.EmailField(
label=_('Email address'),
required=True
)
password = forms.CharField(
label=_('Password'),
widget=forms.PasswordInput,
required=True
)
password_repeat = forms.CharField(
label=_('Repeat password'),
widget=forms.PasswordInput
)
def clean(self):
password1 = self.cleaned_data.get('password')
password2 = self.cleaned_data.get('password_repeat')
if password1 and password1 != password2:
raise forms.ValidationError(
self.error_messages['pw_mismatch'],
code='pw_mismatch'
)
return self.cleaned_data
def clean_email(self):
email = self.cleaned_data['email']
if User.objects.filter(email=email).exists():
raise forms.ValidationError(
self.error_messages['duplicate_email'],
code='duplicate_email'
)
return email
class PasswordRecoverForm(forms.Form):
error_messages = {
'pw_mismatch': _("Please enter the same password twice")
}
password = forms.CharField(
label=_('Password'),
widget=forms.PasswordInput,
required=True
)
password_repeat = forms.CharField(
label=_('Repeat password'),
widget=forms.PasswordInput
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def clean(self):
password1 = self.cleaned_data.get('password')
password2 = self.cleaned_data.get('password_repeat')
if password1 and password1 != password2:
raise forms.ValidationError(
self.error_messages['pw_mismatch'],
code='pw_mismatch'
)
return self.cleaned_data
class PasswordForgotForm(forms.Form):
email = forms.EmailField(
label=_('E-mail'),
)
def __init__(self, event, *args, **kwargs):
self.event = event
super().__init__(*args, **kwargs)
def clean_email(self):
email = self.cleaned_data['email']
try:
self.cleaned_data['user'] = User.objects.get(
email=email, event__isnull=True
)
return email
except User.DoesNotExist:
raise forms.ValidationError(
_("We are unable to find a user matching the data you provided."),
code='unknown_user'
)

View File

@@ -59,7 +59,7 @@ class UserSettingsForm(forms.ModelForm):
def clean_email(self): def clean_email(self):
email = self.cleaned_data['email'] email = self.cleaned_data['email']
if User.objects.filter(Q(identifier=email) & ~Q(pk=self.instance.pk)).exists(): if User.objects.filter(Q(email=email) & ~Q(pk=self.instance.pk)).exists():
raise forms.ValidationError( raise forms.ValidationError(
self.error_messages['duplicate_identifier'], self.error_messages['duplicate_identifier'],
code='duplicate_identifier', code='duplicate_identifier',
@@ -88,6 +88,5 @@ class UserSettingsForm(forms.ModelForm):
if password1: if password1:
self.instance.set_password(password1) self.instance.set_password(password1)
self.instance.identifier = email
return self.cleaned_data return self.cleaned_data

View File

@@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import pretix.base.models
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0014_auto_20150916_1319'),
]
operations = [
migrations.AddField(
model_name='cartposition',
name='session',
field=models.CharField(max_length=255, verbose_name='Session', null=True, blank=True),
),
migrations.AddField(
model_name='order',
name='secret',
field=models.CharField(max_length=32, default=pretix.base.models.generate_secret),
),
migrations.AlterField(
model_name='user',
name='email',
field=models.EmailField(max_length=254, verbose_name='E-mail', blank=True, db_index=True, unique=True, null=True),
),
migrations.AlterUniqueTogether(
name='user',
unique_together=set([]),
),
migrations.RemoveField(
model_name='user',
name='event',
),
migrations.RemoveField(
model_name='user',
name='identifier',
),
migrations.RemoveField(
model_name='user',
name='username',
),
]

View File

@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0015_auto_20150916_2219'),
]
operations = [
migrations.AddField(
model_name='order',
name='guest_email',
field=models.EmailField(max_length=254, verbose_name='E-mail', blank=True, null=True),
),
]

View File

@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0016_order_guest_email'),
]
operations = [
migrations.AddField(
model_name='order',
name='guest_locale',
field=models.CharField(max_length=32, null=True, blank=True, verbose_name='Locale'),
),
]

View File

@@ -1,5 +1,6 @@
import copy import copy
import random import random
import string
import uuid import uuid
from datetime import datetime from datetime import datetime
from itertools import product from itertools import product
@@ -88,34 +89,17 @@ class UserManager(BaseUserManager):
model documentation to see what's so special about our user model. model documentation to see what's so special about our user model.
""" """
def create_user(self, identifier, username, password=None): def create_user(self, email, password=None, **kwargs):
user = self.model(identifier=identifier) user = self.model(email=email, **kwargs)
user.set_password(password) user.set_password(password)
user.save() user.save()
return user return user
def create_global_user(self, email, password=None, **kwargs): def create_superuser(self, email, password=None): # NOQA
user = self.model(**kwargs)
user.identifier = email
user.email = email
user.set_password(password)
user.save()
return user
def create_local_user(self, event, username, password=None, **kwargs):
user = self.model(**kwargs)
user.identifier = '%s@%s.event.pretix' % (username, event.identity)
user.username = username
user.event = event
user.set_password(password)
user.save()
return user
def create_superuser(self, identifier, password=None): # NOQA
# Not used in the software but required by Django # Not used in the software but required by Django
if password is None: if password is None:
raise Exception("You must provide a password") raise Exception("You must provide a password")
user = self.model(identifier=identifier, email=identifier) user = self.model(email=email)
user.is_staff = True user.is_staff = True
user.is_superuser = True user.is_superuser = True
user.set_password(password) user.set_password(password)
@@ -126,44 +110,8 @@ class UserManager(BaseUserManager):
class User(AbstractBaseUser, PermissionsMixin): class User(AbstractBaseUser, PermissionsMixin):
""" """
This is the user model used by pretix for authentication. This is the user model used by pretix for authentication.
Handling users is somehow complicated, as we try to have two
classes of users in one system:
(1) We want *global* users who can just login into pretix and :param email: The user's e-mail address, used for identification.
buy tickets for multiple events -- we also need those
global users for event organizers who should not need
multiple users for managing multiple events.
(2) We want *local* users who exist only in the scope of a
certain event
The hard part is to find a primary key to identify all of these
users. Letting the users choose usernames is a bad idea, as
the primary key needs to be unique and there is no reason for a
local user to block a name for all time. Using e-mail addresses
is not a good idea either, for two reasons: First, a user might
have multiple local users (so they are not unique), and second,
it should be possible to create anonymous users without having
to supply an e-mail address.
Therefore, we use an abstract "identifier" field as the primary
key. The identifier is:
(1) the e-mail address for global users. An e-mail address
is and should be required for them and global users use
their e-mail address for login.
(2) "{username}@{event.identity}.event.pretix" for local users, who
use their username to login on the event page.
The model's save() method automatically fills the identifier field
according to this scheme when it is empty. The __str__() method
returns the identifier.
:param identifier: The identifier of the user, as described above
:type identifier: str
:param username: The username, null for global users.
:type username: str
:param event: The event the user belongs to, null for global users
:type event: Event
:param email: The user's e-mail address. May be empty or null for local users
:type email: str :type email: str
:param givenname: The user's given name. May be empty or null. :param givenname: The user's given name. May be empty or null.
:type givenname: str :type givenname: str
@@ -183,24 +131,14 @@ class User(AbstractBaseUser, PermissionsMixin):
:type timezone: str :type timezone: str
""" """
USERNAME_FIELD = 'identifier' USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username'] REQUIRED_FIELDS = []
identifier = models.CharField(max_length=255, unique=True) email = models.EmailField(unique=True, db_index=True, null=True, blank=True,
username = models.CharField(max_length=120, blank=True,
null=True,
help_text=_('Letters, digits and ./+/-/_ only.'))
event = models.ForeignKey('Event', related_name="users",
null=True, blank=True,
on_delete=models.PROTECT)
email = models.EmailField(unique=False, db_index=True,
null=True, blank=True,
verbose_name=_('E-mail')) verbose_name=_('E-mail'))
givenname = models.CharField(max_length=255, blank=True, givenname = models.CharField(max_length=255, blank=True, null=True,
null=True,
verbose_name=_('Given name')) verbose_name=_('Given name'))
familyname = models.CharField(max_length=255, blank=True, familyname = models.CharField(max_length=255, blank=True, null=True,
null=True,
verbose_name=_('Family name')) verbose_name=_('Family name'))
is_active = models.BooleanField(default=True, is_active = models.BooleanField(default=True,
verbose_name=_('Is active')) verbose_name=_('Is active'))
@@ -221,32 +159,20 @@ class User(AbstractBaseUser, PermissionsMixin):
class Meta: class Meta:
verbose_name = _("User") verbose_name = _("User")
verbose_name_plural = _("Users") verbose_name_plural = _("Users")
unique_together = (("event", "username"),)
def __str__(self):
return self.identifier
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
""" self.email = self.email.lower()
Before passing the call to the default ``save()`` method, this will fill the ``identifier``
field if it is empty, according to the scheme descriped in the model docstring.
"""
if not self.identifier:
if self.event is None:
self.identifier = self.email.lower()
else:
self.identifier = "%s@%s.event.pretix" % (self.username.lower(), self.event.id)
if not self.pk:
self.identifier = self.identifier.lower()
super().save(*args, **kwargs) super().save(*args, **kwargs)
def __str__(self):
return self.email
def get_short_name(self) -> str: def get_short_name(self) -> str:
""" """
Returns the first of the following user properties that is found to exist: Returns the first of the following user properties that is found to exist:
* Given name * Given name
* Family name * Family name
* User name
* E-mail address * E-mail address
""" """
if self.givenname: if self.givenname:
@@ -254,7 +180,7 @@ class User(AbstractBaseUser, PermissionsMixin):
elif self.familyname: elif self.familyname:
return self.familyname return self.familyname
else: else:
return self.get_local_name() return self.email
def get_full_name(self) -> str: def get_full_name(self) -> str:
""" """
@@ -264,7 +190,6 @@ class User(AbstractBaseUser, PermissionsMixin):
* Given name * Given name
* Family name * Family name
* User name * User name
* E-mail address
""" """
if self.givenname and not self.familyname: if self.givenname and not self.familyname:
return self.givenname return self.givenname
@@ -276,18 +201,7 @@ class User(AbstractBaseUser, PermissionsMixin):
'given': self.givenname 'given': self.givenname
} }
else: else:
return self.get_local_name()
def get_local_name(self) -> str:
"""
Returns the username for local users and the e-mail address for global
users.
"""
if self.username:
return self.username
if self.email:
return self.email return self.email
return self.identifier # NOQA
def cachedfile_name(instance, filename): def cachedfile_name(instance, filename):
@@ -1450,6 +1364,10 @@ class Quota(Versionable):
pass pass
def generate_secret():
return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(32))
class Order(Versionable): class Order(Versionable):
""" """
An order is created when a user clicks 'buy' on his cart. It holds An order is created when a user clicks 'buy' on his cart. It holds
@@ -1524,6 +1442,15 @@ class Order(Versionable):
verbose_name=_("User"), verbose_name=_("User"),
related_name="orders" related_name="orders"
) )
guest_email = models.EmailField(
null=True, blank=True,
verbose_name=_('E-mail')
)
guest_locale = models.CharField(
null=True, blank=True, max_length=32,
verbose_name=_('Locale')
)
secret = models.CharField(max_length=32, default=generate_secret)
datetime = models.DateTimeField( datetime = models.DateTimeField(
verbose_name=_("Date") verbose_name=_("Date")
) )
@@ -1671,6 +1598,18 @@ class Order(Versionable):
return error_messages['busy'] return error_messages['busy']
return True return True
@property
def locale(self):
if self.user:
return self.user.locale
return self.guest_locale
@property
def email(self):
if self.user:
return self.user.email
return self.guest_email
class CachedTicket(models.Model): class CachedTicket(models.Model):
order = VersionedForeignKey(Order, on_delete=models.CASCADE) order = VersionedForeignKey(Order, on_delete=models.CASCADE)
@@ -1824,6 +1763,10 @@ class CartPosition(ObjectWithAnswers, Versionable):
User, null=True, blank=True, User, null=True, blank=True,
verbose_name=_("User") verbose_name=_("User")
) )
session = models.CharField(
max_length=255, null=True, blank=True,
verbose_name=_("Session")
)
item = VersionedForeignKey( item = VersionedForeignKey(
Item, Item,
verbose_name=_("Item") verbose_name=_("Item")

View File

@@ -14,6 +14,7 @@ from pretix.base.models import CartPosition, Order
from pretix.base.services.orders import mark_order_paid from pretix.base.services.orders import mark_order_paid
from pretix.base.settings import SettingsSandbox from pretix.base.settings import SettingsSandbox
from pretix.base.signals import register_payment_providers from pretix.base.signals import register_payment_providers
from pretix.presale.views import user_cart_q
class BasePaymentProvider: class BasePaymentProvider:
@@ -441,7 +442,7 @@ class FreeOrderProvider(BasePaymentProvider):
def is_allowed(self, request: HttpRequest) -> bool: def is_allowed(self, request: HttpRequest) -> bool:
return CartPosition.objects.current.filter( return CartPosition.objects.current.filter(
Q(user=request.user) & Q(event=request.event) user_cart_q(request) & Q(event=request.event)
).aggregate(sum=Sum('price'))['sum'] == 0 ).aggregate(sum=Sum('price'))['sum'] == 0

View File

@@ -13,7 +13,7 @@ from pretix.helpers.urls import build_absolute_uri
logger = logging.getLogger('pretix.base.mail') logger = logging.getLogger('pretix.base.mail')
def mail(user: User, subject: str, template: str, context: dict=None, event: Event=None): def mail(email: str, subject: str, template: str, context: dict=None, event: Event=None, locale: str=None):
""" """
Sends out an email to a user. Sends out an email to a user.
@@ -30,11 +30,8 @@ def mail(user: User, subject: str, template: str, context: dict=None, event: Eve
the email has been sent, just that it has been queued by the e-mail the email has been sent, just that it has been queued by the e-mail
backend. backend.
""" """
if not user.email:
return False
_lng = translation.get_language() _lng = translation.get_language()
translation.activate(user.locale or settings.LANGUAGE_CODE) translation.activate(locale or settings.LANGUAGE_CODE)
if isinstance(template, LazyI18nString): if isinstance(template, LazyI18nString):
body = str(template) body = str(template)
@@ -66,7 +63,7 @@ def mail(user: User, subject: str, template: str, context: dict=None, event: Eve
) )
body += "\r\n" body += "\r\n"
try: try:
return mail_send([user.email], subject, body, sender) return mail_send([email], subject, body, sender)
finally: finally:
translation.activate(_lng) translation.activate(_lng)

View File

@@ -4,13 +4,16 @@ from django.db import transaction
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from pretix.base.models import EventLock, Order, OrderPosition, Quota from pretix.base.models import (
Event, EventLock, Order, OrderPosition, Quota, User,
)
from pretix.base.services.mail import mail from pretix.base.services.mail import mail
from pretix.base.signals import order_paid, order_placed from pretix.base.signals import order_paid, order_placed
from pretix.helpers.urls import build_absolute_uri from pretix.helpers.urls import build_absolute_uri
def mark_order_paid(order, provider=None, info=None, date=None, manual=None, force=False): def mark_order_paid(order: Order, provider: str=None, info: str=None, date: datetime=None, manual: bool=None,
force: bool=False):
""" """
Marks an order as paid. This clones the order object, sets the payment provider, Marks an order as paid. This clones the order object, sets the payment provider,
info and date and returns the cloned order object. info and date and returns the cloned order object.
@@ -44,20 +47,19 @@ def mark_order_paid(order, provider=None, info=None, date=None, manual=None, for
from pretix.base.services.mail import mail from pretix.base.services.mail import mail
mail( mail(
order.user, _('Payment received for your order: %(code)s') % {'code': order.code}, order.email, _('Payment received for your order: %(code)s') % {'code': order.code},
'pretixpresale/email/order_paid.txt', 'pretixpresale/email/order_paid.txt',
{ {
'user': order.user,
'order': order, 'order': order,
'event': order.event, 'event': order.event,
'url': build_absolute_uri('presale:event.order', kwargs={ 'url': build_absolute_uri('presale:event.order', kwargs={
'event': order.event.slug, 'event': order.event.slug,
'organizer': order.event.organizer.slug, 'organizer': order.event.organizer.slug,
'order': order.code, 'order': order.code,
}), }) + '?order_secret=' + order.secret,
'downloads': order.event.settings.get('ticket_download', as_type=bool) 'downloads': order.event.settings.get('ticket_download', as_type=bool)
}, },
order.event order.event, locale=order.locale
) )
return order return order
@@ -66,7 +68,7 @@ class OrderError(Exception):
pass pass
def check_positions(event, dt, positions): def check_positions(event: Event, dt: datetime, positions: list):
error_messages = { error_messages = {
'unavailable': _('Some of the products you selected were no longer available. ' 'unavailable': _('Some of the products you selected were no longer available. '
'Please see below for details.'), 'Please see below for details.'),
@@ -117,7 +119,8 @@ def check_positions(event, dt, positions):
raise OrderError(err) raise OrderError(err)
def perform_order(event, user, payment_provider, positions): def perform_order(event: Event, payment_provider: str, positions: list, user: User=None, email: str=None,
locale: str=None):
error_messages = { error_messages = {
'busy': _('We were not able to process your request completely as the ' 'busy': _('We were not able to process your request completely as the '
'server was too busy. Please try again.'), 'server was too busy. Please try again.'),
@@ -127,21 +130,22 @@ def perform_order(event, user, payment_provider, positions):
try: try:
with event.lock(): with event.lock():
check_positions(event, dt, positions) check_positions(event, dt, positions)
order = place_order(event, user, positions, dt, payment_provider) order = place_order(event, user, email if user is None else None, positions, dt, payment_provider,
locale=locale)
mail( mail(
user, _('Your order: %(code)s') % {'code': order.code}, order.email, _('Your order: %(code)s') % {'code': order.code},
'pretixpresale/email/order_placed.txt', 'pretixpresale/email/order_placed.txt',
{ {
'user': user, 'order': order, 'order': order,
'event': event, 'event': event,
'url': build_absolute_uri('presale:event.order', kwargs={ 'url': build_absolute_uri('presale:event.order', kwargs={
'event': event.slug, 'event': event.slug,
'organizer': event.organizer.slug, 'organizer': event.organizer.slug,
'order': order.code, 'order': order.code,
}), }) + '?order_secret=' + order.secret,
'payment': payment_provider.order_pending_mail_render(order) 'payment': payment_provider.order_pending_mail_render(order)
}, },
event event, locale=order.locale
) )
return order return order
except EventLock.LockTimeoutException: except EventLock.LockTimeoutException:
@@ -151,7 +155,8 @@ def perform_order(event, user, payment_provider, positions):
@transaction.atomic() @transaction.atomic()
def place_order(event, user, positions, dt, payment_provider): def place_order(event: Event, user: User, email: str, positions: list, dt: datetime, payment_provider: str,
locale: str=None):
total = sum([c.price for c in positions]) total = sum([c.price for c in positions])
payment_fee = payment_provider.calculate_fee(total) payment_fee = payment_provider.calculate_fee(total)
total += payment_fee total += payment_fee
@@ -162,8 +167,10 @@ def place_order(event, user, positions, dt, payment_provider):
status=Order.STATUS_PENDING, status=Order.STATUS_PENDING,
event=event, event=event,
user=user, user=user,
guest_email=email,
datetime=dt, datetime=dt,
expires=min(expires), expires=min(expires),
locale=locale,
total=total, total=total,
payment_fee=payment_fee, payment_fee=payment_fee,
payment_provider=payment_provider.identifier, payment_provider=payment_provider.identifier,

View File

@@ -1,85 +0,0 @@
from django import forms
from django.contrib.auth import authenticate
from django.contrib.auth.forms import \
AuthenticationForm as BaseAuthenticationForm
from django.utils.translation import ugettext as _
from pretix.base.models import User
class AuthenticationForm(BaseAuthenticationForm):
"""
The login form, providing an email and password field. The form already implements
validation for correct user data.
"""
email = forms.EmailField(label=_("Email address"), max_length=254)
password = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
username = None
error_messages = {
'invalid_login': _("Please enter a correct e-mail address and password."),
'inactive': _("This account is inactive.")
}
def __init__(self, request=None, *args, **kwargs):
self.request = request
self.user_cache = None
super(forms.Form, self).__init__(*args, **kwargs)
def clean(self):
email = self.cleaned_data.get('email')
password = self.cleaned_data.get('password')
if email and password:
self.user_cache = authenticate(identifier=email.lower(),
password=password)
if self.user_cache is None:
raise forms.ValidationError(
self.error_messages['invalid_login'],
code='invalid_login'
)
else:
self.confirm_login_allowed(self.user_cache)
return self.cleaned_data
class GlobalRegistrationForm(forms.Form):
error_messages = {
'duplicate_email': _("You already registered with that e-mail address, please use the login form."),
'pw_mismatch': _("Please enter the same password twice")
}
email = forms.EmailField(
label=_('Email address'),
required=True
)
password = forms.CharField(
label=_('Password'),
widget=forms.PasswordInput,
required=True
)
password_repeat = forms.CharField(
label=_('Repeat password'),
widget=forms.PasswordInput
)
def clean(self):
password1 = self.cleaned_data.get('password')
password2 = self.cleaned_data.get('password_repeat')
if password1 and password1 != password2:
raise forms.ValidationError(
self.error_messages['pw_mismatch'],
code='pw_mismatch',
)
return self.cleaned_data
def clean_email(self):
email = self.cleaned_data['email']
if User.objects.filter(identifier=email).exists():
raise forms.ValidationError(
self.error_messages['duplicate_email'],
code='duplicate_email',
)
return email

View File

@@ -66,8 +66,8 @@
<dt>{% trans "Expiry date" %}</dt> <dt>{% trans "Expiry date" %}</dt>
<dd>{{ order.expires }}</dd> <dd>{{ order.expires }}</dd>
{% endif %} {% endif %}
<dt>{% trans "Username" %}</dt> <dt>{% trans "User" %}</dt>
<dd>{{ order.user }}</dd> <dd>{{ order.user|default:order.guest_email }}</dd>
</dl> </dl>
</div> </div>
</div> </div>

View File

@@ -4,10 +4,8 @@ from django.contrib.auth import (
) )
from django.shortcuts import redirect, render from django.shortcuts import redirect, render
from pretix.base.forms.auth import LoginForm, RegistrationForm
from pretix.base.models import User from pretix.base.models import User
from pretix.control.forms.auth import (
AuthenticationForm, GlobalRegistrationForm,
)
def login(request): def login(request):
@@ -21,14 +19,14 @@ def login(request):
return redirect(request.GET.get("next", 'control:index')) return redirect(request.GET.get("next", 'control:index'))
return redirect('control:index') return redirect('control:index')
if request.method == 'POST': if request.method == 'POST':
form = AuthenticationForm(data=request.POST) form = LoginForm(data=request.POST)
if form.is_valid() and form.user_cache: if form.is_valid() and form.user_cache:
auth_login(request, form.user_cache) auth_login(request, form.user_cache)
if "next" in request.GET: if "next" in request.GET:
return redirect(request.GET.get("next", 'control:index')) return redirect(request.GET.get("next", 'control:index'))
return redirect('control:index') return redirect('control:index')
else: else:
form = AuthenticationForm() form = LoginForm()
ctx['form'] = form ctx['form'] = form
return render(request, 'pretixcontrol/auth/login.html', ctx) return render(request, 'pretixcontrol/auth/login.html', ctx)
@@ -51,17 +49,17 @@ def register(request):
return redirect(request.GET.get("next", 'control:index')) return redirect(request.GET.get("next", 'control:index'))
return redirect('control:index') return redirect('control:index')
if request.method == 'POST': if request.method == 'POST':
form = GlobalRegistrationForm(data=request.POST) form = RegistrationForm(data=request.POST)
if form.is_valid(): if form.is_valid():
user = User.objects.create_global_user( user = User.objects.create_user(
form.cleaned_data['email'], form.cleaned_data['password'], form.cleaned_data['email'], form.cleaned_data['password'],
locale=request.LANGUAGE_CODE, locale=request.LANGUAGE_CODE,
timezone=request.timezone if hasattr(request, 'timezone') else settings.TIME_ZONE timezone=request.timezone if hasattr(request, 'timezone') else settings.TIME_ZONE
) )
user = authenticate(identifier=user.identifier, password=form.cleaned_data['password']) user = authenticate(email=user.email, password=form.cleaned_data['password'])
auth_login(request, user) auth_login(request, user)
return redirect('control:index') return redirect('control:index')
else: else:
form = GlobalRegistrationForm() form = RegistrationForm()
ctx['form'] = form ctx['form'] = form
return render(request, 'pretixcontrol/auth/register.html', ctx) return render(request, 'pretixcontrol/auth/register.html', ctx)

View File

@@ -314,7 +314,7 @@ class EventPermissions(EventPermissionRequiredMixin, TemplateView):
if self.formset.is_valid() and self.add_form.is_valid(): if self.formset.is_valid() and self.add_form.is_valid():
if self.add_form.has_changed(): if self.add_form.has_changed():
try: try:
self.add_form.instance.user = User.objects.get(identifier=self.add_form.cleaned_data['user']) self.add_form.instance.user = User.objects.get(email=self.add_form.cleaned_data['user'])
self.add_form.instance.user_id = self.add_form.instance.user.id self.add_form.instance.user_id = self.add_form.instance.user.id
self.add_form.instance.event = self.request.event self.add_form.instance.event = self.request.event
self.add_form.instance.event_id = self.request.event.identity self.add_form.instance.event_id = self.request.event.identity

View File

@@ -39,8 +39,7 @@ class OrderList(EventPermissionRequiredMixin, ListView):
if self.request.GET.get("user", "") != "": if self.request.GET.get("user", "") != "":
u = self.request.GET.get("user", "") u = self.request.GET.get("user", "")
qs = qs.filter( qs = qs.filter(
Q(user__identifier__icontains=u) | Q(user__email__icontains=u) Q(user__email__icontains=u) | Q(user__givenname__icontains=u) | Q(user__familyname__icontains=u)
| Q(user__givenname__icontains=u) | Q(user__familyname__icontains=u)
) )
if self.request.GET.get("status", "") != "": if self.request.GET.get("status", "") != "":
s = self.request.GET.get("status", "") s = self.request.GET.get("status", "")

View File

@@ -31,8 +31,8 @@ class SenderView(EventPermissionRequiredMixin, FormView):
users = set([o.user for o in orders]) users = set([o.user for o in orders])
for u in users: for u in users:
mail(u, form.cleaned_data['subject'], form.cleaned_data['message'], mail(u.email, form.cleaned_data['subject'], form.cleaned_data['message'],
None, self.request.event) None, self.request.event, locale=u.locale)
messages.success(self.request, _('Your message will be sent to the selected users.')) messages.success(self.request, _('Your message will be sent to the selected users.'))

View File

@@ -1,227 +0,0 @@
from django import forms
from django.conf import settings
from django.contrib.auth import authenticate
from django.contrib.auth.forms import \
AuthenticationForm as BaseAuthenticationForm
from django.core.validators import RegexValidator
from django.forms import Form
from django.utils.translation import ugettext_lazy as _
from pretix.base.models import User
class LoginForm(BaseAuthenticationForm):
username = forms.CharField(
label=_('Username'),
help_text=(
_('If you registered for multiple events, your username is your email address.')
if settings.PRETIX_GLOBAL_REGISTRATION
else None
)
)
password = forms.CharField(
label=_('Password'),
widget=forms.PasswordInput
)
error_messages = {
'invalid_login': _("Please enter a correct username and password."),
'inactive': _("This account is inactive."),
}
def __init__(self, request=None, *args, **kwargs):
self.request = request
self.user_cache = None
super(forms.Form, self).__init__(*args, **kwargs)
def clean(self):
username = self.cleaned_data.get('username')
password = self.cleaned_data.get('password')
if username and password:
if '@' in username:
identifier = username.lower()
else:
identifier = "%s@%s.event.pretix" % (username, self.request.event.identity)
self.user_cache = authenticate(identifier=identifier,
password=password)
if self.user_cache is None:
raise forms.ValidationError(
self.error_messages['invalid_login'],
code='invalid_login',
)
else:
self.confirm_login_allowed(self.user_cache)
return self.cleaned_data
class GlobalRegistrationForm(forms.Form):
error_messages = {
'duplicate_email': _("You already registered with that e-mail address, please use the login form."),
'pw_mismatch': _("Please enter the same password twice"),
}
email = forms.EmailField(
label=_('Email address'),
required=True
)
password = forms.CharField(
label=_('Password'),
widget=forms.PasswordInput,
required=True
)
password_repeat = forms.CharField(
label=_('Repeat password'),
widget=forms.PasswordInput
)
def clean(self):
password1 = self.cleaned_data.get('password')
password2 = self.cleaned_data.get('password_repeat')
if password1 and password1 != password2:
raise forms.ValidationError(
self.error_messages['pw_mismatch'],
code='pw_mismatch',
)
return self.cleaned_data
def clean_email(self):
email = self.cleaned_data['email']
if User.objects.filter(identifier=email).exists():
raise forms.ValidationError(
self.error_messages['duplicate_email'],
code='duplicate_email',
)
return email
class LocalRegistrationForm(forms.Form):
error_messages = {
'invalid_username': _("Please only use characters, numbers or ./+/-/_ in your username."),
'duplicate_username': _("This username is already taken. Please choose a different one."),
'pw_mismatch': _("Please enter the same password twice"),
}
username = forms.CharField(
label=_('Username'),
validators=[
RegexValidator(
regex='^[a-zA-Z0-9\.+\-_]*$',
code='invalid_username',
message=error_messages['invalid_username']
),
],
required=True
)
email = forms.EmailField(
label=_('E-mail address'),
required=False
)
password = forms.CharField(
label=_('Password'),
widget=forms.PasswordInput,
required=True
)
password_repeat = forms.CharField(
label=_('Repeat password'),
widget=forms.PasswordInput
)
def __init__(self, request, *args, **kwargs):
super().__init__(*args, **kwargs)
self.request = request
self.fields['email'].required = request.event.settings.user_mail_required
def clean(self):
password1 = self.cleaned_data.get('password')
password2 = self.cleaned_data.get('password_repeat')
if password1 and password1 != password2:
raise forms.ValidationError(
self.error_messages['pw_mismatch'],
code='pw_mismatch',
)
return self.cleaned_data
def clean_username(self):
username = self.cleaned_data['username']
if User.objects.filter(event=self.request.event, username=username).exists():
raise forms.ValidationError(
self.error_messages['duplicate_username'],
code='duplicate_username',
)
return username
class PasswordRecoverForm(Form):
error_messages = {
'pw_mismatch': _("Please enter the same password twice"),
}
password = forms.CharField(
label=_('Password'),
widget=forms.PasswordInput,
required=True
)
password_repeat = forms.CharField(
label=_('Repeat password'),
widget=forms.PasswordInput
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def clean(self):
password1 = self.cleaned_data.get('password')
password2 = self.cleaned_data.get('password_repeat')
if password1 and password1 != password2:
raise forms.ValidationError(
self.error_messages['pw_mismatch'],
code='pw_mismatch',
)
return self.cleaned_data
class PasswordForgotForm(Form):
username = forms.CharField(
label=_('Username or E-mail'),
)
def __init__(self, event, *args, **kwargs):
self.event = event
super().__init__(*args, **kwargs)
def clean_username(self):
username = self.cleaned_data['username']
try:
self.cleaned_data['user'] = User.objects.get(
identifier=username, event__isnull=True
)
return username
except User.DoesNotExist:
pass
try:
self.cleaned_data['user'] = User.objects.get(
username=username, event=self.event
)
return username
except User.DoesNotExist:
pass
try:
self.cleaned_data['user'] = User.objects.get(
email=username, event=self.event
)
return username
except User.MultipleObjectsReturned:
raise forms.ValidationError(
_("We found multiple users with that e-mail address. Please specify the username instead"),
code='unknown_user',
)
except User.DoesNotExist:
raise forms.ValidationError(
_("We are unable to find a user matching the data you provided."),
code='unknown_user',
)

View File

@@ -4,6 +4,10 @@ from django.utils.translation import ugettext_lazy as _
from pretix.base.models import Question from pretix.base.models import Question
class GuestForm(forms.Form):
email = forms.EmailField(label=_('E-mail'))
class QuestionsForm(forms.Form): class QuestionsForm(forms.Form):
""" """
This form class is responsible for asking order-related questions. This includes This form class is responsible for asking order-related questions. This includes

View File

@@ -5,13 +5,24 @@ from pretix.base.models import Event
class EventMiddleware: class EventMiddleware:
def process_request(self, request): def process_request(self, request):
url = resolve(request.path_info) url = resolve(request.path_info)
url_namespace = url.namespace url_namespace = url.namespace
url_name = url.url_name url_name = url.url_name
if url_namespace != 'presale': if url_namespace != 'presale':
return return
if 'order_secrets' not in request.session:
request.session['order_secrets'] = []
if 'order_secret' in request.GET and request.GET.get('order_secret') not in request.session['order_secrets']:
# We can't use append here, because this would not trigger __setitem__
# on the session store and would not be saved
request.session['order_secrets'] = request.session['order_secrets'] + [request.GET.get('order_secret')]
# Removal of the secret from the URL has been disabled so people can bookmark it
# g = request.GET.copy()
# del g['order_secret']
# return redirect(request.path + '?' + g.urlencode())
if 'event.' in url_name and 'event' in url.kwargs: if 'event.' in url_name and 'event' in url.kwargs:
try: try:
request.event = Event.objects.current.filter( request.event = Event.objects.current.filter(

View File

@@ -11,7 +11,7 @@ Your {{ event }} team
we successfully received your payment for {{ event }}. Thank you! we successfully received your payment for {{ event }}. Thank you!
You can view the status of your order at You can change your order details and view the status of your order at
{{ url }} {{ url }}
Best regards, Best regards,

View File

@@ -4,7 +4,7 @@ we successfully received your order for {{ event }} with a total value
of {{ total }} {{ currency }}. Please complete your payment before {{ date }}. of {{ total }} {{ currency }}. Please complete your payment before {{ date }}.
{{ paymentinfo }} {{ paymentinfo }}
You can view the status of your order at You can change your order details and view the status of your order at
{{ url }} {{ url }}

View File

@@ -7,10 +7,10 @@
<form class="form-horizontal" method="post"> <form class="form-horizontal" method="post">
{% csrf_token %} {% csrf_token %}
{% bootstrap_form_errors form type='all' layout='inline' %} {% bootstrap_form_errors form type='all' layout='inline' %}
{% bootstrap_field form.username layout="horizontal" %} {% bootstrap_field form.email layout="horizontal" %}
<input type="hidden" name="form" value="login" /> <input type="hidden" name="form" value="login" />
<div class="form-group"> <div class="form-group">
<div class="submit-group col-md-offset-2 col-md-4 text-right"> <div class="submit-group col-md-offset-3 col-md-4">
<button type="submit" class="btn btn-primary btn-save"> <button type="submit" class="btn btn-primary btn-save">
{% trans "Send recovery information" %} {% trans "Send recovery information" %}
</button> </button>

View File

@@ -19,17 +19,17 @@
<form class="form-horizontal" method="post"> <form class="form-horizontal" method="post">
{% csrf_token %} {% csrf_token %}
{% bootstrap_form_errors login_form type='all' layout='inline' %} {% bootstrap_form_errors login_form type='all' layout='inline' %}
{% bootstrap_field login_form.username layout="horizontal" %} {% bootstrap_field login_form.email layout="horizontal" %}
{% bootstrap_field login_form.password layout="horizontal" %} {% bootstrap_field login_form.password layout="horizontal" %}
<input type="hidden" name="form" value="login" /> <input type="hidden" name="form" value="login" />
<div class="form-group"> <div class="form-group">
<div class="submit-group col-md-offset-2 col-md-4 text-right"> <div class="submit-group col-md-offset-3 col-md-4">
<a href="{% url "presale:event.forgot" event=request.event.slug organizer=request.event.organizer.slug %}" class="btn btn-link">
{% trans "Lost password?" %}
</a>
<button type="submit" class="btn btn-primary btn-save"> <button type="submit" class="btn btn-primary btn-save">
{% trans "Login" %} {% trans "Login" %}
</button> </button>
<a href="{% url "presale:event.forgot" event=request.event.slug organizer=request.event.organizer.slug %}" class="btn btn-link">
{% trans "Lost password?" %}
</a>
</div> </div>
</div> </div>
</form> </form>
@@ -39,30 +39,23 @@
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading" role="tab" id="headingOne"> <div class="panel-heading" role="tab" id="headingOne">
<h4 class="panel-title"> <h4 class="panel-title">
<a data-toggle="collapse" href="#localRegistrationForm" data-parent="#login_accordion"> <a data-toggle="collapse" href="#guestForm" data-parent="#login_accordion">
{% if global_registration_form %} {% trans "I want to order as a guest" %}
{% trans "I want to create a new account just for this event" %}
{% else %}
{% trans "I want to create a new account" %}
{% endif %}
</a> </a>
</h4> </h4>
</div> </div>
<div id="localRegistrationForm" class="panel-collapse collapsed {% if request.POST.form == 'local_registration' %}in{% endif %}"> <div id="guestForm" class="panel-collapse collapsed {% if request.POST.form == 'guest' %}in{% endif %}">
<div class="panel-body"> <div class="panel-body">
<div class="panel-body"> <div class="panel-body">
<form class="form-horizontal" method="post"> <form class="form-horizontal" method="post">
{% csrf_token %} {% csrf_token %}
{% bootstrap_form_errors local_registration_form type='all' layout='inline' %} {% bootstrap_form_errors guest_form type='all' layout='inline' %}
{% bootstrap_field local_registration_form.username layout="horizontal" %} {% bootstrap_field guest_form.email layout="horizontal" %}
{% bootstrap_field local_registration_form.email layout="horizontal" %} <input type="hidden" name="form" value="guest" />
{% bootstrap_field local_registration_form.password layout="horizontal" %}
{% bootstrap_field local_registration_form.password_repeat layout="horizontal" %}
<input type="hidden" name="form" value="local_registration" />
<div class="form-group"> <div class="form-group">
<div class="submit-group col-md-offset-2 col-md-4 text-right"> <div class="submit-group col-md-offset-3 col-md-4">
<button type="submit" class="btn btn-primary btn-save"> <button type="submit" class="btn btn-primary btn-save">
{% trans "Register" %} {% trans "Continue" %}
</button> </button>
</div> </div>
</div> </div>
@@ -71,35 +64,34 @@
</div> </div>
</div> </div>
</div> </div>
{% if global_registration_form %} <div class="panel panel-default">
<div class="panel panel-default"> <div class="panel-heading" role="tab" id="headingOne">
<div class="panel-heading" role="tab" id="headingOne"> <h4 class="panel-title">
<h4 class="panel-title"> <a data-toggle="collapse" href="#registrationForm" data-parent="#login_accordion">
<a data-toggle="collapse" href="#globalRegistrationForm" data-parent="#login_accordion"> {% trans "I want to create a permanent account" %}
{% trans "I want to create a permanent account" %} </a>
</a> </h4>
</h4> </div>
</div> <div id="registrationForm" class="panel-collapse collapsed
<div id="globalRegistrationForm" class="panel-collapse collapsed {% if request.POST.form == 'global_registration' %}in{% endif %}"> {% if request.POST.form == 'registration' %}in{% endif %}">
<div class="panel-body"> <div class="panel-body">
<form class="form-horizontal" method="post"> <form class="form-horizontal" method="post">
{% csrf_token %} {% csrf_token %}
{% bootstrap_form_errors global_registration_form type='all' layout='inline' %} {% bootstrap_form_errors registration_form type='all' layout='inline' %}
{% bootstrap_field global_registration_form.email layout="horizontal" %} {% bootstrap_field registration_form.email layout="horizontal" %}
{% bootstrap_field global_registration_form.password layout="horizontal" %} {% bootstrap_field registration_form.password layout="horizontal" %}
{% bootstrap_field global_registration_form.password_repeat layout="horizontal" %} {% bootstrap_field registration_form.password_repeat layout="horizontal" %}
<input type="hidden" name="form" value="global_registration" /> <input type="hidden" name="form" value="registration" />
<div class="form-group"> <div class="form-group">
<div class="submit-group col-md-offset-2 col-md-4 text-right"> <div class="submit-group col-md-offset-3 col-md-4">
<button type="submit" class="btn btn-primary btn-save"> <button type="submit" class="btn btn-primary btn-save">
{% trans "Register" %} {% trans "Register" %}
</button> </button>
</div>
</div> </div>
</form> </div>
</div> </form>
</div> </div>
</div> </div>
{% endif %} </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -11,7 +11,7 @@
{% bootstrap_field form.password_repeat layout="horizontal" %} {% bootstrap_field form.password_repeat layout="horizontal" %}
<input type="hidden" name="form" value="login" /> <input type="hidden" name="form" value="login" />
<div class="form-group"> <div class="form-group">
<div class="submit-group col-md-offset-2 col-md-4 text-right"> <div class="submit-group col-md-offset-3 col-md-4">
<button type="submit" class="btn btn-primary btn-save"> <button type="submit" class="btn btn-primary btn-save">
{% trans "Set new password" %} {% trans "Set new password" %}
</button> </button>

View File

@@ -11,26 +11,52 @@ from pretix.base.models import CartPosition
from pretix.base.signals import register_payment_providers from pretix.base.signals import register_payment_providers
class EventLoginRequiredMixin: def login_required(view_func):
def _wrapped_view(request, *args, **kwargs):
if request.user.is_authenticated():
return view_func(request, *args, **kwargs)
path = request.path
return redirect_to_login(
path, reverse('presale:event.checkout.login', kwargs={
'organizer': request.event.organizer.slug,
'event': request.event.slug,
}), 'next'
)
return _wrapped_view
def login_or_guest_required(view_func):
def _wrapped_view(request, *args, **kwargs):
if request.user.is_authenticated() or 'guest_email' in request.session:
return view_func(request, *args, **kwargs)
path = request.path
return redirect_to_login(
path, reverse('presale:event.checkout.login', kwargs={
'organizer': request.event.organizer.slug,
'event': request.event.slug,
}), 'next'
)
return _wrapped_view
class LoginRequiredMixin:
@classmethod @classmethod
def as_view(cls, **initkwargs): def as_view(cls, **initkwargs):
view = super(EventLoginRequiredMixin, cls).as_view(**initkwargs) view = super().as_view(**initkwargs)
return login_required(view)
def decorator(view_func):
def _wrapped_view(request, *args, **kwargs): class LoginOrGuestRequiredMixin:
if request.user.is_authenticated() and \ @classmethod
(request.user.event is None or request.user.event == request.event): def as_view(cls, **initkwargs):
return view_func(request, *args, **kwargs) view = super().as_view(**initkwargs)
path = request.path return login_or_guest_required(view)
return redirect_to_login(
path, reverse('presale:event.checkout.login', kwargs={
'organizer': request.event.organizer.slug, def user_cart_q(request):
'event': request.event.slug, if request.user.is_authenticated():
}), 'next' return Q(Q(user=request.user) | Q(session=request.session.session_key))
) return Q(Q(user__isnull=True) & Q(session=request.session.session_key))
return _wrapped_view
return decorator(view)
class CartDisplayMixin: class CartDisplayMixin:
@@ -41,7 +67,7 @@ class CartDisplayMixin:
A list of this users cart position A list of this users cart position
""" """
return list(CartPosition.objects.current.filter( return list(CartPosition.objects.current.filter(
Q(user=self.request.user) & Q(event=self.request.event) user_cart_q(self.request) & Q(event=self.request.event)
).order_by( ).order_by(
'item', 'variation' 'item', 'variation'
).select_related( ).select_related(
@@ -53,7 +79,7 @@ class CartDisplayMixin:
def get_cart(self, answers=False, queryset=None, payment_fee=None): def get_cart(self, answers=False, queryset=None, payment_fee=None):
queryset = queryset or CartPosition.objects.current.filter( queryset = queryset or CartPosition.objects.current.filter(
Q(user=self.request.user) & Q(event=self.request.event) user_cart_q(self.request) & Q(event=self.request.event)
) )
prefetch = ['variation__values', 'variation__values__prop'] prefetch = ['variation__values', 'variation__values__prop']
@@ -119,7 +145,6 @@ class CartDisplayMixin:
class EventViewMixin: class EventViewMixin:
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['event'] = self.request.event context['event'] = self.request.event

View File

@@ -13,7 +13,9 @@ from django.views.generic import View
from pretix.base.models import ( from pretix.base.models import (
CartPosition, EventLock, Item, ItemVariation, Quota, CartPosition, EventLock, Item, ItemVariation, Quota,
) )
from pretix.presale.views import EventLoginRequiredMixin, EventViewMixin from pretix.presale.views import (
EventViewMixin, LoginOrGuestRequiredMixin, user_cart_q,
)
class CartActionMixin: class CartActionMixin:
@@ -62,13 +64,13 @@ class CartActionMixin:
return items return items
class CartRemove(EventViewMixin, CartActionMixin, EventLoginRequiredMixin, View): class CartRemove(EventViewMixin, CartActionMixin, LoginOrGuestRequiredMixin, View):
def post(self, *args, **kwargs): def post(self, *args, **kwargs):
items = self._items_from_post_data() items = self._items_from_post_data()
if not items: if not items:
return redirect(self.get_failure_url()) return redirect(self.get_failure_url())
qw = Q(user=self.request.user) qw = user_cart_q(self.request)
for item, variation, cnt in items: for item, variation, cnt in items:
cw = qw & Q(item_id=item) cw = qw & Q(item_id=item)
@@ -110,10 +112,9 @@ class CartAdd(EventViewMixin, CartActionMixin, View):
self.items = self._items_from_post_data() self.items = self._items_from_post_data()
# We do not use EventLoginRequiredMixin here, as we want to store stuff into the # We do not use LoginRequiredMixin here, as we want to store stuff into the
# session before redirecting to login # session before redirecting to login
if not request.user.is_authenticated() or \ if not request.user.is_authenticated() and 'guest_email' not in request.session:
(request.user.event is not None and request.user.event != request.event):
request.session['cart_tmp'] = json.dumps(self.items) request.session['cart_tmp'] = json.dumps(self.items)
return redirect_to_login( return redirect_to_login(
self.get_success_url(), reverse('presale:event.checkout.login', kwargs={ self.get_success_url(), reverse('presale:event.checkout.login', kwargs={
@@ -122,7 +123,7 @@ class CartAdd(EventViewMixin, CartActionMixin, View):
}), 'next' }), 'next'
) )
existing = CartPosition.objects.current.filter(user=self.request.user, event=self.request.event).count() existing = CartPosition.objects.current.filter(user_cart_q(self.request) & Q(event=self.request.event)).count()
if sum(i[2] for i in self.items) + existing > int(self.request.event.settings.max_items_per_order): if sum(i[2] for i in self.items) + existing > int(self.request.event.settings.max_items_per_order):
# TODO: i18n plurals # TODO: i18n plurals
self.error_message(self.error_messages['max_items'] % self.request.event.settings.max_items_per_order) self.error_message(self.error_messages['max_items'] % self.request.event.settings.max_items_per_order)
@@ -143,7 +144,7 @@ class CartAdd(EventViewMixin, CartActionMixin, View):
# For items that are already expired, we have to delete and re-add them, as they might # For items that are already expired, we have to delete and re-add them, as they might
# be no longer available or prices might have changed. Sorry! # be no longer available or prices might have changed. Sorry!
for cp in CartPosition.objects.current.filter( for cp in CartPosition.objects.current.filter(
Q(user=self.request.user) & Q(event=self.request.event) & Q(expires__lte=now()) user_cart_q(self.request) & Q(event=self.request.event) & Q(expires__lte=now())
): ):
self._re_add_position(cp) self._re_add_position(cp)
positions.add(cp) positions.add(cp)
@@ -154,7 +155,7 @@ class CartAdd(EventViewMixin, CartActionMixin, View):
# cart expire at the same time # cart expire at the same time
# We can extend the reservation of items which are not yet expired without risk # We can extend the reservation of items which are not yet expired without risk
CartPosition.objects.current.filter( CartPosition.objects.current.filter(
Q(user=self.request.user) & Q(event=self.request.event) & Q(expires__gt=now()) user_cart_q(self.request) & Q(event=self.request.event) & Q(expires__gt=now())
).update(expires=expiry) ).update(expires=expiry)
def _delete_expired(self): def _delete_expired(self):
@@ -238,14 +239,18 @@ class CartAdd(EventViewMixin, CartActionMixin, View):
cp.price = price cp.price = price
cp.save() cp.save()
else: else:
CartPosition.objects.create( cp = CartPosition(
event=self.request.event, event=self.request.event,
user=self.request.user,
item=item, item=item,
variation=variation, variation=variation,
price=price, price=price,
expires=expiry expires=expiry
) )
if self.request.user.is_authenticated():
cp.user = self.request.user
else:
cp.session = self.request.session.session_key
cp.save()
self._delete_expired() self._delete_expired()

View File

@@ -3,6 +3,7 @@ from django.core.urlresolvers import reverse
from django.db.models import Q, Sum from django.db.models import Q, Sum
from django.http import HttpRequest from django.http import HttpRequest
from django.shortcuts import redirect from django.shortcuts import redirect
from django.utils import translation
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.generic import TemplateView from django.views.generic import TemplateView
@@ -12,11 +13,13 @@ from pretix.base.services.orders import OrderError, perform_order
from pretix.base.signals import register_payment_providers from pretix.base.signals import register_payment_providers
from pretix.presale.forms.checkout import QuestionsForm from pretix.presale.forms.checkout import QuestionsForm
from pretix.presale.views import ( from pretix.presale.views import (
CartDisplayMixin, EventLoginRequiredMixin, EventViewMixin, CartDisplayMixin, EventViewMixin, LoginOrGuestRequiredMixin,
LoginRequiredMixin, user_cart_q,
) )
class CheckoutView(TemplateView): class CheckoutView(TemplateView):
def get_payment_url(self): def get_payment_url(self):
return reverse('presale:event.checkout.payment', kwargs={ return reverse('presale:event.checkout.payment', kwargs={
'event': self.request.event.slug, 'event': self.request.event.slug,
@@ -41,12 +44,12 @@ class CheckoutView(TemplateView):
'organizer': self.request.event.organizer.slug 'organizer': self.request.event.organizer.slug
}) })
def get_order_url(self, order): def get_order_url(self, order, add_secret):
return reverse('presale:event.order', kwargs={ return reverse('presale:event.order', kwargs={
'event': self.request.event.slug, 'event': self.request.event.slug,
'organizer': self.request.event.organizer.slug, 'organizer': self.request.event.organizer.slug,
'order': order.code, 'order': order.code,
}) }) + '?thanks=yes' + ('&order_secret=' + order.secret if add_secret else '')
class QuestionsViewMixin: class QuestionsViewMixin:
@@ -106,7 +109,7 @@ class QuestionsViewMixin:
return not failed return not failed
class CheckoutStart(EventViewMixin, CartDisplayMixin, EventLoginRequiredMixin, class CheckoutStart(EventViewMixin, CartDisplayMixin, LoginOrGuestRequiredMixin,
QuestionsViewMixin, CheckoutView): QuestionsViewMixin, CheckoutView):
template_name = "pretixpresale/event/checkout_questions.html" template_name = "pretixpresale/event/checkout_questions.html"
@@ -138,13 +141,13 @@ class CheckoutStart(EventViewMixin, CartDisplayMixin, EventLoginRequiredMixin,
return ctx return ctx
class PaymentDetails(EventViewMixin, CartDisplayMixin, EventLoginRequiredMixin, CheckoutView): class PaymentDetails(EventViewMixin, CartDisplayMixin, LoginOrGuestRequiredMixin, CheckoutView):
template_name = "pretixpresale/event/checkout_payment.html" template_name = "pretixpresale/event/checkout_payment.html"
@cached_property @cached_property
def _total_order_value(self): def _total_order_value(self):
return CartPosition.objects.current.filter( return CartPosition.objects.current.filter(
Q(user=self.request.user) & Q(event=self.request.event) user_cart_q(self.request) & Q(event=self.request.event)
).aggregate(sum=Sum('price'))['sum'] ).aggregate(sum=Sum('price'))['sum']
@cached_property @cached_property
@@ -194,7 +197,7 @@ class PaymentDetails(EventViewMixin, CartDisplayMixin, EventLoginRequiredMixin,
return self.get_questions_url() + "?back=true" return self.get_questions_url() + "?back=true"
class OrderConfirm(EventViewMixin, CartDisplayMixin, EventLoginRequiredMixin, CheckoutView): class OrderConfirm(EventViewMixin, CartDisplayMixin, LoginOrGuestRequiredMixin, CheckoutView):
template_name = "pretixpresale/event/checkout_confirm.html" template_name = "pretixpresale/event/checkout_confirm.html"
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@@ -256,14 +259,18 @@ class OrderConfirm(EventViewMixin, CartDisplayMixin, EventLoginRequiredMixin, Ch
def perform_order(self, request: HttpRequest): def perform_order(self, request: HttpRequest):
try: try:
order = perform_order(self.request.event, self.request.user, self.payment_provider, self.positions) order = perform_order(self.request.event, self.payment_provider, self.positions,
user=request.user if request.user.is_authenticated() else None,
email=request.session.get('guest_email', None),
locale=translation.get_language())
except OrderError as e: except OrderError as e:
messages.error(request, str(e)) messages.error(request, str(e))
return redirect(self.get_confirm_url()) return redirect(self.get_confirm_url())
else: else:
messages.success(request, _('Your order has been placed.')) # Message is delivered via GET parameter
# messages.success(request, _('Your order has been placed.'))
resp = self.payment_provider.payment_perform(request, order) resp = self.payment_provider.payment_perform(request, order)
return redirect(resp or self.get_order_url(order)) return redirect(resp or self.get_order_url(order, not request.user.is_authenticated()))
def get_previous_url(self): def get_previous_url(self):
if self.payment_provider.identifier != "free": if self.payment_provider.identifier != "free":

View File

@@ -14,16 +14,16 @@ from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.generic import TemplateView, UpdateView, View from django.views.generic import TemplateView, UpdateView, View
from pretix.base.forms.auth import (
LoginForm, PasswordForgotForm, PasswordRecoverForm, RegistrationForm,
)
from pretix.base.forms.user import UserSettingsForm from pretix.base.forms.user import UserSettingsForm
from pretix.base.models import User from pretix.base.models import User
from pretix.base.services.mail import mail from pretix.base.services.mail import mail
from pretix.helpers.urls import build_absolute_uri from pretix.helpers.urls import build_absolute_uri
from pretix.presale.forms.auth import ( from pretix.presale.forms.checkout import GuestForm
GlobalRegistrationForm, LocalRegistrationForm, LoginForm,
PasswordForgotForm, PasswordRecoverForm,
)
from pretix.presale.views import ( from pretix.presale.views import (
CartDisplayMixin, EventLoginRequiredMixin, EventViewMixin, CartDisplayMixin, EventViewMixin, LoginRequiredMixin,
) )
from pretix.presale.views.cart import CartAdd from pretix.presale.views.cart import CartAdd
@@ -79,7 +79,7 @@ class EventIndex(EventViewMixin, CartDisplayMixin, TemplateView):
key=lambda group: (group[0].position, group[0].identity) if group[0] is not None else (0, "") key=lambda group: (group[0].position, group[0].identity) if group[0] is not None else (0, "")
) )
context['cart'] = self.get_cart() if self.request.user.is_authenticated() else None context['cart'] = self.get_cart()
return context return context
@@ -102,8 +102,7 @@ class EventLogin(EventViewMixin, TemplateView):
event=self.request.event.slug) event=self.request.event.slug)
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
if request.user.is_authenticated() and \ if request.user.is_authenticated():
(request.user.event is None or request.user.event == request.event):
return self.redirect_to_next() return self.redirect_to_next()
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
@@ -113,27 +112,20 @@ class EventLogin(EventViewMixin, TemplateView):
if form.is_valid() and form.user_cache: if form.is_valid() and form.user_cache:
login(request, form.user_cache) login(request, form.user_cache)
return self.redirect_to_next() return self.redirect_to_next()
elif request.POST.get('form') == 'local_registration': elif request.POST.get('form') == 'guest':
form = self.local_registration_form form = self.guest_form
if form.is_valid(): if form.is_valid():
user = User.objects.create_local_user( request.session['guest_email'] = form.cleaned_data['email']
request.event, form.cleaned_data['username'], form.cleaned_data['password'],
email=form.cleaned_data['email'] if form.cleaned_data['email'] != '' else None,
locale=request.LANGUAGE_CODE,
timezone=request.timezone if hasattr(request, 'timezone') else settings.TIME_ZONE
)
user = authenticate(identifier=user.identifier, password=form.cleaned_data['password'])
login(request, user)
return self.redirect_to_next() return self.redirect_to_next()
elif request.POST.get('form') == 'global_registration' and settings.PRETIX_GLOBAL_REGISTRATION: elif request.POST.get('form') == 'registration':
form = self.global_registration_form form = self.registration_form
if form.is_valid(): if form.is_valid():
user = User.objects.create_global_user( user = User.objects.create_user(
form.cleaned_data['email'], form.cleaned_data['password'], form.cleaned_data['email'], form.cleaned_data['password'],
locale=request.LANGUAGE_CODE, locale=request.LANGUAGE_CODE,
timezone=request.timezone if hasattr(request, 'timezone') else settings.TIME_ZONE timezone=request.timezone if hasattr(request, 'timezone') else settings.TIME_ZONE
) )
user = authenticate(identifier=user.identifier, password=form.cleaned_data['password']) user = authenticate(email=user.email, password=form.cleaned_data['password'])
login(request, user) login(request, user)
return self.redirect_to_next() return self.redirect_to_next()
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
@@ -146,26 +138,22 @@ class EventLogin(EventViewMixin, TemplateView):
) )
@cached_property @cached_property
def global_registration_form(self): def guest_form(self):
if settings.PRETIX_GLOBAL_REGISTRATION: return GuestForm(
return GlobalRegistrationForm( data=self.request.POST if self.request.POST.get('form', '') == 'guest' else None
data=self.request.POST if self.request.POST.get('form', '') == 'global_registration' else None )
)
else:
return None
@cached_property @cached_property
def local_registration_form(self): def registration_form(self):
return LocalRegistrationForm( return RegistrationForm(
self.request, data=self.request.POST if self.request.POST.get('form', '') == 'registration' else None
data=self.request.POST if self.request.POST.get('form', '') == 'local_registration' else None
) )
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['login_form'] = self.login_form context['login_form'] = self.login_form
context['global_registration_form'] = self.global_registration_form context['registration_form'] = self.registration_form
context['local_registration_form'] = self.local_registration_form context['guest_form'] = self.guest_form
return context return context
@@ -173,8 +161,7 @@ class EventForgot(EventViewMixin, TemplateView):
template_name = 'pretixpresale/event/forgot.html' template_name = 'pretixpresale/event/forgot.html'
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
if request.user.is_authenticated() and \ if request.user.is_authenticated():
(request.user.event is None or request.user.event == request.event):
return redirect('presale:event.orders', return redirect('presale:event.orders',
organizer=self.request.event.organizer.slug, organizer=self.request.event.organizer.slug,
event=self.request.event.slug) event=self.request.event.slug)
@@ -189,24 +176,19 @@ class EventForgot(EventViewMixin, TemplateView):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
if self.form.is_valid(): if self.form.is_valid():
user = self.form.cleaned_data['user'] user = self.form.cleaned_data['user']
if user.email: mail(
mail( user.email, _('Password recovery'), 'pretixpresale/email/forgot.txt',
user, _('Password recovery'), {
'pretixpresale/email/forgot.txt', 'user': user,
{ 'event': self.request.event,
'user': user, 'url': build_absolute_uri('presale:event.forgot.recover', kwargs={
'event': self.request.event, 'event': self.request.event.slug,
'url': build_absolute_uri('presale:event.forgot.recover', kwargs={ 'organizer': self.request.event.organizer.slug,
'event': self.request.event.slug, }) + '?token=' + self.generate_token(user),
'organizer': self.request.event.organizer.slug, },
}) + '?token=' + self.generate_token(user), self.request.event, locale=user.locale
}, )
self.request.event messages.success(request, _('We sent you an e-mail containing further instructions.'))
)
messages.success(request, _('We sent you an e-mail containing further instructions.'))
else:
messages.success(request, _('We are unable to send you a new password, as you did not enter an e-mail '
'address at your registration.'))
return redirect('presale:event.forgot', return redirect('presale:event.forgot',
organizer=self.request.event.organizer.slug, organizer=self.request.event.organizer.slug,
event=self.request.event.slug) event=self.request.event.slug)
@@ -238,8 +220,7 @@ class EventRecover(EventViewMixin, TemplateView):
} }
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
if request.user.is_authenticated() and \ if request.user.is_authenticated():
(request.user.event is None or request.user.event == request.event):
return redirect('presale:event.orders', return redirect('presale:event.orders',
organizer=self.request.event.organizer.slug, organizer=self.request.event.organizer.slug,
event=self.request.event.slug) event=self.request.event.slug)
@@ -306,7 +287,7 @@ class EventLogout(EventViewMixin, View):
event=self.request.event.slug) event=self.request.event.slug)
class EventAccount(EventLoginRequiredMixin, EventViewMixin, TemplateView): class EventAccount(LoginRequiredMixin, EventViewMixin, TemplateView):
template_name = 'pretixpresale/event/account.html' template_name = 'pretixpresale/event/account.html'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
@@ -315,7 +296,7 @@ class EventAccount(EventLoginRequiredMixin, EventViewMixin, TemplateView):
return context return context
class EventOrders(EventLoginRequiredMixin, EventViewMixin, TemplateView): class EventOrders(LoginRequiredMixin, EventViewMixin, TemplateView):
template_name = 'pretixpresale/event/orders.html' template_name = 'pretixpresale/event/orders.html'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
@@ -324,7 +305,7 @@ class EventOrders(EventLoginRequiredMixin, EventViewMixin, TemplateView):
return context return context
class EventAccountSettings(EventLoginRequiredMixin, EventViewMixin, UpdateView): class EventAccountSettings(LoginRequiredMixin, EventViewMixin, UpdateView):
model = User model = User
form_class = UserSettingsForm form_class = UserSettingsForm
template_name = 'pretixpresale/event/account_settings.html' template_name = 'pretixpresale/event/account_settings.html'

View File

@@ -2,34 +2,28 @@ from datetime import timedelta
from django.contrib import messages from django.contrib import messages
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.db.models import Q
from django.http import HttpResponseForbidden, HttpResponseNotFound from django.http import HttpResponseForbidden, HttpResponseNotFound
from django.shortcuts import redirect from django.shortcuts import redirect
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.generic import TemplateView, View from django.views.generic import TemplateView, View
from pretix.base.models import CachedFile, CachedTicket, Order, OrderPosition from pretix.base.models import CachedFile, CachedTicket, Order, OrderPosition
from pretix.base.services.tickets import generate from pretix.base.services.tickets import generate
from pretix.base.signals import ( from pretix.base.signals import register_payment_providers, register_ticket_outputs
register_payment_providers, register_ticket_outputs, from pretix.presale.views import CartDisplayMixin, EventViewMixin
)
from pretix.presale.views import (
CartDisplayMixin, EventLoginRequiredMixin, EventViewMixin,
)
from pretix.presale.views.checkout import QuestionsViewMixin from pretix.presale.views.checkout import QuestionsViewMixin
class OrderDetailMixin: class OrderDetailMixin:
@cached_property @cached_property
def order(self): def order(self):
try: try:
return Order.objects.current.get( q = Q(Q(secret__isnull=False) & Q(secret__in=self.request.session['order_secrets']))
user=self.request.user, if self.request.user.is_authenticated():
event=self.request.event, q |= Q(user=self.request.user)
code=self.kwargs['order'], return Order.objects.current.get(q & Q(event=self.request.event) & Q(code=self.kwargs['order']))
)
except Order.DoesNotExist: except Order.DoesNotExist:
return None return None
@@ -49,8 +43,7 @@ class OrderDetailMixin:
}) })
class OrderDetails(EventViewMixin, EventLoginRequiredMixin, OrderDetailMixin, class OrderDetails(EventViewMixin, OrderDetailMixin, CartDisplayMixin, TemplateView):
CartDisplayMixin, TemplateView):
template_name = "pretixpresale/event/order.html" template_name = "pretixpresale/event/order.html"
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
@@ -102,7 +95,7 @@ class OrderDetails(EventViewMixin, EventLoginRequiredMixin, OrderDetailMixin,
return ctx return ctx
class OrderPay(EventViewMixin, EventLoginRequiredMixin, OrderDetailMixin, TemplateView): class OrderPay(EventViewMixin, OrderDetailMixin, TemplateView):
template_name = "pretixpresale/event/order_pay.html" template_name = "pretixpresale/event/order_pay.html"
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
@@ -145,7 +138,7 @@ class OrderPay(EventViewMixin, EventLoginRequiredMixin, OrderDetailMixin, Templa
}) })
class OrderPayDo(EventViewMixin, EventLoginRequiredMixin, OrderDetailMixin, TemplateView): class OrderPayDo(EventViewMixin, OrderDetailMixin, TemplateView):
template_name = "pretixpresale/event/order_pay_confirm.html" template_name = "pretixpresale/event/order_pay_confirm.html"
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
@@ -185,8 +178,7 @@ class OrderPayDo(EventViewMixin, EventLoginRequiredMixin, OrderDetailMixin, Temp
}) })
class OrderModify(EventViewMixin, EventLoginRequiredMixin, OrderDetailMixin, class OrderModify(EventViewMixin, OrderDetailMixin, QuestionsViewMixin, TemplateView):
QuestionsViewMixin, TemplateView):
template_name = "pretixpresale/event/order_modify.html" template_name = "pretixpresale/event/order_modify.html"
@cached_property @cached_property
@@ -227,8 +219,7 @@ class OrderModify(EventViewMixin, EventLoginRequiredMixin, OrderDetailMixin,
return ctx return ctx
class OrderCancel(EventViewMixin, EventLoginRequiredMixin, OrderDetailMixin, class OrderCancel(EventViewMixin, OrderDetailMixin, TemplateView):
TemplateView):
template_name = "pretixpresale/event/order_cancel.html" template_name = "pretixpresale/event/order_cancel.html"
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
@@ -255,9 +246,7 @@ class OrderCancel(EventViewMixin, EventLoginRequiredMixin, OrderDetailMixin,
return ctx return ctx
class OrderDownload(EventViewMixin, EventLoginRequiredMixin, OrderDetailMixin, class OrderDownload(EventViewMixin, OrderDetailMixin, View):
View):
@cached_property @cached_property
def output(self): def output(self):
responses = register_ticket_outputs.send(self.request.event) responses = register_ticket_outputs.send(self.request.event)

View File

@@ -57,7 +57,6 @@ STATIC_URL = config.get('urls', 'static', fallback='/static/')
MEDIA_URL = config.get('urls', 'media', fallback='/media/') MEDIA_URL = config.get('urls', 'media', fallback='/media/')
PRETIX_INSTANCE_NAME = config.get('pretix', 'instance_name', fallback='pretix.de') PRETIX_INSTANCE_NAME = config.get('pretix', 'instance_name', fallback='pretix.de')
PRETIX_GLOBAL_REGISTRATION = config.getboolean('pretix', 'global_registration', fallback=True)
SITE_URL = config.get('pretix', 'url', fallback='http://localhost') SITE_URL = config.get('pretix', 'url', fallback='http://localhost')

View File

@@ -15,7 +15,7 @@ def env():
organizer=o, name='Dummy', slug='dummy', organizer=o, name='Dummy', slug='dummy',
date_from=now(), plugins='pretix.plugins.banktransfer' date_from=now(), plugins='pretix.plugins.banktransfer'
) )
user = User.objects.create_user('dummy@dummy.dummy', 'dummy@dummy.dummy', 'dummy') user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
user.email = 'dummy@dummy.dummy' user.email = 'dummy@dummy.dummy'
user.save() user.save()
return event, user, o return event, user, o
@@ -26,8 +26,7 @@ def test_send_mail_with_prefix(client, env):
djmail.outbox = [] djmail.outbox = []
event, user, organizer = env event, user, organizer = env
event.settings.set('mail_prefix', 'test') event.settings.set('mail_prefix', 'test')
mail(user, 'Test subject', mail('dummy@dummy.dummy', 'Test subject', 'mailtest.txt', {}, event)
'mailtest.txt', {}, event)
assert len(djmail.outbox) == 1 assert len(djmail.outbox) == 1
assert djmail.outbox[0].to == [user.email] assert djmail.outbox[0].to == [user.email]
@@ -39,8 +38,7 @@ def test_send_mail_with_event_sender(client, env):
djmail.outbox = [] djmail.outbox = []
event, user, organizer = env event, user, organizer = env
event.settings.set('mail_from', 'foo@bar') event.settings.set('mail_from', 'foo@bar')
mail(user, 'Test subject', mail('dummy@dummy.dummy', 'Test subject', 'mailtest.txt', {}, event)
'mailtest.txt', {}, event)
assert len(djmail.outbox) == 1 assert len(djmail.outbox) == 1
assert djmail.outbox[0].to == [user.email] assert djmail.outbox[0].to == [user.email]
@@ -52,8 +50,7 @@ def test_send_mail_with_event_sender(client, env):
def test_send_mail_with_default_sender(client, env): def test_send_mail_with_default_sender(client, env):
djmail.outbox = [] djmail.outbox = []
event, user, organizer = env event, user, organizer = env
mail(user, 'Test subject', mail('dummy@dummy.dummy', 'Test subject', 'mailtest.txt', {}, event)
'mailtest.txt', {}, event)
del event.settings['mail_from'] del event.settings['mail_from']
assert len(djmail.outbox) == 1 assert len(djmail.outbox) == 1
@@ -68,8 +65,7 @@ def test_send_mail_with_user_locale(client, env):
event, user, organizer = env event, user, organizer = env
user.locale = 'de' user.locale = 'de'
user.save() user.save()
mail(user, _('User'), mail('dummy@dummy.dummy', _('User'), 'mailtest.txt', {}, event, locale=user.locale)
'mailtest.txt', {}, event)
del event.settings['mail_from'] del event.settings['mail_from']
assert len(djmail.outbox) == 1 assert len(djmail.outbox) == 1

View File

@@ -19,7 +19,7 @@ class LocaleDeterminationTest(TestCase):
) )
cls.TEST_LOCALE = 'de' if settings.LANGUAGE_CODE == 'en' else 'en' cls.TEST_LOCALE = 'de' if settings.LANGUAGE_CODE == 'en' else 'en'
cls.TEST_LOCALE_LONG = 'de-AT' if settings.LANGUAGE_CODE == 'en' else 'en-NZ' cls.TEST_LOCALE_LONG = 'de-AT' if settings.LANGUAGE_CODE == 'en' else 'en-NZ'
cls.user = User.objects.create_user('dummy@dummy.dummy', 'dummy@dummy.dummy', 'dummy') cls.user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
def test_global_default(self): def test_global_default(self):
c = Client() c = Client()

View File

@@ -160,31 +160,8 @@ class VersionableTestCase(TestCase):
class UserTestCase(TestCase): class UserTestCase(TestCase):
def test_identifier_local(self):
o = Organizer.objects.create(name='Dummy', slug='dummy')
event = Event.objects.create(
organizer=o, name='Dummy', slug='dummy',
date_from=now(),
)
u = User(event=event, username='tester')
u.set_password("test")
u.save()
self.assertEqual(u.identifier, "%s@%s.event.pretix" % (u.username.lower(), event.id))
def test_identifier_global(self):
u = User(email='test@example.com')
u.set_password("test")
u.save()
self.assertEqual(u.identifier, "test@example.com")
def test_name(self): def test_name(self):
o = Organizer.objects.create(name='Dummy', slug='dummy') u = User.objects.create_user('test@foo.bar', 'test')
event = Event.objects.create(
organizer=o, name='Dummy', slug='dummy',
date_from=now(),
)
u = User.objects.create_local_user(event, 'test', 'test')
self.assertEqual(u.get_local_name(), 'test')
u.givenname = "Christopher" u.givenname = "Christopher"
u.familyname = "Nolan" u.familyname = "Nolan"
u.set_password("test") u.set_password("test")
@@ -202,8 +179,8 @@ class UserTestCase(TestCase):
self.assertEqual(u.get_short_name(), 'Christopher') self.assertEqual(u.get_short_name(), 'Christopher')
u.givenname = None u.givenname = None
u.save() u.save()
self.assertEqual(u.get_full_name(), 'test') self.assertEqual(u.get_full_name(), 'test@foo.bar')
self.assertEqual(u.get_short_name(), 'test') self.assertEqual(u.get_short_name(), 'test@foo.bar')
class BaseQuotaTestCase(TestCase): class BaseQuotaTestCase(TestCase):
@@ -330,7 +307,7 @@ class OrderTestCase(BaseQuotaTestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.user = User.objects.create_local_user(self.event, 'dummy', 'dummy') self.user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
self.order = Order.objects.create( self.order = Order.objects.create(
status=Order.STATUS_PENDING, event=self.event, status=Order.STATUS_PENDING, event=self.event,
user=self.user, datetime=now() - timedelta(days=5), user=self.user, datetime=now() - timedelta(days=5),

View File

@@ -152,7 +152,7 @@ class SettingsTestCase(TestCase):
self._test_serialization(self.event, Event) self._test_serialization(self.event, Event)
def test_serialize_model(self): def test_serialize_model(self):
self._test_serialization(User.objects.create_local_user(self.event, 'dummy', 'dummy'), User) self._test_serialization(User.objects.create_user('dummy@dummy.dummy', 'dummy'), User)
def test_serialize_unknown(self): def test_serialize_unknown(self):
class Type: class Type:

View File

@@ -8,7 +8,7 @@ class LoginFormBrowserTest(BrowserTest):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.user = User.objects.create_user('dummy@dummy.dummy', 'dummy@dummy.dummy', 'dummy') self.user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
def test_login(self): def test_login(self):
self.driver.implicitly_wait(10) self.driver.implicitly_wait(10)
@@ -38,7 +38,7 @@ class LoginFormTest(TestCase):
""" """
def setUp(self): def setUp(self):
self.user = User.objects.create_user('dummy@dummy.dummy', 'dummy@dummy.dummy', 'dummy') self.user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
def test_wrong_credentials(self): def test_wrong_credentials(self):
c = Client() c = Client()

View File

@@ -11,7 +11,7 @@ class EventsTest(BrowserTest):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.user = User.objects.create_user('dummy@dummy.dummy', 'dummy@dummy.dummy', 'dummy') self.user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
self.orga1 = Organizer.objects.create(name='CCC', slug='ccc') self.orga1 = Organizer.objects.create(name='CCC', slug='ccc')
self.orga2 = Organizer.objects.create(name='MRM', slug='mrm') self.orga2 = Organizer.objects.create(name='MRM', slug='mrm')
self.event1 = Event.objects.create( self.event1 = Event.objects.create(

View File

@@ -16,7 +16,7 @@ from pretix.base.models import (
class ItemFormTest(BrowserTest): class ItemFormTest(BrowserTest):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.user = User.objects.create_user('dummy@dummy.dummy', 'dummy@dummy.dummy', 'dummy') self.user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
self.orga1 = Organizer.objects.create(name='CCC', slug='ccc') self.orga1 = Organizer.objects.create(name='CCC', slug='ccc')
self.orga2 = Organizer.objects.create(name='MRM', slug='mrm') self.orga2 = Organizer.objects.create(name='MRM', slug='mrm')
self.event1 = Event.objects.create( self.event1 = Event.objects.create(

View File

@@ -17,7 +17,7 @@ def env():
organizer=o, name='Dummy', slug='dummy', organizer=o, name='Dummy', slug='dummy',
date_from=now(), plugins='pretix.plugins.banktransfer' date_from=now(), plugins='pretix.plugins.banktransfer'
) )
user = User.objects.create_user('dummy@dummy.dummy', 'dummy@dummy.dummy', 'dummy') user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
EventPermission.objects.create( EventPermission.objects.create(
event=event, event=event,
user=user, user=user,
@@ -46,7 +46,7 @@ def env():
@pytest.mark.django_db @pytest.mark.django_db
def test_order_list(client, env): def test_order_list(client, env):
client.login(identifier='dummy@dummy.dummy', password='dummy') client.login(email='dummy@dummy.dummy', password='dummy')
response = client.get('/control/event/dummy/dummy/orders/') response = client.get('/control/event/dummy/dummy/orders/')
assert 'FOO' in response.rendered_content assert 'FOO' in response.rendered_content
response = client.get('/control/event/dummy/dummy/orders/?user=peter') response = client.get('/control/event/dummy/dummy/orders/?user=peter')
@@ -61,7 +61,7 @@ def test_order_list(client, env):
@pytest.mark.django_db @pytest.mark.django_db
def test_order_detail(client, env): def test_order_detail(client, env):
client.login(identifier='dummy@dummy.dummy', password='dummy') client.login(email='dummy@dummy.dummy', password='dummy')
response = client.get('/control/event/dummy/dummy/orders/FOO/') response = client.get('/control/event/dummy/dummy/orders/FOO/')
assert 'Early-bird' in response.rendered_content assert 'Early-bird' in response.rendered_content
assert 'Peter' in response.rendered_content assert 'Peter' in response.rendered_content
@@ -69,7 +69,7 @@ def test_order_detail(client, env):
@pytest.mark.django_db @pytest.mark.django_db
def test_order_transition_cancel(client, env): def test_order_transition_cancel(client, env):
client.login(identifier='dummy@dummy.dummy', password='dummy') client.login(email='dummy@dummy.dummy', password='dummy')
client.post('/control/event/dummy/dummy/orders/FOO/transition', { client.post('/control/event/dummy/dummy/orders/FOO/transition', {
'status': 'c' 'status': 'c'
}) })
@@ -79,7 +79,7 @@ def test_order_transition_cancel(client, env):
@pytest.mark.django_db @pytest.mark.django_db
def test_order_transition_to_paid_success(client, env): def test_order_transition_to_paid_success(client, env):
client.login(identifier='dummy@dummy.dummy', password='dummy') client.login(email='dummy@dummy.dummy', password='dummy')
client.post('/control/event/dummy/dummy/orders/FOO/transition', { client.post('/control/event/dummy/dummy/orders/FOO/transition', {
'status': 'p' 'status': 'p'
}) })
@@ -92,7 +92,7 @@ def test_order_transition_to_unpaid_success(client, env):
o = Order.objects.current.get(identity=env[2].identity) o = Order.objects.current.get(identity=env[2].identity)
o.status = Order.STATUS_PAID o.status = Order.STATUS_PAID
o.save() o.save()
client.login(identifier='dummy@dummy.dummy', password='dummy') client.login(email='dummy@dummy.dummy', password='dummy')
client.post('/control/event/dummy/dummy/orders/FOO/transition', { client.post('/control/event/dummy/dummy/orders/FOO/transition', {
'status': 'n' 'status': 'n'
}) })

View File

@@ -15,7 +15,7 @@ def env():
organizer=o, name='Dummy', slug='dummy', organizer=o, name='Dummy', slug='dummy',
date_from=now(), plugins='pretix.plugins.banktransfer' date_from=now(), plugins='pretix.plugins.banktransfer'
) )
user = User.objects.create_user('dummy@dummy.dummy', 'dummy@dummy.dummy', 'dummy') user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
Order.objects.create( Order.objects.create(
code='FOO', event=event, code='FOO', event=event,
user=user, status=Order.STATUS_PENDING, user=user, status=Order.STATUS_PENDING,
@@ -86,7 +86,7 @@ def test_logged_out(client, env, url):
@pytest.mark.django_db @pytest.mark.django_db
@pytest.mark.parametrize("url", event_urls) @pytest.mark.parametrize("url", event_urls)
def test_wrong_event(client, env, url): def test_wrong_event(client, env, url):
client.login(identifier='dummy@dummy.dummy', password='dummy') client.login(email='dummy@dummy.dummy', password='dummy')
response = client.get('/control/event/dummy/dummy/' + url) response = client.get('/control/event/dummy/dummy/' + url)
# These permission violations do not yield a 403 error, but # These permission violations do not yield a 403 error, but
# a 404 error to prevent information leakage # a 404 error to prevent information leakage
@@ -136,7 +136,7 @@ def test_wrong_event_permission(client, env, perm, url, code):
) )
setattr(ep, perm, False) setattr(ep, perm, False)
ep.save() ep.save()
client.login(identifier='dummy@dummy.dummy', password='dummy') client.login(email='dummy@dummy.dummy', password='dummy')
response = client.get('/control/event/dummy/dummy/' + url) response = client.get('/control/event/dummy/dummy/' + url)
assert response.status_code == 403 assert response.status_code == 403
@@ -148,7 +148,7 @@ def test_current_permission(client, env):
) )
setattr(ep, 'can_change_settings', True) setattr(ep, 'can_change_settings', True)
ep.save() ep.save()
client.login(identifier='dummy@dummy.dummy', password='dummy') client.login(email='dummy@dummy.dummy', password='dummy')
response = client.get('/control/event/dummy/dummy/settings/') response = client.get('/control/event/dummy/dummy/settings/')
assert response.status_code == 200 assert response.status_code == 200
ep = ep.clone() ep = ep.clone()
@@ -166,7 +166,7 @@ def test_correct_event_permission(client, env, perm, url, code):
) )
setattr(ep, perm, True) setattr(ep, perm, True)
ep.save() ep.save()
client.login(identifier='dummy@dummy.dummy', password='dummy') client.login(email='dummy@dummy.dummy', password='dummy')
response = client.get('/control/event/dummy/dummy/' + url) response = client.get('/control/event/dummy/dummy/' + url)
assert response.status_code == code assert response.status_code == code
@@ -174,7 +174,7 @@ def test_correct_event_permission(client, env, perm, url, code):
@pytest.mark.django_db @pytest.mark.django_db
@pytest.mark.parametrize("url", organizer_urls) @pytest.mark.parametrize("url", organizer_urls)
def test_wrong_organizer(client, env, url): def test_wrong_organizer(client, env, url):
client.login(identifier='dummy@dummy.dummy', password='dummy') client.login(email='dummy@dummy.dummy', password='dummy')
response = client.get('/control/' + url) response = client.get('/control/' + url)
# These permission violations do not yield a 403 error, but # These permission violations do not yield a 403 error, but
# a 404 error to prevent information leakage # a 404 error to prevent information leakage
@@ -195,7 +195,7 @@ def test_wrong_organizer_permission(client, env, perm, url, code):
) )
setattr(op, perm, False) setattr(op, perm, False)
op.save() op.save()
client.login(identifier='dummy@dummy.dummy', password='dummy') client.login(email='dummy@dummy.dummy', password='dummy')
response = client.get('/control/' + url) response = client.get('/control/' + url)
assert response.status_code == 403 assert response.status_code == 403
@@ -209,6 +209,6 @@ def test_correct_organizer_permission(client, env, perm, url, code):
if perm: if perm:
setattr(op, perm, True) setattr(op, perm, True)
op.save() op.save()
client.login(identifier='dummy@dummy.dummy', password='dummy') client.login(email='dummy@dummy.dummy', password='dummy')
response = client.get('/control/' + url) response = client.get('/control/' + url)
assert response.status_code == code assert response.status_code == code

View File

@@ -6,7 +6,7 @@ from pretix.base.models import User
class UserSettingsTest(BrowserTest): class UserSettingsTest(BrowserTest):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.user = User.objects.create_global_user('dummy@dummy.dummy', 'dummy') self.user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
self.driver.implicitly_wait(10) self.driver.implicitly_wait(10)
self.driver.get('%s%s' % (self.live_server_url, '/control/login')) self.driver.get('%s%s' % (self.live_server_url, '/control/login'))
username_input = self.driver.find_element_by_name("email") username_input = self.driver.find_element_by_name("email")
@@ -47,7 +47,7 @@ class UserSettingsTest(BrowserTest):
assert self.user.email == 'foo@example.com' assert self.user.email == 'foo@example.com'
def test_change_email_no_duplicates(self): def test_change_email_no_duplicates(self):
User.objects.create_global_user('foo@example.com', 'foo') User.objects.create_user('foo@example.com', 'foo')
self.driver.find_element_by_name("email").clear() self.driver.find_element_by_name("email").clear()
self.driver.find_element_by_name("email").send_keys("foo@example.com") self.driver.find_element_by_name("email").send_keys("foo@example.com")
self.driver.find_element_by_name("old_pw").clear() self.driver.find_element_by_name("old_pw").clear()

View File

@@ -17,7 +17,7 @@ def env():
organizer=o, name='Dummy', slug='dummy', organizer=o, name='Dummy', slug='dummy',
date_from=now(), plugins='pretix.plugins.banktransfer' date_from=now(), plugins='pretix.plugins.banktransfer'
) )
user = User.objects.create_user('dummy@dummy.dummy', 'dummy@dummy.dummy', 'dummy') user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
EventPermission.objects.create(user=user, event=event) EventPermission.objects.create(user=user, event=event)
o1 = Order.objects.create( o1 = Order.objects.create(
code='1234S', event=event, code='1234S', event=event,
@@ -40,7 +40,7 @@ def env():
@pytest.mark.django_db @pytest.mark.django_db
def test_import_csv_file(client, env): def test_import_csv_file(client, env):
client.login(identifier='dummy@dummy.dummy', password='dummy') client.login(email='dummy@dummy.dummy', password='dummy')
r = client.get('/control/event/dummy/dummy/banktransfer/import/') r = client.get('/control/event/dummy/dummy/banktransfer/import/')
assert r.status_code == 200 assert r.status_code == 200

View File

@@ -17,7 +17,7 @@ def env():
organizer=o, name='Dummy', slug='dummy', organizer=o, name='Dummy', slug='dummy',
date_from=now(), plugins='pretix.plugins.banktransfer' date_from=now(), plugins='pretix.plugins.banktransfer'
) )
user = User.objects.create_user('dummy@dummy.dummy', 'dummy@dummy.dummy', 'dummy') user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
EventPermission.objects.create(user=user, event=event) EventPermission.objects.create(user=user, event=event)
shirt = Item.objects.create(event=event, name='T-Shirt', default_price=12) shirt = Item.objects.create(event=event, name='T-Shirt', default_price=12)
prop1 = Property.objects.create(event=event, name="Color") prop1 = Property.objects.create(event=event, name="Color")
@@ -48,7 +48,7 @@ def env():
@pytest.mark.django_db @pytest.mark.django_db
def test_pretixdroid(client, env): def test_pretixdroid(client, env):
client.login(identifier='dummy@dummy.dummy', password='dummy') client.login(email='dummy@dummy.dummy', password='dummy')
client.get('/control/event/%s/%s/pretixdroid/' % (env[0].organizer.slug, env[0].slug)) client.get('/control/event/%s/%s/pretixdroid/' % (env[0].organizer.slug, env[0].slug))
key1 = env[0].settings.get('pretixdroid_key') key1 = env[0].settings.get('pretixdroid_key')
assert key1 assert key1

View File

@@ -9,14 +9,14 @@ from pretix.base.models import User
class UserSettingsTest(EventTestMixin, BrowserTest): class UserSettingsTest(EventTestMixin, BrowserTest):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.user = User.objects.create_global_user('dummy@dummy.dummy', 'dummy') self.user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
self.driver.implicitly_wait(10) self.driver.implicitly_wait(10)
self.driver.get('%s/%s/%s/login' % (self.live_server_url, self.orga.slug, self.event.slug)) self.driver.get('%s/%s/%s/login' % (self.live_server_url, self.orga.slug, self.event.slug))
# open the login accordion # open the login accordion
self.scroll_and_click(self.driver.find_element_by_css_selector('a[href*=loginForm]')) self.scroll_and_click(self.driver.find_element_by_css_selector('a[href*=loginForm]'))
time.sleep(1) time.sleep(1)
# enter login details # enter login details
self.driver.find_element_by_css_selector('#loginForm input[name=username]').send_keys('dummy@dummy.dummy') self.driver.find_element_by_css_selector('#loginForm input[name=email]').send_keys('dummy@dummy.dummy')
self.driver.find_element_by_css_selector('#loginForm input[name=password]').send_keys('dummy') self.driver.find_element_by_css_selector('#loginForm input[name=password]').send_keys('dummy')
self.scroll_and_click(self.driver.find_element_by_css_selector('#loginForm button.btn-primary')) self.scroll_and_click(self.driver.find_element_by_css_selector('#loginForm button.btn-primary'))
self.driver.find_element_by_partial_link_text('Your account') self.driver.find_element_by_partial_link_text('Your account')
@@ -51,19 +51,8 @@ class UserSettingsTest(EventTestMixin, BrowserTest):
self.user = User.objects.get(pk=self.user.pk) self.user = User.objects.get(pk=self.user.pk)
assert self.user.email == 'foo@example.com' assert self.user.email == 'foo@example.com'
def test_change_email_allow_local_duplicates(self): def test_change_email_no_duplicates(self):
User.objects.create_local_user(event=self.event, username='test', email='foo@example.com', password='foo') User.objects.create_user('foo@example.com', 'foo')
self.driver.find_element_by_name("email").clear()
self.driver.find_element_by_name("email").send_keys("foo@example.com")
self.driver.find_element_by_name("old_pw").clear()
self.driver.find_element_by_name("old_pw").send_keys("dummy")
self.scroll_and_click(self.driver.find_element_by_class_name('btn-save'))
self.driver.find_element_by_class_name("alert-success")
self.user = User.objects.get(pk=self.user.pk)
assert self.user.email == 'foo@example.com'
def test_change_email_no_global_duplicates(self):
User.objects.create_global_user('foo@example.com', 'foo')
self.driver.find_element_by_name("email").clear() self.driver.find_element_by_name("email").clear()
self.driver.find_element_by_name("email").send_keys("foo@example.com") self.driver.find_element_by_name("email").send_keys("foo@example.com")
self.driver.find_element_by_name("old_pw").clear() self.driver.find_element_by_name("old_pw").clear()

View File

@@ -22,7 +22,7 @@ class CartTestMixin:
organizer=self.orga, name='30C3', slug='30c3', organizer=self.orga, name='30C3', slug='30c3',
date_from=datetime.datetime(2013, 12, 26, tzinfo=datetime.timezone.utc) date_from=datetime.datetime(2013, 12, 26, tzinfo=datetime.timezone.utc)
) )
self.user = User.objects.create_local_user(self.event, 'demo', 'demo') self.user = User.objects.create_user('dummy@dummy.dummy', 'demo')
self.category = ItemCategory.objects.create(event=self.event, name="Everything", position=0) self.category = ItemCategory.objects.create(event=self.event, name="Everything", position=0)
self.quota_shirts = Quota.objects.create(event=self.event, name='Shirts', size=2) self.quota_shirts = Quota.objects.create(event=self.event, name='Shirts', size=2)
self.shirt = Item.objects.create(event=self.event, name='T-Shirt', category=self.category, default_price=12) self.shirt = Item.objects.create(event=self.event, name='T-Shirt', category=self.category, default_price=12)
@@ -51,7 +51,7 @@ class CartBrowserTest(CartTestMixin, BrowserTest):
self.driver.find_element_by_css_selector('input[type=number][name=item_%s]' % self.ticket.identity).send_keys('1') self.driver.find_element_by_css_selector('input[type=number][name=item_%s]' % self.ticket.identity).send_keys('1')
self.scroll_and_click(self.driver.find_element_by_css_selector('.checkout-button-row button')) self.scroll_and_click(self.driver.find_element_by_css_selector('.checkout-button-row button'))
# should redirect to login page # should redirect to login page
self.driver.find_element_by_name('username') self.driver.find_element_by_name('email')
def test_simple_login(self): def test_simple_login(self):
self.driver.get('%s/%s/%s/' % (self.live_server_url, self.orga.slug, self.event.slug)) self.driver.get('%s/%s/%s/' % (self.live_server_url, self.orga.slug, self.event.slug))
@@ -63,44 +63,26 @@ class CartBrowserTest(CartTestMixin, BrowserTest):
self.scroll_and_click(self.driver.find_element_by_css_selector('a[href*=loginForm]')) self.scroll_and_click(self.driver.find_element_by_css_selector('a[href*=loginForm]'))
time.sleep(1) time.sleep(1)
# enter login details # enter login details
self.driver.find_element_by_css_selector('#loginForm input[name=username]').send_keys('demo') self.driver.find_element_by_css_selector('#loginForm input[name=email]').send_keys('dummy@dummy.dummy')
self.driver.find_element_by_css_selector('#loginForm input[name=password]').send_keys('demo') self.driver.find_element_by_css_selector('#loginForm input[name=password]').send_keys('demo')
self.scroll_and_click(self.driver.find_element_by_css_selector('#loginForm button.btn-primary')) self.scroll_and_click(self.driver.find_element_by_css_selector('#loginForm button.btn-primary'))
# should display our ticket # should display our ticket
self.assertIn('Early-bird', self.driver.find_element_by_css_selector('.cart-row:first-child').text) self.assertIn('Early-bird', self.driver.find_element_by_css_selector('.cart-row:first-child').text)
def test_local_registration(self): def test_registration(self):
self.driver.get('%s/%s/%s/' % (self.live_server_url, self.orga.slug, self.event.slug)) self.driver.get('%s/%s/%s/' % (self.live_server_url, self.orga.slug, self.event.slug))
# add the entry ticket to cart # add the entry ticket to cart
self.driver.find_element_by_css_selector('input[type=number][name=item_%s]' % self.ticket.identity).send_keys('1') self.driver.find_element_by_css_selector('input[type=number][name=item_%s]' % self.ticket.identity).send_keys('1')
self.scroll_and_click(self.driver.find_element_by_css_selector('.checkout-button-row button')) self.scroll_and_click(self.driver.find_element_by_css_selector('.checkout-button-row button'))
# should redirect to login page # should redirect to login page
# open the login accordion # open the login accordion
self.scroll_and_click(self.driver.find_element_by_css_selector('a[href*=localRegistrationForm]')) self.scroll_and_click(self.driver.find_element_by_css_selector('a[href*=registrationForm]'))
time.sleep(1) time.sleep(1)
# enter login details # enter login details
self.driver.find_element_by_css_selector('#localRegistrationForm input[name=username]').send_keys('demo2') self.driver.find_element_by_css_selector('#registrationForm input[name=email]').send_keys('demo@example.com')
self.driver.find_element_by_css_selector('#localRegistrationForm input[name=email]').send_keys('demo@demo.demo') self.driver.find_element_by_css_selector('#registrationForm input[name=password]').send_keys('demo')
self.driver.find_element_by_css_selector('#localRegistrationForm input[name=password]').send_keys('demo') self.driver.find_element_by_css_selector('#registrationForm input[name=password_repeat]').send_keys('demo')
self.driver.find_element_by_css_selector('#localRegistrationForm input[name=password_repeat]').send_keys('demo') self.scroll_and_click(self.driver.find_element_by_css_selector('#registrationForm button.btn-primary'))
self.scroll_and_click(self.driver.find_element_by_css_selector('#localRegistrationForm button.btn-primary'))
# should display our ticket
self.assertIn('Early-bird', self.driver.find_element_by_css_selector('.cart-row:first-child').text)
def test_global_registration(self):
self.driver.get('%s/%s/%s/' % (self.live_server_url, self.orga.slug, self.event.slug))
# add the entry ticket to cart
self.driver.find_element_by_css_selector('input[type=number][name=item_%s]' % self.ticket.identity).send_keys('1')
self.scroll_and_click(self.driver.find_element_by_css_selector('.checkout-button-row button'))
# should redirect to login page
# open the login accordion
self.scroll_and_click(self.driver.find_element_by_css_selector('a[href*=globalRegistrationForm]'))
time.sleep(1)
# enter login details
self.driver.find_element_by_css_selector('#globalRegistrationForm input[name=email]').send_keys('demo@example.com')
self.driver.find_element_by_css_selector('#globalRegistrationForm input[name=password]').send_keys('demo')
self.driver.find_element_by_css_selector('#globalRegistrationForm input[name=password_repeat]').send_keys('demo')
self.scroll_and_click(self.driver.find_element_by_css_selector('#globalRegistrationForm button.btn-primary'))
# should display our ticket # should display our ticket
self.assertIn('Early-bird', self.driver.find_element_by_css_selector('.cart-row:first-child').text) self.assertIn('Early-bird', self.driver.find_element_by_css_selector('.cart-row:first-child').text)
@@ -109,7 +91,7 @@ class CartTest(CartTestMixin, TestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.assertTrue(self.client.login(username='demo@%s.event.pretix' % self.event.identity, password='demo')) self.assertTrue(self.client.login(email='dummy@dummy.dummy', password='demo'))
def test_simple(self): def test_simple(self):
response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {

View File

@@ -21,13 +21,13 @@ class CheckoutTestCase(TestCase):
date_from=datetime.datetime(2013, 12, 26, tzinfo=datetime.timezone.utc), date_from=datetime.datetime(2013, 12, 26, tzinfo=datetime.timezone.utc),
plugins='pretix.plugins.stripe,pretix.plugins.banktransfer' plugins='pretix.plugins.stripe,pretix.plugins.banktransfer'
) )
self.user = User.objects.create_local_user(self.event, 'demo', 'demo') self.user = User.objects.create_user('dummy@dummy.dummy', 'demo')
self.category = ItemCategory.objects.create(event=self.event, name="Everything", position=0) self.category = ItemCategory.objects.create(event=self.event, name="Everything", position=0)
self.quota_tickets = Quota.objects.create(event=self.event, name='Tickets', size=5) self.quota_tickets = Quota.objects.create(event=self.event, name='Tickets', size=5)
self.ticket = Item.objects.create(event=self.event, name='Early-bird ticket', self.ticket = Item.objects.create(event=self.event, name='Early-bird ticket',
category=self.category, default_price=23, admission=True) category=self.category, default_price=23, admission=True)
self.quota_tickets.items.add(self.ticket) self.quota_tickets.items.add(self.ticket)
self.assertTrue(self.client.login(username='demo@%s.event.pretix' % self.event.identity, password='demo')) self.assertTrue(self.client.login(email='dummy@dummy.dummy', password='demo'))
self.event.settings.set('attendee_names_asked', False) self.event.settings.set('attendee_names_asked', False)
self.event.settings.set('payment_banktransfer__enabled', True) self.event.settings.set('payment_banktransfer__enabled', True)

View File

@@ -144,52 +144,40 @@ class LoginTest(EventTestMixin, TestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.local_user = User.objects.create_local_user(self.event, 'demo', 'foo') self.user = User.objects.create_user('demo@demo.dummy', 'demo')
self.global_user = User.objects.create_global_user('demo@demo.dummy', 'demo')
def test_login_invalid(self): def test_login_invalid(self):
response = self.client.post( response = self.client.post(
'/%s/%s/login' % (self.orga.slug, self.event.slug), '/%s/%s/login' % (self.orga.slug, self.event.slug),
{ {
'form': 'login', 'form': 'login',
'username': 'demo', 'email': 'demo@demo.foo',
'password': 'bar' 'password': 'bar'
} }
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertIn('alert-danger', response.rendered_content) self.assertIn('alert-danger', response.rendered_content)
def test_login_local(self): def test_login_valid(self):
response = self.client.post( response = self.client.post(
'/%s/%s/login' % (self.orga.slug, self.event.slug), '/%s/%s/login' % (self.orga.slug, self.event.slug),
{ {
'form': 'login', 'form': 'login',
'username': 'demo', 'email': 'demo@demo.dummy',
'password': 'foo'
}
)
self.assertEqual(response.status_code, 302)
def test_login_global(self):
response = self.client.post(
'/%s/%s/login' % (self.orga.slug, self.event.slug),
{
'form': 'login',
'username': 'demo@demo.dummy',
'password': 'demo' 'password': 'demo'
} }
) )
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
def test_login_already_logged_in(self): def test_login_already_logged_in(self):
self.assertTrue(self.client.login(username='demo@%s.event.pretix' % self.event.identity, password='foo')) self.assertTrue(self.client.login(email='demo@demo.dummy', password='demo'))
response = self.client.get( response = self.client.get(
'/%s/%s/login' % (self.orga.slug, self.event.slug), '/%s/%s/login' % (self.orga.slug, self.event.slug),
) )
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
def test_logout(self): def test_logout(self):
self.assertTrue(self.client.login(username='demo@%s.event.pretix' % self.event.identity, password='foo')) self.assertTrue(self.client.login(email='demo@demo.dummy', password='demo'))
response = self.client.get( response = self.client.get(
'/%s/%s/logout' % (self.orga.slug, self.event.slug), '/%s/%s/logout' % (self.orga.slug, self.event.slug),
) )

View File

@@ -23,9 +23,9 @@ class OrdersTest(TestCase):
) )
self.event.settings.set('payment_banktransfer__enabled', True) self.event.settings.set('payment_banktransfer__enabled', True)
self.event.settings.set('ticketoutput_testdummy__enabled', True) self.event.settings.set('ticketoutput_testdummy__enabled', True)
self.user = User.objects.create_local_user(self.event, 'demo', 'foo') self.user = User.objects.create_user('dummy@dummy.dummy', 'foo')
self.user2 = User.objects.create_local_user(self.event, 'bar', 'foo') self.user2 = User.objects.create_user('bar@dummy.dummy', 'foo')
self.assertTrue(self.client.login(username='demo@%s.event.pretix' % self.event.identity, password='foo')) self.assertTrue(self.client.login(email='dummy@dummy.dummy', password='foo'))
self.category = ItemCategory.objects.create(event=self.event, name="Everything", position=0) self.category = ItemCategory.objects.create(event=self.event, name="Everything", position=0)
self.quota_shirts = Quota.objects.create(event=self.event, name='Shirts', size=2) self.quota_shirts = Quota.objects.create(event=self.event, name='Shirts', size=2)