diff --git a/example/myshop/templates/myshop/pages/password-reset-confirm.html b/example/myshop/templates/myshop/pages/password-reset-confirm.html index 12f36401f..5c571d5f9 100644 --- a/example/myshop/templates/myshop/pages/password-reset-confirm.html +++ b/example/myshop/templates/myshop/pages/password-reset-confirm.html @@ -3,19 +3,29 @@ {% block title %}{% trans "Password Reset Confirm" %}{% endblock %} -{% block breadcrumb %}{% endblock %} +{% block breadcrumb %} +
+
+ +
+
+{% endblock %} {% block main-content %} -
-
- +
+
+
{% if validlink %} - {% page_url 'shop-cart' as action %} - {% include "shop/auth/password-reset-confirm.html" %} + {% page_url 'shop-customer-details' as customer_details_url %} + {% include "shop/auth/password-reset-confirm.html" with action=customer_details_url|default:'DO_NOTHING' form_name="password_reset_confirm" %} {% else %} {% include "shop/auth/password-reset-decline.html" %} {% endif %} - +
{% endblock main-content %} diff --git a/example/myshop/urls/__init__.py b/example/myshop/urls/__init__.py index f4003a5d8..a0ffdb31a 100644 --- a/example/myshop/urls/__init__.py +++ b/example/myshop/urls/__init__.py @@ -20,13 +20,13 @@ def render_robots(request): permission = 'noindex' in settings.ROBOTS_META_TAGS and 'Disallow' or 'Allow' return HttpResponse('User-Agent: *\n%s: /\n' % permission, content_type='text/plain') -i18n_urls = ( +i18n_urls = [ url(r'^admin/', include(admin.site.urls)), url(r'^password-reset-confirm/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/?$', PasswordResetConfirm.as_view(template_name='myshop/pages/password-reset-confirm.html'), name='password_reset_confirm'), url(r'^', include('cms.urls')), -) +] urlpatterns = [ url(r'^robots\.txt$', render_robots), url(r'^sitemap\.xml$', sitemap, {'sitemaps': sitemaps}, name='sitemap'), diff --git a/shop/cascade/auth.py b/shop/cascade/auth.py index 237cbe16f..7068fb12c 100644 --- a/shop/cascade/auth.py +++ b/shop/cascade/auth.py @@ -22,6 +22,7 @@ ('reset', _("Password Reset Form")), ('change', _("Change Password Form")), ('register-user', _("Register User"), 'shop.forms.auth.RegisterUserForm'), + ('register-user-activate', _("Register User with Activation"), 'shop.forms.auth.RegisterUserActivateForm'), ('continue-as-guest', _("Continue as guest")), ) @@ -41,7 +42,7 @@ def clean(self): class ShopAuthenticationPlugin(ShopLinkPluginBase): """ A placeholder plugin which provides various authentication forms, such as login-, logout-, - register-, and other forms. They can be added any placeholder using the Cascade framework. + register-, and other forms. They can be added to any placeholder using the Cascade framework. """ name = _("Authentication") parent_classes = ('BootstrapColumnPlugin',) @@ -71,10 +72,10 @@ def render(self, context, instance, placeholder): form_type = instance.glossary.get('form_type') if form_type: # prevent a malicious database entry to import an ineligible file - form_type = AUTH_FORM_TYPES[[ft[0] for ft in AUTH_FORM_TYPES].index(form_type)] try: + form_type = AUTH_FORM_TYPES[[ft[0] for ft in AUTH_FORM_TYPES].index(form_type)] FormClass = import_string(form_type[2]) - except (ImportError, IndexError): + except (ImportError, IndexError, ValueError): form_name = form_type[0].replace('-', '_') context['form_name'] = '{0}_form'.format(form_name) else: diff --git a/shop/forms/auth.py b/shop/forms/auth.py index 80c431a11..d813fa351 100644 --- a/shop/forms/auth.py +++ b/shop/forms/auth.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +from django.conf import settings from django.contrib.auth import get_user_model, authenticate, login from django.contrib.sites.shortcuts import get_current_site from django.core.exceptions import ValidationError @@ -14,6 +15,7 @@ from shop.conf import app_settings from shop.models.customer import CustomerModel +from shop.rest.auth import PasswordResetSerializer from .base import UniqueEmailValidationMixin @@ -99,6 +101,39 @@ def _send_password(self, request, user, password): user.email_user(subject, body) +class RegisterUserActivateSerializer(PasswordResetSerializer): + base_template = 'register-user-activate' + + +class RegisterUserActivateForm(NgModelFormMixin, NgFormValidationMixin, UniqueEmailValidationMixin, Bootstrap3ModelForm): + form_name = 'register_user_form' + scope_prefix = 'form_data' + field_css_classes = 'input-group has-feedback' + + email = fields.EmailField(label=_("Your e-mail address")) + + class Meta: + model = CustomerModel + fields = ['email'] + + def save(self, request=None, commit=True): + self.instance.user.is_active = True + self.instance.user.email = self.cleaned_data['email'] + password = get_user_model().objects.make_random_password(20) + self.instance.user.set_password(password) + self.instance.user.save() + self.instance.recognize_as_guest(request, commit=False) + + serializer = RegisterUserActivateSerializer(data=request.data, context={'request': request}) + if serializer.is_valid(): + msg = _("A link to activate this account has been sent to '{email}'.") + self.data.update(success=msg.format(**self.cleaned_data)) + serializer.save() + + customer = super(RegisterUserActivateForm, self).save(commit) + return customer + + class ContinueAsGuestForm(ModelForm): """ Handles Customer's decision to order as guest. diff --git a/shop/rest/auth.py b/shop/rest/auth.py index 946b44dd1..8b83881a8 100644 --- a/shop/rest/auth.py +++ b/shop/rest/auth.py @@ -11,14 +11,16 @@ class PasswordResetSerializer(serializers.PasswordResetSerializer): + base_template = 'reset-password' + def save(self): subject_template = select_template([ - '{}/email/reset-password-subject.txt'.format(app_settings.APP_LABEL), - 'shop/email/reset-password-subject.txt', + '{}/email/{}-subject.txt'.format(app_settings.APP_LABEL, self.base_template), + 'shop/email/{}-subject.txt'.format(self.base_template), ]) body_template = select_template([ - '{}/email/reset-password-body.txt'.format(app_settings.APP_LABEL), - 'shop/email/reset-password-body.txt', + '{}/email/{}-body.txt'.format(app_settings.APP_LABEL, self.base_template), + 'shop/email/{}-body.txt'.format(self.base_template), ]) opts = { 'use_https': self.context['request'].is_secure(), diff --git a/shop/templates/shop/auth/password-reset-confirm.html b/shop/templates/shop/auth/password-reset-confirm.html index ca0a762cf..9c3cc25c3 100644 --- a/shop/templates/shop/auth/password-reset-confirm.html +++ b/shop/templates/shop/auth/password-reset-confirm.html @@ -9,18 +9,33 @@ {{ block.super }}

{% trans "Please enter your new password twice so we can verify you typed it in correctly." %}

- -
- - + +
+ +
    +
  • + +
  • +
+
+ + +
- - -
- - + +
+ +
    +
  • + +
  • +
+
+ + +
- +
diff --git a/shop/templates/shop/auth/password-reset-decline.html b/shop/templates/shop/auth/password-reset-decline.html index e51d8432f..b9f73a08f 100644 --- a/shop/templates/shop/auth/password-reset-decline.html +++ b/shop/templates/shop/auth/password-reset-decline.html @@ -1,9 +1,11 @@ -{% load i18n %} +{% load i18n cms_tags %}
-
{% trans 'Password reset unsuccessful' %}
+
{% trans "Password can not be reset" %}
- {% trans "The password reset link was invalid, possibly because it has already been used. Please request a new password reset." %} + {% page_url 'shop-password-reset' as password_reset_url %} +

{% trans "The password reset link is invalid, possibly because it has already been used." %}

+

{% if password_reset_url %}{% endif %}{% trans "Please request a new password reset." %}{% if password_reset_url %}{% endif %}

diff --git a/shop/templates/shop/auth/register-user-activate.html b/shop/templates/shop/auth/register-user-activate.html new file mode 100644 index 000000000..233d38f26 --- /dev/null +++ b/shop/templates/shop/auth/register-user-activate.html @@ -0,0 +1,23 @@ +{% extends "shop/auth/anonymous-base.html" %} +{% load i18n %} + +{% block form_title %}{% trans "Register Yourself" %}{% endblock %} + +{% block form_content %} +{{ block.super }} +
+ {{ register_user_form.email.label_tag }} +
+ + {{ register_user_form.email }} +
+
    +
  • {% trans "Please provide a valid email address" %}
  • +
  • +
+
+ +
+ +
+{% endblock form_content %} diff --git a/shop/templates/shop/email/register-user-activate-body.txt b/shop/templates/shop/email/register-user-activate-body.txt new file mode 100644 index 000000000..aaa20c53a --- /dev/null +++ b/shop/templates/shop/email/register-user-activate-body.txt @@ -0,0 +1,12 @@ +{% load i18n %}{% autoescape off %} +{% blocktrans %}You're receiving this email because you registered a new account +for '{{ user }}' at {{ site_name }}.{% endblocktrans %} + +{% trans "Please go to the following webpage to activate your new account:" %} +{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %} + +{% trans "Thanks for visiting our site!" %} + +{% blocktrans %}The {{ site_name }} team{% endblocktrans %} + +{% endautoescape %} diff --git a/shop/templates/shop/email/register-user-activate-subject.txt b/shop/templates/shop/email/register-user-activate-subject.txt new file mode 100644 index 000000000..d9cf2f6ad --- /dev/null +++ b/shop/templates/shop/email/register-user-activate-subject.txt @@ -0,0 +1,3 @@ +{% load i18n %}{% autoescape off %} +{% blocktrans %}Activate your new account on {{ site_name }}{% endblocktrans %} +{% endautoescape %} diff --git a/shop/templates/shop/email/reset-password-body.txt b/shop/templates/shop/email/reset-password-body.txt index c3c6b9b16..98938beb7 100644 --- a/shop/templates/shop/email/reset-password-body.txt +++ b/shop/templates/shop/email/reset-password-body.txt @@ -1,11 +1,11 @@ {% load i18n %}{% autoescape off %} -{% blocktrans %}You're receiving this email because you requested a password reset for your user +{% blocktrans %}You're receiving this email because you requested to reset the password for your account '{{ user }}' at {{ site_name }}.{% endblocktrans %} -{% trans "Please go to the following page and choose a new password:" %} +{% trans "Please go to the following webpage and choose a new password:" %} {{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %} -{% trans "Thanks for using our site!" %} +{% trans "Thanks for visiting our site!" %} {% blocktrans %}The {{ site_name }} team{% endblocktrans %} diff --git a/shop/urls/auth.py b/shop/urls/auth.py index d0b1177db..e931aa0b5 100644 --- a/shop/urls/auth.py +++ b/shop/urls/auth.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from django.conf.urls import url from rest_auth.views import PasswordChangeView -from shop.forms.auth import RegisterUserForm, ContinueAsGuestForm +from shop.forms.auth import RegisterUserForm, RegisterUserActivateForm, ContinueAsGuestForm from shop.views.auth import AuthFormsView, LoginView, LogoutView, PasswordResetView urlpatterns = [ @@ -12,6 +12,8 @@ name='login'), url(r'^register/?$', AuthFormsView.as_view(form_class=RegisterUserForm), name='register-user'), + url(r'^register-activate/?$', AuthFormsView.as_view(form_class=RegisterUserActivateForm), + name='register-user-activate'), url(r'^continue/?$', AuthFormsView.as_view(form_class=ContinueAsGuestForm), name='continue-as-guest'), diff --git a/shop/views/auth.py b/shop/views/auth.py index 53113ff13..52011fe4f 100644 --- a/shop/views/auth.py +++ b/shop/views/auth.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.contrib.auth import logout, get_user_model +from django.contrib.auth import logout, get_user_model, authenticate, login from django.contrib.auth.models import AnonymousUser from django.contrib.auth.tokens import default_token_generator from django.utils.encoding import force_text @@ -146,4 +146,13 @@ def post(self, request, uidb64=None, token=None): serializer.errors, status=status.HTTP_400_BAD_REQUEST ) serializer.save() - return Response({"success": _("Password has been reset with the new password.")}) + + # TODO: the following code can be simplified to ``customer = serializer.save().customer`` + # whenever https://github.com/Tivix/django-rest-auth/pull/334 has been merged + customer = serializer.set_password_form.user.customer + customer.recognize_as_registered(request) + user = authenticate(username=customer.user.username, password=serializer.data['new_password1']) + login(request, user) + + msg = _("The password for '{email}' has been reset by a new password.") + return Response({'success': msg.format(email=customer.email)})