diff --git a/marietje/marietje/admin.py b/marietje/marietje/admin.py index 70ccb6e..a60dbfc 100644 --- a/marietje/marietje/admin.py +++ b/marietje/marietje/admin.py @@ -12,6 +12,7 @@ class UserAdmin(BaseUserAdmin): (_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions')}), (_('Important dates'), {'fields': ('last_login', 'date_joined')}), + (_('Activation'), {'fields': ('activation_token', 'reset_token')}), ) list_display = ('username', 'email', 'name', 'is_staff') search_fields = ('username', 'name', 'email') diff --git a/marietje/marietje/forms.py b/marietje/marietje/forms.py index 26ef2d4..c04b1c1 100644 --- a/marietje/marietje/forms.py +++ b/marietje/marietje/forms.py @@ -9,7 +9,7 @@ class AuthenticationForm(BaseAuthenticationForm): super(AuthenticationForm, self).__init__(request, *args, **kwargs) def confirm_login_allowed(self, user): - if user.activation_token is not None: + if user.activation_token: raise forms.ValidationError( self.error_messages['inactive'], code='inactive', @@ -68,7 +68,39 @@ class RegistrationForm(forms.ModelForm): def save(self, commit=True): user = super(RegistrationForm, self).save(commit=False) - user.set_password(self.cleaned_data["password1"]) + user.set_password(self.cleaned_data.get("password1")) if commit: user.save() return user + + +class ResetPasswordForm(forms.Form): + error_messages = { + 'password_mismatch': _("The two password fields didn't match."), + } + password1 = forms.CharField( + label=_("Password"), + strip=False, + widget=forms.PasswordInput, + ) + password2 = forms.CharField( + label=_("Password confirmation"), + widget=forms.PasswordInput, + strip=False, + help_text=_("Enter the same password as before, for verification."), + ) + + def __init__(self, *args, **kwargs): + super(ResetPasswordForm, self).__init__(*args, **kwargs) + + def clean_password2(self): + password1 = self.cleaned_data.get("password1") + password2 = self.cleaned_data.get("password2") + + if password1 and password2 and password1 != password2: + raise forms.ValidationError( + self.error_messages['password_mismatch'], + code='password_mismatch', + ) + password_validation.validate_password(self.cleaned_data.get('password2')) + return password2 diff --git a/marietje/marietje/migrations/0004_user_reset_token.py b/marietje/marietje/migrations/0004_user_reset_token.py new file mode 100644 index 0000000..9c6174a --- /dev/null +++ b/marietje/marietje/migrations/0004_user_reset_token.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-01-29 18:55 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('marietje', '0003_auto_20170117_2320'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='reset_token', + field=models.TextField(blank=True, null=True, verbose_name='reset token'), + ), + ] diff --git a/marietje/marietje/models.py b/marietje/marietje/models.py index 327038f..0a1d59e 100644 --- a/marietje/marietje/models.py +++ b/marietje/marietje/models.py @@ -94,6 +94,12 @@ class User(AbstractBaseUser, PermissionsMixin): null=True ) + reset_token = models.TextField( + _('reset token'), + blank=True, + null=True + ) + USERNAME_FIELD = 'username' REQUIRED_FIELDS = ['email', 'name'] diff --git a/marietje/marietje/templates/registration/forgotpassword.html b/marietje/marietje/templates/registration/forgotpassword.html new file mode 100644 index 0000000..d118f26 --- /dev/null +++ b/marietje/marietje/templates/registration/forgotpassword.html @@ -0,0 +1,27 @@ +{% extends 'base.html' %} + +{% block title %}Reset Password{% endblock %} + +{% block content %} +
+{% endblock %} diff --git a/marietje/marietje/templates/registration/login.html b/marietje/marietje/templates/registration/login.html index 70118e3..3e9269e 100644 --- a/marietje/marietje/templates/registration/login.html +++ b/marietje/marietje/templates/registration/login.html @@ -27,7 +27,8 @@ diff --git a/marietje/marietje/templates/registration/resetpassword.html b/marietje/marietje/templates/registration/resetpassword.html new file mode 100644 index 0000000..a38a6c9 --- /dev/null +++ b/marietje/marietje/templates/registration/resetpassword.html @@ -0,0 +1,30 @@ +{% extends 'base.html' %} + +{% block title %}Reset Password{% endblock %} + +{% block content %} + +{% endblock %} diff --git a/marietje/marietje/urls.py b/marietje/marietje/urls.py index bd2b2c1..4a7deff 100644 --- a/marietje/marietje/urls.py +++ b/marietje/marietje/urls.py @@ -17,17 +17,18 @@ from django.conf.urls import include, url from django.contrib import admin from queues.views import index from django.contrib.auth.views import login, logout -from .views import register, activate +from .views import register, activate, forgotpassword, resetpassword from .forms import AuthenticationForm - urlpatterns = [ url(r'^$', index, name='index'), url(r'^login/$', login, {'authentication_form': AuthenticationForm}, name='login'), url(r'^logout/$', logout, name='logout'), url(r'^register/$', register, name='register'), url(r'^activate/(\d+)/(\w+)/$', activate, name='activate'), + url(r'^forgotpassword/$', forgotpassword, name='forgotpassword'), + url(r'^resetpassword/(\d+)/(\w+)/$', resetpassword, name='resetpassword'), url(r'^admin/', admin.site.urls), url(r'^songs/', include('songs.urls')), url(r'^api/', include('api.urls')), diff --git a/marietje/marietje/views.py b/marietje/marietje/views.py index 8475a71..d7c5cef 100644 --- a/marietje/marietje/views.py +++ b/marietje/marietje/views.py @@ -4,7 +4,7 @@ from django.contrib.auth import get_user_model from django.core.mail import send_mail from django.conf import settings from django.urls import reverse -from .forms import RegistrationForm +from .forms import RegistrationForm, ResetPasswordForm from marietje.utils import get_first_queue @@ -31,9 +31,8 @@ def register(request): 'Please confirm your account by following this link: ' + activation_link, settings.MAIL_FROM, [user.email], - fail_silently=False, + fail_silently=True ) - return redirect('login') @@ -44,3 +43,45 @@ def activate(request, user_id, token): user.activation_token = None user.save() return redirect('login') + + +def forgotpassword(request): + if not request.POST: + return render(request, 'registration/forgotpassword.html') + + User = get_user_model() + user = User.objects.filter(username=request.POST.get('email')).first() + if user is None or user.activation_token: + return render(request, 'registration/forgotpassword.html') + + user.reset_token = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(32)) + user.save() + reset_link = request.build_absolute_uri(reverse('resetpassword', args=[user.id, user.reset_token])) + + send_mail('Marietje - Reset password', + 'You have requested to reset your password. You can reset your password by following this link: ' + + reset_link + '\nIf you did not request to reset your password, you can ignore this email.', + settings.MAIL_FROM, + [user.email], + fail_silently=True + ) + return redirect('login') + + +def resetpassword(request, user_id, token): + User = get_user_model() + user = get_object_or_404(User, pk=user_id) + if not user.reset_token or token != user.reset_token: + return redirect('login') + if not request.POST: + return render(request, 'registration/resetpassword.html', {'user_id': user.id, 'reset_token': token}) + + form = ResetPasswordForm(request.POST) + + if not form.is_valid(): + return render(request, 'registration/resetpassword.html', {'user_id': user.id, 'reset_token': token, 'form': form}) + + user.reset_token = None + user.set_password(form.cleaned_data['password1']) + user.save() + return redirect('login')