From 7ad0e32027b37a5be3a4cadbe4a94582fb521ef7 Mon Sep 17 00:00:00 2001 From: Oliver Zhou Date: Wed, 12 Jul 2017 00:03:48 -0700 Subject: [PATCH 1/6] Add reregistration option : conf.py configurations to control email/response, UserReregistrationEmailFactory, override RegistrationView.create(), add RegistrationView.send_reregistration_email() --- djoser/conf.py | 2 ++ djoser/serializers.py | 1 - .../templates/reregistration_email_body.html | 17 ++++++++++++++ .../templates/reregistration_email_body.txt | 14 ++++++++++++ .../reregistration_email_subject.txt | 3 +++ djoser/utils.py | 14 ++++++++++++ djoser/views.py | 22 +++++++++++++++++++ 7 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 djoser/templates/reregistration_email_body.html create mode 100644 djoser/templates/reregistration_email_body.txt create mode 100644 djoser/templates/reregistration_email_subject.txt diff --git a/djoser/conf.py b/djoser/conf.py index 5c3a036a..785e0fce 100644 --- a/djoser/conf.py +++ b/djoser/conf.py @@ -15,6 +15,7 @@ 'USE_HTML_EMAIL_TEMPLATES': False, 'SEND_ACTIVATION_EMAIL': False, 'SEND_CONFIRMATION_EMAIL': False, + 'SEND_REREGISTRATION_EMAIL': False, 'SET_PASSWORD_RETYPE': False, 'SET_USERNAME_RETYPE': False, 'PASSWORD_RESET_CONFIRM_RETYPE': False, @@ -38,6 +39,7 @@ }, 'LOGOUT_ON_PASSWORD_CHANGE': False, 'USER_EMAIL_FIELD_NAME': 'email', + 'REREGISTRATION_SHOW_RESPONSE': True, } SETTINGS_TO_IMPORT = ['TOKEN_MODEL'] diff --git a/djoser/serializers.py b/djoser/serializers.py index 5d28b803..9c7771e4 100644 --- a/djoser/serializers.py +++ b/djoser/serializers.py @@ -72,7 +72,6 @@ def perform_create(self, validated_data): return user - class LoginSerializer(serializers.Serializer): password = serializers.CharField( required=False, style={'input_type': 'password'} diff --git a/djoser/templates/reregistration_email_body.html b/djoser/templates/reregistration_email_body.html new file mode 100644 index 00000000..38a766ae --- /dev/null +++ b/djoser/templates/reregistration_email_body.html @@ -0,0 +1,17 @@ +{% load i18n %}{% autoescape off %} + + +

+{% blocktrans %}You're receiving this email because someone tried to reregister your user account at {{ site_name }}.{% endblocktrans %}

+ +

{% trans "Please go to the following page and choose a new password:" %}

+

{% block reset_link %} +{{ protocol }}://{{ domain }}/{{ url }} +{% endblock %}

+

{% trans "Your username, in case you've forgotten:" %} {{ user.get_username }}

+ +

{% trans "Thanks for using our site!" %}

+ +

{% blocktrans %}The {{ site_name }} team{% endblocktrans %}

+ +{% endautoescape %} diff --git a/djoser/templates/reregistration_email_body.txt b/djoser/templates/reregistration_email_body.txt new file mode 100644 index 00000000..0cbd2658 --- /dev/null +++ b/djoser/templates/reregistration_email_body.txt @@ -0,0 +1,14 @@ +{% load i18n %}{% autoescape off %} +{% blocktrans %}You're receiving this email because someone tried to reregister your user account at {{ site_name }}.{% endblocktrans %} + +{% trans "Please go to the following page and choose a new password:" %} +{% block reset_link %} +{{ protocol }}://{{ domain }}/{{ url }} +{% endblock %} +{% trans "Your username, in case you've forgotten:" %} {{ user.get_username }} + +{% trans "Thanks for using our site!" %} + +{% blocktrans %}The {{ site_name }} team{% endblocktrans %} + +{% endautoescape %} diff --git a/djoser/templates/reregistration_email_subject.txt b/djoser/templates/reregistration_email_subject.txt new file mode 100644 index 00000000..76085c94 --- /dev/null +++ b/djoser/templates/reregistration_email_subject.txt @@ -0,0 +1,3 @@ +{% load i18n %}{% autoescape off %} +{% blocktrans %}Reregistration requested on {{ site_name }}{% endblocktrans %} +{% endautoescape %} \ No newline at end of file diff --git a/djoser/utils.py b/djoser/utils.py index 5a8f6eec..31bc3d77 100644 --- a/djoser/utils.py +++ b/djoser/utils.py @@ -143,6 +143,20 @@ def get_context(self): return context +class UserReregistrationEmailFactory(UserEmailFactoryBase): + subject_template_name = 'reregistration_email_subject.txt' + plain_body_template_name = 'reregistration_email_body.txt' + if settings.USE_HTML_EMAIL_TEMPLATES: + html_body_template_name = 'reregistration_email_body.html' + + def get_context(self): + context = super(UserReregistrationEmailFactory, self).get_context() + context['url'] = settings.PASSWORD_RESET_CONFIRM_URL.format( + **context + ) + return context + + class UserConfirmationEmailFactory(UserEmailFactoryBase): subject_template_name = 'confirmation_email_subject.txt' plain_body_template_name = 'confirmation_email_body.txt' diff --git a/djoser/views.py b/djoser/views.py index 5ffb9b20..8b119b13 100644 --- a/djoser/views.py +++ b/djoser/views.py @@ -55,6 +55,21 @@ class RegistrationView(generics.CreateAPIView): permissions.AllowAny, ) + def create(self, request, *args, **kwargs): + if not settings.REREGISTRATION_SHOW_RESPONSE: + try: + username_input = {User.USERNAME_FIELD: request.POST.get(User.USERNAME_FIELD)} + existing_user = User.objects.get(**username_input) + serializer = self.get_serializer(instance=existing_user) + if settings.SEND_REREGISTRATION_EMAIL: + self.send_reregistration_email(existing_user) + headers = self.get_success_headers(serializer.data) + return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) + except User.DoesNotExist: + pass + response = super(RegistrationView, self).create(request, *args, **kwargs) + return response + def perform_create(self, serializer): user = serializer.save() signals.user_registered.send( @@ -79,6 +94,13 @@ def send_confirmation_email(self, user): email = email_factory.create() email.send() + def send_reregistration_email(self, user): + email_factory = utils.UserReregistrationEmailFactory.from_request( + self.request, user=user + ) + email = email_factory.create() + email.send() + class LoginView(utils.ActionViewMixin, generics.GenericAPIView): """ From b0581375c475acc5715993aa960f9e75b9a11545 Mon Sep 17 00:00:00 2001 From: Oliver Zhou Date: Wed, 12 Jul 2017 03:13:23 -0700 Subject: [PATCH 2/6] Docs and additional email templates --- .../templates/reregistration_email_body.html | 4 ++-- .../templates/reregistration_email_body.txt | 4 ++-- .../reregistration_inactive_email_body.html | 14 +++++++++++++ .../reregistration_inactive_email_body.txt | 11 ++++++++++ docs/source/settings.rst | 20 +++++++++++++++++++ 5 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 djoser/templates/reregistration_inactive_email_body.html create mode 100644 djoser/templates/reregistration_inactive_email_body.txt diff --git a/djoser/templates/reregistration_email_body.html b/djoser/templates/reregistration_email_body.html index 38a766ae..4a70b624 100644 --- a/djoser/templates/reregistration_email_body.html +++ b/djoser/templates/reregistration_email_body.html @@ -2,9 +2,9 @@

-{% blocktrans %}You're receiving this email because someone tried to reregister your user account at {{ site_name }}.{% endblocktrans %}

+{% blocktrans %}You're receiving this email because someone tried to reregister a user account with this email at {{ site_name }}.{% endblocktrans %}

-

{% trans "Please go to the following page and choose a new password:" %}

+

{% trans "If this wasn't you, please go to the following page and choose a new password:" %}

{% block reset_link %} {{ protocol }}://{{ domain }}/{{ url }} {% endblock %}

diff --git a/djoser/templates/reregistration_email_body.txt b/djoser/templates/reregistration_email_body.txt index 0cbd2658..f6fd8c1f 100644 --- a/djoser/templates/reregistration_email_body.txt +++ b/djoser/templates/reregistration_email_body.txt @@ -1,7 +1,7 @@ {% load i18n %}{% autoescape off %} -{% blocktrans %}You're receiving this email because someone tried to reregister your user account at {{ site_name }}.{% endblocktrans %} +{% blocktrans %}You're receiving this email because someone tried to reregister a user account with this email at {{ site_name }}.{% endblocktrans %}

-{% trans "Please go to the following page and choose a new password:" %} +{% trans "If this wasn't you, please go to the following page and choose a new password:" %} {% block reset_link %} {{ protocol }}://{{ domain }}/{{ url }} {% endblock %} diff --git a/djoser/templates/reregistration_inactive_email_body.html b/djoser/templates/reregistration_inactive_email_body.html new file mode 100644 index 00000000..1d1941f3 --- /dev/null +++ b/djoser/templates/reregistration_inactive_email_body.html @@ -0,0 +1,14 @@ +{% load i18n %}{% autoescape off %} + + +

+{% blocktrans %}You're receiving this email because someone tried to reregister your user account at {{ site_name }}.{% endblocktrans %}

+{% blocktrans %}Please find the previously sent activation email, or request a new one to properly set this account up{% endblocktrans %} + +

{% trans "Your username, in case you've forgotten:" %} {{ user.get_username }}

+ +

{% trans "Thanks for using our site!" %}

+ +

{% blocktrans %}The {{ site_name }} team{% endblocktrans %}

+ +{% endautoescape %} diff --git a/djoser/templates/reregistration_inactive_email_body.txt b/djoser/templates/reregistration_inactive_email_body.txt new file mode 100644 index 00000000..f07fe4bd --- /dev/null +++ b/djoser/templates/reregistration_inactive_email_body.txt @@ -0,0 +1,11 @@ +{% load i18n %}{% autoescape off %} +{% blocktrans %}You're receiving this email because someone tried to reregister your user account at {{ site_name }}.{% endblocktrans %} +{% blocktrans %}Please find the previously sent activation email, or request a new one to properly set this account up{% endblocktrans %} + +{% trans "Your username, in case you've forgotten:" %} {{ user.get_username }} + +{% trans "Thanks for using our site!" %} + +{% blocktrans %}The {{ site_name }} team{% endblocktrans %} + +{% endautoescape %} diff --git a/docs/source/settings.rst b/docs/source/settings.rst index e03428eb..a76bc4bb 100644 --- a/docs/source/settings.rst +++ b/docs/source/settings.rst @@ -56,6 +56,26 @@ If ``True``, register or activation endpoint will send confirmation email to use **Default**: ``False`` +SEND_REREGISTRATION_EMAIL +------------------------- + +If ``True``, register endpoint will send warning and reminder email to user. +Only active users are able to reset their passwords (User.is_active is True). +The email will ask the User to reset their password since someone is trying to +re-register their account. +If User.is_active is False, will send a warning + +**Default**: ``False`` + +REREGISTRATION_SHOW_RESPONSE +---------------------------- + +If ``False`` (default), the ``/register/`` endpoint will always return +a ``HTTP_201_CREATED`` response, as well as the UserSerializer serializer.data response +of one of those users, so that it makes it difficult to distinguish if a User has this email + +**Default**: ``True`` + ACTIVATION_URL -------------- From 75f9e00de2c512a1ec88bdff2a8e7c282e519c22 Mon Sep 17 00:00:00 2001 From: Oliver Zhou Date: Wed, 12 Jul 2017 03:26:20 -0700 Subject: [PATCH 3/6] Fix typo of extra

tag --- djoser/templates/reregistration_email_body.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djoser/templates/reregistration_email_body.txt b/djoser/templates/reregistration_email_body.txt index f6fd8c1f..902f62fb 100644 --- a/djoser/templates/reregistration_email_body.txt +++ b/djoser/templates/reregistration_email_body.txt @@ -1,5 +1,5 @@ {% load i18n %}{% autoescape off %} -{% blocktrans %}You're receiving this email because someone tried to reregister a user account with this email at {{ site_name }}.{% endblocktrans %}

+{% blocktrans %}You're receiving this email because someone tried to reregister a user account with this email at {{ site_name }}.{% endblocktrans %} {% trans "If this wasn't you, please go to the following page and choose a new password:" %} {% block reset_link %} From b3c694cd5381479438384db38dbbf919a71dcd5a Mon Sep 17 00:00:00 2001 From: Oliver Zhou Date: Wed, 12 Jul 2017 03:31:14 -0700 Subject: [PATCH 4/6] Create the ability to send different kinds of emails to Users who are either is_active or not, also allow for all combinations of settings.REREGISTRATION_SHOW_RESPONSE and settings.SEND_REREGISTRATION_EMAIL --- djoser/utils.py | 14 ++++++++++++++ djoser/views.py | 38 +++++++++++++++++++++++++++----------- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/djoser/utils.py b/djoser/utils.py index 31bc3d77..ce1f4790 100644 --- a/djoser/utils.py +++ b/djoser/utils.py @@ -157,6 +157,20 @@ def get_context(self): return context +class UserReregistrationInactiveEmailFactory(UserEmailFactoryBase): + subject_template_name = 'reregistration_email_subject.txt' + plain_body_template_name = 'reregistration_inactive_email_body.txt' + if settings.USE_HTML_EMAIL_TEMPLATES: + html_body_template_name = 'reregistration_inactive_email_body.html' + + def get_context(self): + context = super(UserReregistrationInactiveEmailFactory, self).get_context() + context['url'] = settings.PASSWORD_RESET_CONFIRM_URL.format( + **context + ) + return context + + class UserConfirmationEmailFactory(UserEmailFactoryBase): subject_template_name = 'confirmation_email_subject.txt' plain_body_template_name = 'confirmation_email_body.txt' diff --git a/djoser/views.py b/djoser/views.py index 8b119b13..e01cd558 100644 --- a/djoser/views.py +++ b/djoser/views.py @@ -54,19 +54,20 @@ class RegistrationView(generics.CreateAPIView): permission_classes = ( permissions.AllowAny, ) + _users = None def create(self, request, *args, **kwargs): - if not settings.REREGISTRATION_SHOW_RESPONSE: - try: - username_input = {User.USERNAME_FIELD: request.POST.get(User.USERNAME_FIELD)} - existing_user = User.objects.get(**username_input) - serializer = self.get_serializer(instance=existing_user) + try: + email_users = self.get_email_users(request.POST.get('email')) + for user in email_users: + serializer = self.get_serializer(instance=user) if settings.SEND_REREGISTRATION_EMAIL: - self.send_reregistration_email(existing_user) + self.send_reregistration_email(user) headers = self.get_success_headers(serializer.data) + if not settings.REREGISTRATION_SHOW_RESPONSE and email_users: return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) - except User.DoesNotExist: - pass + except User.DoesNotExist: + pass response = super(RegistrationView, self).create(request, *args, **kwargs) return response @@ -95,12 +96,27 @@ def send_confirmation_email(self, user): email.send() def send_reregistration_email(self, user): - email_factory = utils.UserReregistrationEmailFactory.from_request( - self.request, user=user - ) + if user.is_active: + email_factory = utils.UserReregistrationEmailFactory.from_request( + self.request, user=user + ) + else: + email_factory = utils.UserReregistrationInactiveEmailFactory.from_request( + self.request, user=user + ) email = email_factory.create() email.send() + def get_email_users(self, email): + if self._users is None: + email_field_name = get_user_email_field_name(User) + email_users_kwargs = { + email_field_name + '__iexact': email, + } + email_users = User._default_manager.filter(**email_users_kwargs) + self._users = [u for u in email_users if u.has_usable_password()] + return self._users + class LoginView(utils.ActionViewMixin, generics.GenericAPIView): """ From 12ef6540042b9d1e9830fa3103291d2ecca692c6 Mon Sep 17 00:00:00 2001 From: Oliver Zhou Date: Wed, 12 Jul 2017 14:00:08 -0700 Subject: [PATCH 5/6] Use request.data instead of request.POST --- djoser/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djoser/views.py b/djoser/views.py index e01cd558..14f93100 100644 --- a/djoser/views.py +++ b/djoser/views.py @@ -58,7 +58,7 @@ class RegistrationView(generics.CreateAPIView): def create(self, request, *args, **kwargs): try: - email_users = self.get_email_users(request.POST.get('email')) + email_users = self.get_email_users(request.data.get('email')) for user in email_users: serializer = self.get_serializer(instance=user) if settings.SEND_REREGISTRATION_EMAIL: From 9a1e5d7751997a977792ff0bfe70802f10b83fd0 Mon Sep 17 00:00:00 2001 From: Oliver Zhou Date: Fri, 21 Jul 2017 14:05:24 -0700 Subject: [PATCH 6/6] Address a few requests in PR : Rename REREGISTRATION_SHOW_RESPONSE to REGISTRATION_SHOW_EMAIL_FOUND, use "get_email_field_name" to properly find the User model's email field, rename SEND_REREGISTRATION_EMAIL to RESEND_REGISTRATION_EMAIL --- djoser/conf.py | 4 ++-- djoser/views.py | 13 +++++++------ docs/source/settings.rst | 6 +++--- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/djoser/conf.py b/djoser/conf.py index 785e0fce..1a8b2c23 100644 --- a/djoser/conf.py +++ b/djoser/conf.py @@ -15,7 +15,7 @@ 'USE_HTML_EMAIL_TEMPLATES': False, 'SEND_ACTIVATION_EMAIL': False, 'SEND_CONFIRMATION_EMAIL': False, - 'SEND_REREGISTRATION_EMAIL': False, + 'RESEND_REGISTRATION_EMAIL': False, 'SET_PASSWORD_RETYPE': False, 'SET_USERNAME_RETYPE': False, 'PASSWORD_RESET_CONFIRM_RETYPE': False, @@ -39,7 +39,7 @@ }, 'LOGOUT_ON_PASSWORD_CHANGE': False, 'USER_EMAIL_FIELD_NAME': 'email', - 'REREGISTRATION_SHOW_RESPONSE': True, + 'REGISTRATION_SHOW_EMAIL_FOUND': True, } SETTINGS_TO_IMPORT = ['TOKEN_MODEL'] diff --git a/djoser/views.py b/djoser/views.py index 14f93100..470fcd87 100644 --- a/djoser/views.py +++ b/djoser/views.py @@ -58,13 +58,14 @@ class RegistrationView(generics.CreateAPIView): def create(self, request, *args, **kwargs): try: - email_users = self.get_email_users(request.data.get('email')) - for user in email_users: + email_field_name = get_user_email_field_name(User) + users = self.get_email_users(request.data.get(email_field_name)) + for user in users: serializer = self.get_serializer(instance=user) - if settings.SEND_REREGISTRATION_EMAIL: - self.send_reregistration_email(user) + if settings.RESEND_REGISTRATION_EMAIL: + self.resend_registration_email(user) headers = self.get_success_headers(serializer.data) - if not settings.REREGISTRATION_SHOW_RESPONSE and email_users: + if not settings.REGISTRATION_SHOW_EMAIL_FOUND and users: return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) except User.DoesNotExist: pass @@ -95,7 +96,7 @@ def send_confirmation_email(self, user): email = email_factory.create() email.send() - def send_reregistration_email(self, user): + def resend_registration_email(self, user): if user.is_active: email_factory = utils.UserReregistrationEmailFactory.from_request( self.request, user=user diff --git a/docs/source/settings.rst b/docs/source/settings.rst index a76bc4bb..87ee2fef 100644 --- a/docs/source/settings.rst +++ b/docs/source/settings.rst @@ -56,7 +56,7 @@ If ``True``, register or activation endpoint will send confirmation email to use **Default**: ``False`` -SEND_REREGISTRATION_EMAIL +RESEND_REGISTRATION_EMAIL ------------------------- If ``True``, register endpoint will send warning and reminder email to user. @@ -67,10 +67,10 @@ If User.is_active is False, will send a warning **Default**: ``False`` -REREGISTRATION_SHOW_RESPONSE +REGISTRATION_SHOW_EMAIL_FOUND ---------------------------- -If ``False`` (default), the ``/register/`` endpoint will always return +If ``True`` (default), the ``/register/`` endpoint will always return a ``HTTP_201_CREATED`` response, as well as the UserSerializer serializer.data response of one of those users, so that it makes it difficult to distinguish if a User has this email