From 7def097dcde532a0a676f1344451faca6cdcab90 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Thu, 17 Sep 2015 00:52:36 +0200 Subject: [PATCH] Refs #96 -- Completely removed local users --- doc/admin/config.rst | 5 - doc/development/concepts.rst | 23 +- src/pretix/base/exporter.py | 2 +- src/pretix/base/forms/auth.py | 158 ++++++++++++ src/pretix/base/forms/user.py | 3 +- .../migrations/0015_auto_20150916_2219.py | 47 ++++ src/pretix/base/models.py | 127 ++-------- src/pretix/control/forms/auth.py | 85 ------- src/pretix/control/views/auth.py | 16 +- src/pretix/control/views/event.py | 2 +- src/pretix/control/views/orders.py | 3 +- src/pretix/presale/forms/auth.py | 227 ------------------ .../templates/pretixpresale/event/forgot.html | 2 +- .../templates/pretixpresale/event/login.html | 82 +++---- src/pretix/presale/views/__init__.py | 21 +- src/pretix/presale/views/cart.py | 9 +- src/pretix/presale/views/checkout.py | 8 +- src/pretix/presale/views/event.py | 63 ++--- src/pretix/presale/views/order.py | 14 +- src/pretix/settings.py | 1 - src/tests/base/test_mail.py | 2 +- src/tests/base/test_middleware.py | 2 +- src/tests/base/test_models.py | 31 +-- src/tests/base/test_settings.py | 2 +- src/tests/control/test_auth.py | 4 +- src/tests/control/test_events.py | 2 +- src/tests/control/test_items.py | 2 +- src/tests/control/test_orders.py | 12 +- src/tests/control/test_permissions.py | 16 +- src/tests/control/test_user.py | 4 +- src/tests/plugins/banktransfer/test_import.py | 4 +- src/tests/plugins/test_pretixdroid.py | 4 +- src/tests/presale/test_account.py | 19 +- src/tests/presale/test_cart.py | 38 +-- src/tests/presale/test_checkout.py | 4 +- src/tests/presale/test_event.py | 24 +- src/tests/presale/test_orders.py | 6 +- 37 files changed, 367 insertions(+), 707 deletions(-) create mode 100644 src/pretix/base/forms/auth.py create mode 100644 src/pretix/base/migrations/0015_auto_20150916_2219.py delete mode 100644 src/pretix/control/forms/auth.py delete mode 100644 src/pretix/presale/forms/auth.py diff --git a/doc/admin/config.rst b/doc/admin/config.rst index e0c4d6d72..7825ee58f 100644 --- a/doc/admin/config.rst +++ b/doc/admin/config.rst @@ -25,7 +25,6 @@ Example:: [pretix] instance_name=pretix.de - global_registration=off url=http://localhost currency=EUR cookiedomain=.pretix.de @@ -36,10 +35,6 @@ Example:: ``instance_name`` 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`` The installation's full URL, without a trailing slash. diff --git a/doc/development/concepts.rst b/doc/development/concepts.rst index 403881567..1baf60822 100644 --- a/doc/development/concepts.rst +++ b/doc/development/concepts.rst @@ -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 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 -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. +should be possible to order products **without** needing an user account. Items and variations diff --git a/src/pretix/base/exporter.py b/src/pretix/base/exporter.py index 4b8c61e52..fa25da2fc 100644 --- a/src/pretix/base/exporter.py +++ b/src/pretix/base/exporter.py @@ -121,7 +121,7 @@ class JSONExporter(BaseExporter): { 'code': o.code, 'status': o.status, - 'user': o.user.identifier, + 'user': o.user.email, 'datetime': o.datetime, 'payment_fee': o.payment_fee, 'total': o.total, diff --git a/src/pretix/base/forms/auth.py b/src/pretix/base/forms/auth.py new file mode 100644 index 000000000..6421cd1ce --- /dev/null +++ b/src/pretix/base/forms/auth.py @@ -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' + ) diff --git a/src/pretix/base/forms/user.py b/src/pretix/base/forms/user.py index a97dd51e4..0ff68895a 100644 --- a/src/pretix/base/forms/user.py +++ b/src/pretix/base/forms/user.py @@ -59,7 +59,7 @@ class UserSettingsForm(forms.ModelForm): def clean_email(self): 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( self.error_messages['duplicate_identifier'], code='duplicate_identifier', @@ -88,6 +88,5 @@ class UserSettingsForm(forms.ModelForm): if password1: self.instance.set_password(password1) - self.instance.identifier = email return self.cleaned_data diff --git a/src/pretix/base/migrations/0015_auto_20150916_2219.py b/src/pretix/base/migrations/0015_auto_20150916_2219.py new file mode 100644 index 000000000..7fd26f839 --- /dev/null +++ b/src/pretix/base/migrations/0015_auto_20150916_2219.py @@ -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', + ), + ] diff --git a/src/pretix/base/models.py b/src/pretix/base/models.py index cf2ded385..52678fdf7 100644 --- a/src/pretix/base/models.py +++ b/src/pretix/base/models.py @@ -1,5 +1,6 @@ import copy import random +import string import uuid from datetime import datetime from itertools import product @@ -88,34 +89,17 @@ class UserManager(BaseUserManager): model documentation to see what's so special about our user model. """ - def create_user(self, identifier, username, password=None): - user = self.model(identifier=identifier) + def create_user(self, email, password=None, **kwargs): + user = self.model(email=email, **kwargs) user.set_password(password) user.save() return user - def create_global_user(self, email, password=None, **kwargs): - 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 + def create_superuser(self, email, password=None): # NOQA # Not used in the software but required by Django if password is None: 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_superuser = True user.set_password(password) @@ -126,44 +110,8 @@ class UserManager(BaseUserManager): class User(AbstractBaseUser, PermissionsMixin): """ 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 - 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 + :param email: The user's e-mail address, used for identification. :type email: str :param givenname: The user's given name. May be empty or null. :type givenname: str @@ -183,24 +131,14 @@ class User(AbstractBaseUser, PermissionsMixin): :type timezone: str """ - USERNAME_FIELD = 'identifier' - REQUIRED_FIELDS = ['username'] + USERNAME_FIELD = 'email' + REQUIRED_FIELDS = [] - identifier = models.CharField(max_length=255, unique=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, + email = models.EmailField(unique=True, db_index=True, null=True, blank=True, verbose_name=_('E-mail')) - givenname = models.CharField(max_length=255, blank=True, - null=True, + givenname = models.CharField(max_length=255, blank=True, null=True, verbose_name=_('Given name')) - familyname = models.CharField(max_length=255, blank=True, - null=True, + familyname = models.CharField(max_length=255, blank=True, null=True, verbose_name=_('Family name')) is_active = models.BooleanField(default=True, verbose_name=_('Is active')) @@ -221,32 +159,20 @@ class User(AbstractBaseUser, PermissionsMixin): class Meta: verbose_name = _("User") verbose_name_plural = _("Users") - unique_together = (("event", "username"),) - - def __str__(self): - return self.identifier def save(self, *args, **kwargs): - """ - 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() + self.email = self.email.lower() super().save(*args, **kwargs) + def __str__(self): + return self.email + def get_short_name(self) -> str: """ Returns the first of the following user properties that is found to exist: * Given name * Family name - * User name * E-mail address """ if self.givenname: @@ -254,7 +180,7 @@ class User(AbstractBaseUser, PermissionsMixin): elif self.familyname: return self.familyname else: - return self.get_local_name() + return self.email def get_full_name(self) -> str: """ @@ -264,7 +190,6 @@ class User(AbstractBaseUser, PermissionsMixin): * Given name * Family name * User name - * E-mail address """ if self.givenname and not self.familyname: return self.givenname @@ -276,18 +201,7 @@ class User(AbstractBaseUser, PermissionsMixin): 'given': self.givenname } 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.identifier # NOQA def cachedfile_name(instance, filename): @@ -1450,6 +1364,10 @@ class Quota(Versionable): pass +def generate_secret(): + return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(32)) + + class Order(Versionable): """ An order is created when a user clicks 'buy' on his cart. It holds @@ -1524,6 +1442,7 @@ class Order(Versionable): verbose_name=_("User"), related_name="orders" ) + secret = models.CharField(max_length=32, default=generate_secret) datetime = models.DateTimeField( verbose_name=_("Date") ) @@ -1824,6 +1743,10 @@ class CartPosition(ObjectWithAnswers, Versionable): User, null=True, blank=True, verbose_name=_("User") ) + session = models.CharField( + max_length=255, null=True, blank=True, + verbose_name=_("Session") + ) item = VersionedForeignKey( Item, verbose_name=_("Item") diff --git a/src/pretix/control/forms/auth.py b/src/pretix/control/forms/auth.py deleted file mode 100644 index ac224e0d3..000000000 --- a/src/pretix/control/forms/auth.py +++ /dev/null @@ -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 diff --git a/src/pretix/control/views/auth.py b/src/pretix/control/views/auth.py index 3fe1c476d..73a1e16a0 100644 --- a/src/pretix/control/views/auth.py +++ b/src/pretix/control/views/auth.py @@ -4,10 +4,8 @@ from django.contrib.auth import ( ) from django.shortcuts import redirect, render +from pretix.base.forms.auth import LoginForm, RegistrationForm from pretix.base.models import User -from pretix.control.forms.auth import ( - AuthenticationForm, GlobalRegistrationForm, -) def login(request): @@ -21,14 +19,14 @@ def login(request): return redirect(request.GET.get("next", 'control:index')) return redirect('control:index') if request.method == 'POST': - form = AuthenticationForm(data=request.POST) + form = LoginForm(data=request.POST) if form.is_valid() and form.user_cache: auth_login(request, form.user_cache) if "next" in request.GET: return redirect(request.GET.get("next", 'control:index')) return redirect('control:index') else: - form = AuthenticationForm() + form = LoginForm() ctx['form'] = form 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('control:index') if request.method == 'POST': - form = GlobalRegistrationForm(data=request.POST) + form = RegistrationForm(data=request.POST) if form.is_valid(): - user = User.objects.create_global_user( + user = User.objects.create_user( form.cleaned_data['email'], form.cleaned_data['password'], 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']) + user = authenticate(email=user.email, password=form.cleaned_data['password']) auth_login(request, user) return redirect('control:index') else: - form = GlobalRegistrationForm() + form = RegistrationForm() ctx['form'] = form return render(request, 'pretixcontrol/auth/register.html', ctx) diff --git a/src/pretix/control/views/event.py b/src/pretix/control/views/event.py index 7fcd6616d..16371e51e 100644 --- a/src/pretix/control/views/event.py +++ b/src/pretix/control/views/event.py @@ -314,7 +314,7 @@ class EventPermissions(EventPermissionRequiredMixin, TemplateView): if self.formset.is_valid() and self.add_form.is_valid(): if self.add_form.has_changed(): 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.event = self.request.event self.add_form.instance.event_id = self.request.event.identity diff --git a/src/pretix/control/views/orders.py b/src/pretix/control/views/orders.py index c4936066d..490108adb 100644 --- a/src/pretix/control/views/orders.py +++ b/src/pretix/control/views/orders.py @@ -39,8 +39,7 @@ class OrderList(EventPermissionRequiredMixin, ListView): if self.request.GET.get("user", "") != "": u = self.request.GET.get("user", "") qs = qs.filter( - Q(user__identifier__icontains=u) | Q(user__email__icontains=u) - | Q(user__givenname__icontains=u) | Q(user__familyname__icontains=u) + Q(user__email__icontains=u) | Q(user__givenname__icontains=u) | Q(user__familyname__icontains=u) ) if self.request.GET.get("status", "") != "": s = self.request.GET.get("status", "") diff --git a/src/pretix/presale/forms/auth.py b/src/pretix/presale/forms/auth.py deleted file mode 100644 index e9bbb5e27..000000000 --- a/src/pretix/presale/forms/auth.py +++ /dev/null @@ -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', - ) diff --git a/src/pretix/presale/templates/pretixpresale/event/forgot.html b/src/pretix/presale/templates/pretixpresale/event/forgot.html index d67a2358c..88b5faa7c 100644 --- a/src/pretix/presale/templates/pretixpresale/event/forgot.html +++ b/src/pretix/presale/templates/pretixpresale/event/forgot.html @@ -7,7 +7,7 @@
{% csrf_token %} {% bootstrap_form_errors form type='all' layout='inline' %} - {% bootstrap_field form.username layout="horizontal" %} + {% bootstrap_field form.email layout="horizontal" %}
diff --git a/src/pretix/presale/templates/pretixpresale/event/login.html b/src/pretix/presale/templates/pretixpresale/event/login.html index 77cb03faf..27aafb3e9 100644 --- a/src/pretix/presale/templates/pretixpresale/event/login.html +++ b/src/pretix/presale/templates/pretixpresale/event/login.html @@ -19,7 +19,7 @@ {% csrf_token %} {% 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" %}
@@ -39,67 +39,47 @@
- - {% if global_registration_form %} -
- -