Commit 3dc2371725386262a7002a8df73517b41724c6c1

Authored by Zambom
1 parent 2900c425

Password recovery implementation

amadeus/templates/recover_pass_email_template.html
1 -{% extends 'base.html' %}  
2 1
3 {% load i18n %} 2 {% load i18n %}
4 3
5 -{% block nav %}  
6 -{% endblock %}  
7 -  
8 -{% block breadcrumbs %}  
9 -{% endblock %}  
10 -  
11 -{% block sidebar %}  
12 -{% endblock sidebar %}  
13 4
14 {% autoescape off %} 5 {% autoescape off %}
15 6
@@ -18,10 +9,9 @@ @@ -18,10 +9,9 @@
18 {% trans "Please go to the following page and choose a new password:" %} 9 {% trans "Please go to the following page and choose a new password:" %}
19 10
20 {% block reset_link %} 11 {% 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. --> 12 + {{ domain }}
23 {% endblock %} 13 {% endblock %}
24 14
25 {% blocktrans %}The {{ site_name }} team{% endblocktrans %} 15 {% blocktrans %}The {{ site_name }} team{% endblocktrans %}
26 16
27 -{% endautoescape %}  
28 \ No newline at end of file 17 \ No newline at end of file
  18 +{% endautoescape %}
users/forms.py
@@ -197,4 +197,14 @@ class PassResetRequest(forms.Form): @@ -197,4 +197,14 @@ class PassResetRequest(forms.Form):
197 except ValidationError: 197 except ValidationError:
198 self._errors['email'] = [_('You must insert an email address')] 198 self._errors['email'] = [_('You must insert an email address')]
199 199
200 - return ValueError  
201 \ No newline at end of file 200 \ No newline at end of file
  201 + return ValueError
  202 +
  203 +class SetPasswordForm(Validation):
  204 + is_edit = False
  205 +
  206 + new_password = forms.CharField(label=_('New Password'), widget = forms.PasswordInput(render_value=True), required = True)
  207 + password2 = forms.CharField(label = _('Confirm Password'), widget = forms.PasswordInput(render_value=True), required = True)
  208 +
  209 + class Meta:
  210 + model = User
  211 + fields = []
202 \ No newline at end of file 212 \ No newline at end of file
users/templates/users/new_password.html 0 → 100644
@@ -0,0 +1,83 @@ @@ -0,0 +1,83 @@
  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">&times;</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 'Set new password' %} </strong></h2>
  40 + </div>
  41 + </div>
  42 +
  43 + <form id="form-reset" method="post" action="">
  44 + {% csrf_token %}
  45 + {% for field in form %}
  46 + <div class="col-md-10 col-md-offset-1">
  47 + <div class="form-group{% if form.has_error %} has-error {% endif %}">
  48 + {% if field.field.required %}
  49 + <label for="{{ field.auto_id }}" class="control-label">{{ field.label }} <span>*</span></label>
  50 + {% else %}
  51 + <label for="{{ field.auto_id }}" class="control-label">{{ field.label }}</label>
  52 + {% endif %}
  53 + {% render_field field class='form-control' %}
  54 + <span id="helpBlock" class="help-block">{{ field.help_text }}</span>
  55 + {% if field.errors %}
  56 + <div class="alert alert-danger alert-dismissible" role="alert">
  57 + <button type="button" class="close" data-dismiss="alert" aria-label="Close">
  58 + <span aria-hidden="true">&times;</span>
  59 + </button>
  60 + <ul>
  61 + {% for error in field.errors %}
  62 + <li>{{ error }}</li>
  63 + {% endfor %}
  64 + </ul>
  65 + </div>
  66 + {% endif %}
  67 + </div>
  68 + </div>
  69 + {% endfor %}
  70 + </form>
  71 + <div class="row">
  72 + <div class="col-md-5 col-xs-6 col-sm-6 col-lg-5 col-lg-offset-1 col-md-offset-1 text-center">
  73 + <button type="submit" class="btn btn-success btn-raised btn-block" form="form-reset" style="position: initial;"> {% trans 'Reset' %} </button>
  74 + </div>
  75 + <div class="col-md-5 col-xs-6 col-sm-6 col-lg-5 text-center">
  76 + <a class="btn btn-default btn-raised btn-block" href="{% url 'users:login' %}" formaction="#" style="position: initial;">{% trans 'Back' %}</a>
  77 + </div>
  78 + </div>
  79 + </div>
  80 + </div>
  81 + </div>
  82 + </div>
  83 +{% endblock %}
0 \ No newline at end of file 84 \ No newline at end of file
@@ -8,7 +8,7 @@ urlpatterns = [ @@ -8,7 +8,7 @@ urlpatterns = [
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'), 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'), 11 + url(r'^reset_password_confirm/(?P<uidb64>[0-9A-Za-z]+)-(?P<token>.+)/$', views.PasswordResetConfirmView.as_view(), name = 'reset_password_confirm'),
12 url(r'^$', views.UsersListView.as_view(), name = 'manage'), 12 url(r'^$', views.UsersListView.as_view(), name = 'manage'),
13 url(r'^create/$', views.CreateView.as_view(), name = 'create'), 13 url(r'^create/$', views.CreateView.as_view(), name = 'create'),
14 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
@@ -10,7 +10,7 @@ from rolepermissions.shortcuts import assign_role @@ -10,7 +10,7 @@ from rolepermissions.shortcuts import assign_role
10 from rolepermissions.verifications import has_role 10 from rolepermissions.verifications import has_role
11 11
12 from .models import User 12 from .models import User
13 -from .forms import RegisterUserForm, ProfileForm, UserForm, ChangePassForm, PassResetRequest 13 +from .forms import RegisterUserForm, ProfileForm, UserForm, ChangePassForm, PassResetRequest, SetPasswordForm
14 14
15 #RECOVER PASS IMPORTS 15 #RECOVER PASS IMPORTS
16 from django.contrib.auth.tokens import default_token_generator 16 from django.contrib.auth.tokens import default_token_generator
@@ -27,11 +27,6 @@ from rest_framework.permissions import IsAuthenticated, IsAuthenticatedOrReadOnl @@ -27,11 +27,6 @@ from rest_framework.permissions import IsAuthenticated, IsAuthenticatedOrReadOnl
27 27
28 # ================ ADMIN ======================= 28 # ================ ADMIN =======================
29 29
30 -  
31 -  
32 -  
33 -  
34 -  
35 # class View(LoginRequiredMixin, generic.DetailView): 30 # class View(LoginRequiredMixin, generic.DetailView):
36 31
37 # #login_url = reverse_lazy("core:home") 32 # #login_url = reverse_lazy("core:home")
@@ -367,13 +362,16 @@ class ForgotPassword(generic.FormView): @@ -367,13 +362,16 @@ class ForgotPassword(generic.FormView):
367 362
368 if users.exists(): 363 if users.exists():
369 for user in users: 364 for user in users:
  365 + uid = urlsafe_base64_encode(force_bytes(user.pk))
  366 + token = default_token_generator.make_token(user)
  367 +
370 c = { 368 c = {
  369 + 'request': request,
  370 + 'title': _('Recover Password'),
371 'email': user.email, 371 'email': user.email,
372 - 'domain': 'amadeus.com.br', #or your domain 372 + 'domain': request.build_absolute_uri(reverse("users:reset_password_confirm", kwargs = {'uidb64': uid, 'token':token})), #or your domain
373 'site_name': 'Amadeus', 373 'site_name': 'Amadeus',
374 - 'uid': urlsafe_base64_encode(force_bytes(user.pk)),  
375 'user': user, 374 'user': user,
376 - 'token': default_token_generator.make_token(user),  
377 'protocol': 'http', 375 'protocol': 'http',
378 } 376 }
379 377
@@ -388,7 +386,7 @@ class ForgotPassword(generic.FormView): @@ -388,7 +386,7 @@ class ForgotPassword(generic.FormView):
388 send_mail(subject, email, settings.DEFAULT_FROM_EMAIL , [user.email], fail_silently=False) 386 send_mail(subject, email, settings.DEFAULT_FROM_EMAIL , [user.email], fail_silently=False)
389 387
390 result = self.form_valid(form) 388 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.")) 389 + messages.success(request, _("Soon you'll receive an email with instructions to set your new password. If you don't receive it in 24 hours, please check your spam box."))
392 390
393 return result 391 return result
394 392
@@ -397,6 +395,46 @@ class ForgotPassword(generic.FormView): @@ -397,6 +395,46 @@ class ForgotPassword(generic.FormView):
397 395
398 return result 396 return result
399 397
  398 +class PasswordResetConfirmView(generic.FormView):
  399 + template_name = "users/new_password.html"
  400 + success_url = reverse_lazy('users:login')
  401 + form_class = SetPasswordForm
  402 +
  403 + def get_context_data(self, **kwargs):
  404 + context = super(PasswordResetConfirmView, self).get_context_data(**kwargs)
  405 + context['title'] = _('Reset Password')
  406 +
  407 + return context
  408 +
  409 + def post(self, request, uidb64=None, token=None, *arg, **kwargs):
  410 + form = self.get_form()
  411 +
  412 + assert uidb64 is not None and token is not None
  413 +
  414 + try:
  415 + uid = urlsafe_base64_decode(uidb64)
  416 + user = User._default_manager.get(pk=uid)
  417 + except (TypeError, ValueError, OverflowError, User.DoesNotExist):
  418 + user = None
  419 +
  420 + if user is not None and default_token_generator.check_token(user, token):
  421 + if form.is_valid():
  422 + new_password = form.cleaned_data['new_password']
  423 +
  424 + user.set_password(new_password)
  425 + user.save()
  426 +
  427 + messages.success(request, 'Password reset successfully.')
  428 +
  429 + return self.form_valid(form)
  430 + else:
  431 + messages.error(request, 'We were not able to reset your password.')
  432 + return self.form_invalid(form)
  433 + else:
  434 + messages.error(request,'The reset password link is no longer valid.')
  435 + return self.form_invalid(form)
  436 +
  437 +
400 def login(request): 438 def login(request):
401 context = {} 439 context = {}
402 context['title'] = _('Log In') 440 context['title'] = _('Log In')