Commit 1ea6cb0c0b1a8705c943a59fe28e541e15ef6e3d
1 parent
f1e1b1d3
Exists in
master
and in
3 other branches
Adding password recovering
Showing
7 changed files
with
203 additions
and
8 deletions
Show diff stats
amadeus/settings.py
@@ -180,7 +180,7 @@ LOGS_URL = 'logs/' | @@ -180,7 +180,7 @@ LOGS_URL = 'logs/' | ||
180 | 180 | ||
181 | 181 | ||
182 | EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' | 182 | EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' |
183 | -DEFAULT_FROM_EMAIL = 'admin@admin.com' | 183 | +DEFAULT_FROM_EMAIL = 'admin@amadeus.com.br' |
184 | 184 | ||
185 | # Messages | 185 | # Messages |
186 | from django.contrib.messages import constants as messages_constants | 186 | from django.contrib.messages import constants as messages_constants |
@@ -0,0 +1,27 @@ | @@ -0,0 +1,27 @@ | ||
1 | +{% extends 'base.html' %} | ||
2 | + | ||
3 | +{% load i18n %} | ||
4 | + | ||
5 | +{% block nav %} | ||
6 | +{% endblock %} | ||
7 | + | ||
8 | +{% block breadcrumbs %} | ||
9 | +{% endblock %} | ||
10 | + | ||
11 | +{% block sidebar %} | ||
12 | +{% endblock sidebar %} | ||
13 | + | ||
14 | +{% autoescape off %} | ||
15 | + | ||
16 | +{% blocktrans %}You're receiving this email because you requested a password reset for your user account at {{ site_name }}.{% endblocktrans %} | ||
17 | + | ||
18 | +{% trans "Please go to the following page and choose a new password:" %} | ||
19 | + | ||
20 | +{% block reset_link %} | ||
21 | + {{ domain }}{% url 'users:reset_password_confirm' uidb64=uid token=token %} | ||
22 | + <!--This is the only change from ` django/contrib/admin/templates/registration/password_reset_subject.html`. the url name is commented out in urls.py section. The view associated with the url is going to described later in this post. --> | ||
23 | +{% endblock %} | ||
24 | + | ||
25 | +{% blocktrans %}The {{ site_name }} team{% endblocktrans %} | ||
26 | + | ||
27 | +{% endautoescape %} | ||
0 | \ No newline at end of file | 28 | \ No newline at end of file |
users/forms.py
@@ -3,12 +3,25 @@ from django import forms | @@ -3,12 +3,25 @@ from django import forms | ||
3 | from django.utils.translation import ugettext_lazy as _ | 3 | from django.utils.translation import ugettext_lazy as _ |
4 | from rolepermissions.shortcuts import assign_role | 4 | from rolepermissions.shortcuts import assign_role |
5 | from django.contrib.auth import update_session_auth_hash | 5 | from django.contrib.auth import update_session_auth_hash |
6 | +from django.core.validators import validate_email | ||
7 | +from django.core.exceptions import ValidationError | ||
6 | from .models import User | 8 | from .models import User |
7 | 9 | ||
8 | class Validation(forms.ModelForm): | 10 | class Validation(forms.ModelForm): |
9 | MIN_PASS_LENGTH = 8 | 11 | MIN_PASS_LENGTH = 8 |
10 | MAX_UPLOAD_SIZE = 2*1024*1024 | 12 | MAX_UPLOAD_SIZE = 2*1024*1024 |
11 | 13 | ||
14 | + def clean_email(self): | ||
15 | + email = self.cleaned_data.get('email', '') | ||
16 | + | ||
17 | + try: | ||
18 | + validate_email( email ) | ||
19 | + return email | ||
20 | + except ValidationError: | ||
21 | + self._errors['email'] = [_('You must insert an email address')] | ||
22 | + | ||
23 | + return ValueError | ||
24 | + | ||
12 | def clean_image(self): | 25 | def clean_image(self): |
13 | image = self.cleaned_data.get('image', False) | 26 | image = self.cleaned_data.get('image', False) |
14 | 27 | ||
@@ -170,4 +183,18 @@ class ChangePassForm(Validation): | @@ -170,4 +183,18 @@ class ChangePassForm(Validation): | ||
170 | } | 183 | } |
171 | widgets = { | 184 | widgets = { |
172 | 'password': forms.PasswordInput | 185 | 'password': forms.PasswordInput |
173 | - } | ||
174 | \ No newline at end of file | 186 | \ No newline at end of file |
187 | + } | ||
188 | + | ||
189 | +class PassResetRequest(forms.Form): | ||
190 | + email = forms.CharField(label = _('Email'), max_length = 254) | ||
191 | + | ||
192 | + def clean_email(self): | ||
193 | + email = self.cleaned_data.get('email', '') | ||
194 | + | ||
195 | + try: | ||
196 | + validate_email( email ) | ||
197 | + return email | ||
198 | + except ValidationError: | ||
199 | + self._errors['email'] = [_('You must insert an email address')] | ||
200 | + | ||
201 | + return ValueError | ||
175 | \ No newline at end of file | 202 | \ No newline at end of file |
@@ -0,0 +1,84 @@ | @@ -0,0 +1,84 @@ | ||
1 | +{% extends 'base.html' %} | ||
2 | + | ||
3 | +{% load static i18n %} | ||
4 | +{% load widget_tweaks %} | ||
5 | + | ||
6 | +{% block nav %} | ||
7 | +{% endblock %} | ||
8 | + | ||
9 | +{% block breadcrumbs %} | ||
10 | +{% endblock %} | ||
11 | + | ||
12 | +{% block sidebar %} | ||
13 | +{% endblock sidebar %} | ||
14 | + | ||
15 | +{% block content %} | ||
16 | + <div class="row"> | ||
17 | + <div class="col-sm-7 col-sm-offset-4 col-md-6 col-md-offset-4 col-xs-8 col-xs-offset-3 col-lg-6 col-lg-offset-4 col-xl-6 col-xl-offset-4 "> | ||
18 | + <div class="col-sm-9 col-sm-offset-2 col-md-8 col-md-offset-2 col-xs-9 col-xs-offset-2 col-lg-8 col-lg-offset-2 col-xl-8 col-xl-offset-2"> | ||
19 | + <img src="{% static 'img/amadeus.png' %}" class="img-responsive center-block logo-login " alt="logo amadeus"> | ||
20 | + </div> | ||
21 | + </div> | ||
22 | + </div> | ||
23 | + <div class="row"> | ||
24 | + <div class="col-lg-8 col-lg-offset-3 col-md-8 col-md-offset-3 col-sm-9 col-sm-offset-3 col-xs-10 col-xs-offset-2"> | ||
25 | + {% if messages %} | ||
26 | + {% for message in messages %} | ||
27 | + <div class="alert alert-{{ message.tags }} alert-dismissible" role="alert"> | ||
28 | + <button type="button" class="close" data-dismiss="alert" aria-label="Close"> | ||
29 | + <span aria-hidden="true">×</span> | ||
30 | + </button> | ||
31 | + <p>{{ message }}</p> | ||
32 | + </div> | ||
33 | + {% endfor %} | ||
34 | + {% endif %} | ||
35 | + <div class="card"> | ||
36 | + <div class="card-block"> | ||
37 | + <div class="row"> | ||
38 | + <div class="col-md-12 text-center"> | ||
39 | + <h2 style="color:#43a251"><strong> {% trans 'Forgot Password' %} </strong></h2> | ||
40 | + <p>{% trans 'Enter your email below (the one used to access the platform) to recover your password' %}</p> | ||
41 | + </div> | ||
42 | + </div> | ||
43 | + | ||
44 | + <form id="form-reset" method="post" action=""> | ||
45 | + {% csrf_token %} | ||
46 | + {% for field in form %} | ||
47 | + <div class="col-md-10 col-md-offset-1"> | ||
48 | + <div class="form-group{% if form.has_error %} has-error {% endif %}"> | ||
49 | + {% if field.field.required %} | ||
50 | + <label for="{{ field.auto_id }}" class="control-label">{{ field.label }} <span>*</span></label> | ||
51 | + {% else %} | ||
52 | + <label for="{{ field.auto_id }}" class="control-label">{{ field.label }}</label> | ||
53 | + {% endif %} | ||
54 | + {% render_field field class='form-control' %} | ||
55 | + <span id="helpBlock" class="help-block">{{ field.help_text }}</span> | ||
56 | + {% if field.errors %} | ||
57 | + <div class="alert alert-danger alert-dismissible" role="alert"> | ||
58 | + <button type="button" class="close" data-dismiss="alert" aria-label="Close"> | ||
59 | + <span aria-hidden="true">×</span> | ||
60 | + </button> | ||
61 | + <ul> | ||
62 | + {% for error in field.errors %} | ||
63 | + <li>{{ error }}</li> | ||
64 | + {% endfor %} | ||
65 | + </ul> | ||
66 | + </div> | ||
67 | + {% endif %} | ||
68 | + </div> | ||
69 | + </div> | ||
70 | + {% endfor %} | ||
71 | + </form> | ||
72 | + <div class="row"> | ||
73 | + <div class="col-md-5 col-xs-6 col-sm-6 col-lg-5 col-lg-offset-1 col-md-offset-1 text-center"> | ||
74 | + <button type="submit" class="btn btn-success btn-raised btn-block" form="form-reset" style="position: initial;"> {% trans 'Recover' %} </button> | ||
75 | + </div> | ||
76 | + <div class="col-md-5 col-xs-6 col-sm-6 col-lg-5 text-center"> | ||
77 | + <a class="btn btn-default btn-raised btn-block" href="{% url 'users:login' %}" formaction="#" style="position: initial;">{% trans 'Back' %}</a> | ||
78 | + </div> | ||
79 | + </div> | ||
80 | + </div> | ||
81 | + </div> | ||
82 | + </div> | ||
83 | + </div> | ||
84 | +{% endblock %} | ||
0 | \ No newline at end of file | 85 | \ No newline at end of file |
users/templates/users/login.html
@@ -65,7 +65,7 @@ | @@ -65,7 +65,7 @@ | ||
65 | </div> | 65 | </div> |
66 | <div class="row"> | 66 | <div class="row"> |
67 | <div class="col-md-offset-1 col-md-10 text-right forgotPassword"> | 67 | <div class="col-md-offset-1 col-md-10 text-right forgotPassword"> |
68 | - <a href="#">{% trans 'Forgot your password?' %}</a> | 68 | + <a href="{% url 'users:forgot_pass' %}">{% trans 'Forgot your password?' %}</a> |
69 | </div> | 69 | </div> |
70 | </div> | 70 | </div> |
71 | </div> | 71 | </div> |
users/urls.py
@@ -7,6 +7,8 @@ urlpatterns = [ | @@ -7,6 +7,8 @@ urlpatterns = [ | ||
7 | url(r'^login/$', views.login, name='login'), | 7 | url(r'^login/$', views.login, name='login'), |
8 | url(r'^logout/$', auth_views.logout, {'next_page': 'users:login'}, name='logout'), | 8 | url(r'^logout/$', auth_views.logout, {'next_page': 'users:login'}, name='logout'), |
9 | url(r'^signup/$', views.RegisterUser.as_view(), name = 'signup'), | 9 | url(r'^signup/$', views.RegisterUser.as_view(), name = 'signup'), |
10 | + url(r'^forgot_password/$', views.ForgotPassword.as_view(), name = 'forgot_pass'), | ||
11 | + url(r'^reset_password_confirm/(?P<uidb64>[0-9A-Za-z]+)-(?P<token>.+)/$', views.ForgotPassword.as_view(), name = 'reset_password_confirm'), | ||
10 | url(r'^$', views.UsersListView.as_view(), name = 'manage'), | 12 | url(r'^$', views.UsersListView.as_view(), name = 'manage'), |
11 | url(r'^create/$', views.CreateView.as_view(), name = 'create'), | 13 | url(r'^create/$', views.CreateView.as_view(), name = 'create'), |
12 | url(r'^edit/(?P<email>[\w.%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4})/$', views.UpdateView.as_view(), name='update'), | 14 | url(r'^edit/(?P<email>[\w.%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4})/$', views.UpdateView.as_view(), name='update'), |
users/views.py
1 | -from django.http import Http404 | ||
2 | from django.shortcuts import get_object_or_404,redirect, render | 1 | from django.shortcuts import get_object_or_404,redirect, render |
3 | -from django.db.models import Q | ||
4 | from django.views import generic | 2 | from django.views import generic |
5 | from django.contrib import messages | 3 | from django.contrib import messages |
6 | from rolepermissions.mixins import HasRoleMixin | 4 | from rolepermissions.mixins import HasRoleMixin |
@@ -10,11 +8,17 @@ from django.core.urlresolvers import reverse, reverse_lazy | @@ -10,11 +8,17 @@ from django.core.urlresolvers import reverse, reverse_lazy | ||
10 | from django.utils.translation import ugettext_lazy as _ | 8 | from django.utils.translation import ugettext_lazy as _ |
11 | from rolepermissions.shortcuts import assign_role | 9 | from rolepermissions.shortcuts import assign_role |
12 | from rolepermissions.verifications import has_role | 10 | from rolepermissions.verifications import has_role |
13 | -from itertools import chain | ||
14 | -from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger | ||
15 | 11 | ||
16 | from .models import User | 12 | from .models import User |
17 | -from .forms import RegisterUserForm, ProfileForm, UserForm, ChangePassForm | 13 | +from .forms import RegisterUserForm, ProfileForm, UserForm, ChangePassForm, PassResetRequest |
14 | + | ||
15 | +#RECOVER PASS IMPORTS | ||
16 | +from django.contrib.auth.tokens import default_token_generator | ||
17 | +from django.core.mail import send_mail | ||
18 | +from django.conf import settings | ||
19 | +from django.utils.encoding import force_bytes | ||
20 | +from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode | ||
21 | +from django.template import loader | ||
18 | 22 | ||
19 | #API IMPORTS | 23 | #API IMPORTS |
20 | from rest_framework import viewsets | 24 | from rest_framework import viewsets |
@@ -342,6 +346,57 @@ class RegisterUser(generic.edit.CreateView): | @@ -342,6 +346,57 @@ class RegisterUser(generic.edit.CreateView): | ||
342 | 346 | ||
343 | return super(RegisterUser, self).form_valid(form) | 347 | return super(RegisterUser, self).form_valid(form) |
344 | 348 | ||
349 | +class ForgotPassword(generic.FormView): | ||
350 | + template_name = "users/forgot_password.html" | ||
351 | + success_url = reverse_lazy('users:login') | ||
352 | + form_class = PassResetRequest | ||
353 | + | ||
354 | + def get_context_data(self, **kwargs): | ||
355 | + context = super(ForgotPassword, self).get_context_data(**kwargs) | ||
356 | + context['title'] = _('Forgot Password') | ||
357 | + | ||
358 | + return context | ||
359 | + | ||
360 | + def post(self, request, *args, **kwargs): | ||
361 | + form = self.get_form() | ||
362 | + | ||
363 | + if form.is_valid(): | ||
364 | + email = form.cleaned_data['email'] | ||
365 | + | ||
366 | + users = User.objects.filter(email = email) | ||
367 | + | ||
368 | + if users.exists(): | ||
369 | + for user in users: | ||
370 | + c = { | ||
371 | + 'email': user.email, | ||
372 | + 'domain': 'amadeus.com.br', #or your domain | ||
373 | + 'site_name': 'Amadeus', | ||
374 | + 'uid': urlsafe_base64_encode(force_bytes(user.pk)), | ||
375 | + 'user': user, | ||
376 | + 'token': default_token_generator.make_token(user), | ||
377 | + 'protocol': 'http', | ||
378 | + } | ||
379 | + | ||
380 | + subject_template_name='registration/password_reset_subject.txt' | ||
381 | + email_template_name = 'recover_pass_email_template.html' | ||
382 | + | ||
383 | + subject = loader.render_to_string(subject_template_name, c) | ||
384 | + # Email subject *must not* contain newlines | ||
385 | + subject = ''.join(subject.splitlines()) | ||
386 | + email = loader.render_to_string(email_template_name, c) | ||
387 | + | ||
388 | + send_mail(subject, email, settings.DEFAULT_FROM_EMAIL , [user.email], fail_silently=False) | ||
389 | + | ||
390 | + result = self.form_valid(form) | ||
391 | + messages.success(request, _("An email has been sent to the informed address. Please check its inbox to continue reseting password.")) | ||
392 | + | ||
393 | + return result | ||
394 | + | ||
395 | + result = self.form_invalid(form) | ||
396 | + messages.error(request, _('No user is associated with this email address')) | ||
397 | + | ||
398 | + return result | ||
399 | + | ||
345 | def login(request): | 400 | def login(request): |
346 | context = {} | 401 | context = {} |
347 | context['title'] = _('Log In') | 402 | context['title'] = _('Log In') |