core: add placeholders for forms, add sign-up view
This commit is contained in:
parent
cc12f1d8b3
commit
2a500b3e4a
|
@ -1,16 +1,22 @@
|
||||||
"""passbook core authentication forms"""
|
"""passbook core authentication forms"""
|
||||||
|
from logging import getLogger
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.validators import validate_email
|
from django.core.validators import validate_email
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from passbook.core.models import User
|
||||||
from passbook.lib.config import CONFIG
|
from passbook.lib.config import CONFIG
|
||||||
|
|
||||||
|
LOGGER = getLogger(__name__)
|
||||||
|
|
||||||
class LoginForm(forms.Form):
|
class LoginForm(forms.Form):
|
||||||
"""Allow users to login"""
|
"""Allow users to login"""
|
||||||
|
|
||||||
uid_field = forms.CharField()
|
title = _('Log in to your account')
|
||||||
password = forms.CharField(widget=forms.PasswordInput())
|
uid_field = forms.CharField(widget=forms.TextInput(attrs={'placeholder': _('UID')}))
|
||||||
|
password = forms.CharField(widget=forms.PasswordInput(attrs={'placeholder': _('Password')}))
|
||||||
remember_me = forms.BooleanField(required=False)
|
remember_me = forms.BooleanField(required=False)
|
||||||
|
|
||||||
def clean_uid_field(self):
|
def clean_uid_field(self):
|
||||||
|
@ -18,3 +24,55 @@ class LoginForm(forms.Form):
|
||||||
if CONFIG.y('passbook.uid_fields') == ['email']:
|
if CONFIG.y('passbook.uid_fields') == ['email']:
|
||||||
validate_email(self.cleaned_data.get('uid_field'))
|
validate_email(self.cleaned_data.get('uid_field'))
|
||||||
return self.cleaned_data.get('uid_field')
|
return self.cleaned_data.get('uid_field')
|
||||||
|
|
||||||
|
class SignUpForm(forms.Form):
|
||||||
|
"""SignUp Form"""
|
||||||
|
|
||||||
|
title = _('Sign Up')
|
||||||
|
first_name = forms.CharField(label=_('First Name'),
|
||||||
|
widget=forms.TextInput(attrs={'placeholder': _('First Name')}))
|
||||||
|
last_name = forms.CharField(label=_('Last Name'),
|
||||||
|
widget=forms.TextInput(attrs={'placeholder': _('Last Name')}))
|
||||||
|
username = forms.CharField(label=_('Username'),
|
||||||
|
widget=forms.TextInput(attrs={'placeholder': _('Username')}))
|
||||||
|
email = forms.EmailField(label=_('E-Mail'),
|
||||||
|
widget=forms.TextInput(attrs={'placeholder': _('E-Mail')}))
|
||||||
|
password = forms.CharField(label=_('Password'),
|
||||||
|
widget=forms.PasswordInput(attrs={'placeholder': _('Password')}))
|
||||||
|
password_repeat = forms.CharField(label=_('Repeat Password'),
|
||||||
|
widget=forms.PasswordInput(attrs={
|
||||||
|
'placeholder': _('Repeat Password')
|
||||||
|
}))
|
||||||
|
# captcha = ReCaptchaField(
|
||||||
|
# required=(not settings.DEBUG and not settings.TEST),
|
||||||
|
# private_key=Setting.get('recaptcha:private'),
|
||||||
|
# public_key=Setting.get('recaptcha:public'))
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
# TODO: Dynamically add captcha here
|
||||||
|
# if not Setting.get_bool('recaptcha:enabled'):
|
||||||
|
# self.fields.pop('captcha')
|
||||||
|
|
||||||
|
def clean_username(self):
|
||||||
|
"""Check if username is used already"""
|
||||||
|
username = self.cleaned_data.get('username')
|
||||||
|
if User.objects.filter(username=username).exists():
|
||||||
|
LOGGER.warning("Username %s already exists", username)
|
||||||
|
raise ValidationError(_("Username already exists"))
|
||||||
|
return username
|
||||||
|
|
||||||
|
def clean_email(self):
|
||||||
|
"""Check if email is already used in django or other auth sources"""
|
||||||
|
email = self.cleaned_data.get('email')
|
||||||
|
# Check if user exists already, error early
|
||||||
|
if User.objects.filter(email=email).exists():
|
||||||
|
LOGGER.debug("email %s exists in django", email)
|
||||||
|
raise ValidationError(_("Email already exists"))
|
||||||
|
return email
|
||||||
|
|
||||||
|
def clean_password_repeat(self):
|
||||||
|
"""Check if Password adheres to filter and if passwords matche"""
|
||||||
|
# TODO: Password policy? Via Plugin? via Policy?
|
||||||
|
# return check_password(self)
|
||||||
|
return self.cleaned_data.get('password_repeat')
|
||||||
|
|
|
@ -11,8 +11,8 @@ from django.utils.translation import ugettext as _
|
||||||
from django.views import View
|
from django.views import View
|
||||||
from django.views.generic import FormView
|
from django.views.generic import FormView
|
||||||
|
|
||||||
from passbook.core.forms.authentication import LoginForm
|
from passbook.core.forms.authentication import LoginForm, SignUpForm
|
||||||
from passbook.core.models import User
|
from passbook.core.models import Invite, User
|
||||||
from passbook.lib.config import CONFIG
|
from passbook.lib.config import CONFIG
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = getLogger(__name__)
|
||||||
|
@ -41,6 +41,10 @@ class LoginView(UserPassesTestMixin, FormView):
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs['config'] = CONFIG.get('passbook')
|
kwargs['config'] = CONFIG.get('passbook')
|
||||||
kwargs['is_login'] = True
|
kwargs['is_login'] = True
|
||||||
|
kwargs['title'] = _('Log in to your account')
|
||||||
|
kwargs['primary_action'] = _('Log in')
|
||||||
|
kwargs['show_sign_up_notice'] = CONFIG.y('passbook.sign_up.enabled')
|
||||||
|
kwargs['show_password_forget_notice'] = CONFIG.y('passbook.password_reset.enabled')
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
def get_user(self, uid_value) -> User:
|
def get_user(self, uid_value) -> User:
|
||||||
|
@ -105,3 +109,94 @@ class LogoutView(LoginRequiredMixin, View):
|
||||||
logout(request)
|
logout(request)
|
||||||
messages.success(request, _("You've successfully been logged out."))
|
messages.success(request, _("You've successfully been logged out."))
|
||||||
return redirect(reverse('passbook_core:auth-login'))
|
return redirect(reverse('passbook_core:auth-login'))
|
||||||
|
|
||||||
|
|
||||||
|
class SignUpView(UserPassesTestMixin, FormView):
|
||||||
|
"""Sign up new user, optionally consume one-use invite link."""
|
||||||
|
|
||||||
|
template_name = 'login/form.html'
|
||||||
|
form_class = SignUpForm
|
||||||
|
success_url = '.'
|
||||||
|
_invite = None
|
||||||
|
|
||||||
|
# Allow only not authenticated users to login
|
||||||
|
def test_func(self):
|
||||||
|
return self.request.user.is_authenticated is False
|
||||||
|
|
||||||
|
def handle_no_permission(self):
|
||||||
|
return redirect(reverse('passbook_core:overview'))
|
||||||
|
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
"""Check if sign-up is enabled or invite link given"""
|
||||||
|
allowed = False
|
||||||
|
if 'invite' in request.GET:
|
||||||
|
invites = Invite.objects.filter(uuid=request.GET.get('invite'))
|
||||||
|
allowed = invites.exists()
|
||||||
|
if allowed:
|
||||||
|
self._invite = invites.first()
|
||||||
|
if CONFIG.y('passbook.sign_up.enabled'):
|
||||||
|
allowed = True
|
||||||
|
if not allowed:
|
||||||
|
messages.error(request, _('Sign-ups are currently disabled.'))
|
||||||
|
return redirect(reverse('passbook_core:auth-login'))
|
||||||
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
kwargs['config'] = CONFIG.get('passbook')
|
||||||
|
kwargs['is_login'] = True
|
||||||
|
kwargs['title'] = _('Sign Up')
|
||||||
|
kwargs['primary_action'] = _('Sign up')
|
||||||
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
def form_valid(self, form: SignUpForm) -> HttpResponse:
|
||||||
|
"""Create user"""
|
||||||
|
SignUpView.create_user(form.cleaned_data, self.request)
|
||||||
|
if self._invite:
|
||||||
|
self._invite.delete()
|
||||||
|
messages.success(self.request, _("Successfully signed up!"))
|
||||||
|
LOGGER.debug("Successfully signed up %s",
|
||||||
|
form.cleaned_data.get('email'))
|
||||||
|
return redirect(reverse('passbook_core:auth-login'))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_user(data: Dict, request: HttpRequest = None) -> User:
|
||||||
|
"""Create user from data
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data: Dictionary as returned by SignupForm's cleaned_data
|
||||||
|
request: Optional current request.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The user created
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
SignalException: if any signals raise an exception. This also deletes the created user.
|
||||||
|
"""
|
||||||
|
# Create user
|
||||||
|
new_user = User.objects.create_user(
|
||||||
|
username=data.get('username'),
|
||||||
|
email=data.get('email'),
|
||||||
|
first_name=data.get('first_name'),
|
||||||
|
last_name=data.get('last_name'),
|
||||||
|
)
|
||||||
|
new_user.is_active = True
|
||||||
|
new_user.set_password(data.get('password'))
|
||||||
|
new_user.save()
|
||||||
|
# Send signal for other auth sources
|
||||||
|
# try:
|
||||||
|
# TODO: Create signal for signup
|
||||||
|
# on_user_sign_up.send(
|
||||||
|
# sender=None,
|
||||||
|
# user=new_user,
|
||||||
|
# request=request,
|
||||||
|
# password=data.get('password'),
|
||||||
|
# needs_confirmation=needs_confirmation)
|
||||||
|
# TODO: Implement Verification, via email or others
|
||||||
|
# if needs_confirmation:
|
||||||
|
# Create Account Confirmation UUID
|
||||||
|
# AccountConfirmation.objects.create(user=new_user)
|
||||||
|
# except SignalException as exception:
|
||||||
|
# LOGGER.warning("Failed to sign up user %s", exception, exc_info=exception)
|
||||||
|
# new_user.delete()
|
||||||
|
# raise
|
||||||
|
return new_user
|
||||||
|
|
Reference in New Issue