Merge remote-tracking branch 'origin/feature/gm-192' into feature/gm-192
This commit is contained in:
commit
2d41b9142b
18
apps/account/migrations/0009_user_unconfirmed_email.py
Normal file
18
apps/account/migrations/0009_user_unconfirmed_email.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-10 14:40
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('account', '0008_auto_20190912_1325'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='unconfirmed_email',
|
||||
field=models.EmailField(blank=True, default=None, max_length=254, null=True, verbose_name='unconfirmed email'),
|
||||
),
|
||||
]
|
||||
18
apps/account/migrations/0010_user_password_confirmed.py
Normal file
18
apps/account/migrations/0010_user_password_confirmed.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-10 17:21
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('account', '0009_user_unconfirmed_email'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='password_confirmed',
|
||||
field=models.BooleanField(default=True, verbose_name='is new password confirmed'),
|
||||
),
|
||||
]
|
||||
|
|
@ -60,8 +60,10 @@ class User(AbstractUser):
|
|||
blank=True, null=True, default=None)
|
||||
email = models.EmailField(_('email address'), blank=True,
|
||||
null=True, default=None)
|
||||
unconfirmed_email = models.EmailField(_('unconfirmed email'), blank=True, null=True, default=None)
|
||||
email_confirmed = models.BooleanField(_('email status'), default=False)
|
||||
newsletter = models.NullBooleanField(default=True)
|
||||
password_confirmed = models.BooleanField(_('is new password confirmed'), default=True, null=False)
|
||||
|
||||
EMAIL_FIELD = 'email'
|
||||
USERNAME_FIELD = 'username'
|
||||
|
|
@ -112,9 +114,15 @@ class User(AbstractUser):
|
|||
|
||||
def confirm_email(self):
|
||||
"""Method to confirm user email address"""
|
||||
self.email = self.unconfirmed_email
|
||||
self.unconfirmed_email = None
|
||||
self.email_confirmed = True
|
||||
self.save()
|
||||
|
||||
def confirm_password(self):
|
||||
self.password_confirmed = True
|
||||
self.save()
|
||||
|
||||
def approve(self):
|
||||
"""Set user is_active status to True"""
|
||||
self.is_active = True
|
||||
|
|
@ -149,6 +157,11 @@ class User(AbstractUser):
|
|||
"""Make a token for finish signup."""
|
||||
return password_token_generator.make_token(self)
|
||||
|
||||
@property
|
||||
def confirm_password_token(self):
|
||||
"""Make a token for new password confirmation """
|
||||
return GMTokenGenerator(purpose=GMTokenGenerator.CONFIRM_PASSWORD).make_token(self)
|
||||
|
||||
@property
|
||||
def get_user_uidb64(self):
|
||||
"""Get base64 value for user by primary key identifier"""
|
||||
|
|
@ -178,6 +191,16 @@ class User(AbstractUser):
|
|||
template_name=settings.RESETTING_TOKEN_TEMPLATE,
|
||||
context=context)
|
||||
|
||||
def confirm_password_template(self, country_code):
|
||||
"""Get confirm password template"""
|
||||
context = {'token': self.confirm_password_token,
|
||||
'country_code': country_code}
|
||||
context.update(self.base_template)
|
||||
return render_to_string(
|
||||
template_name=settings.CONFIRM_PASSWORD_TEMPLATE,
|
||||
context=context,
|
||||
)
|
||||
|
||||
def confirm_email_template(self, country_code):
|
||||
"""Get confirm email template"""
|
||||
context = {'token': self.confirm_email_token,
|
||||
|
|
|
|||
|
|
@ -61,9 +61,12 @@ class UserSerializer(serializers.ModelSerializer):
|
|||
|
||||
def update(self, instance, validated_data):
|
||||
"""Override update method"""
|
||||
old_email = instance.email
|
||||
instance = super().update(instance, validated_data)
|
||||
if 'email' in validated_data:
|
||||
instance.email_confirmed = False
|
||||
instance.email = old_email
|
||||
instance.unconfirmed_email = validated_data['email']
|
||||
instance.save()
|
||||
# Send verification link on user email for change email address
|
||||
if settings.USE_CELERY:
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
"""Serializers for account web"""
|
||||
from django.contrib.auth import password_validation as password_validators
|
||||
from django.conf import settings
|
||||
from rest_framework import serializers
|
||||
|
||||
from account import models
|
||||
from account import tasks
|
||||
from utils import exceptions as utils_exceptions
|
||||
from utils.methods import username_validator
|
||||
|
||||
|
|
@ -67,5 +69,16 @@ class PasswordResetConfirmSerializer(serializers.ModelSerializer):
|
|||
"""Override update method"""
|
||||
# Update user password from instance
|
||||
instance.set_password(validated_data.get('password'))
|
||||
instance.password_confirmed = False
|
||||
instance.save()
|
||||
if settings.USE_CELERY:
|
||||
tasks.send_reset_password_confirm.delay(
|
||||
user=instance,
|
||||
country_code=self.context.get('request').country_code,
|
||||
)
|
||||
else:
|
||||
tasks.send_reset_password_confirm(
|
||||
user=instance,
|
||||
country_code=self.context.get('request').country_code,
|
||||
)
|
||||
return instance
|
||||
|
|
|
|||
|
|
@ -22,6 +22,17 @@ def send_reset_password_email(user_id, country_code):
|
|||
f'DETAIL: Exception occurred for reset password: '
|
||||
f'{user_id}')
|
||||
|
||||
@shared_task
|
||||
def send_reset_password_confirm(user: models.User, country_code):
|
||||
""" Send email to user for applying new password. """
|
||||
try:
|
||||
user.send_email(subject=_('New password confirmation'),
|
||||
message=user.confirm_password_template(country_code))
|
||||
except:
|
||||
logger.error(f'METHOD_NAME: {send_reset_password_confirm.__name__}\n'
|
||||
f'DETAIL: Exception occured for new passwordconfirmation',
|
||||
f'{user.id}')
|
||||
|
||||
|
||||
@shared_task
|
||||
def confirm_new_email_address(user_id, country_code):
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ app_name = 'account'
|
|||
urlpatterns = [
|
||||
path('user/', views.UserRetrieveUpdateView.as_view(), name='user-retrieve-update'),
|
||||
path('change-password/', views.ChangePasswordView.as_view(), name='change-password'),
|
||||
path('change-password-confirm/<uuid64>/<token>/', views.ConfirmPasswordView.as_view(), name='change-password'),
|
||||
path('email/confirm/', views.SendConfirmationEmailView.as_view(), name='send-confirm-email'),
|
||||
path('email/confirm/<uidb64>/<token>/', views.ConfirmEmailView.as_view(), name='confirm-email'),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -91,6 +91,32 @@ class ConfirmEmailView(JWTGenericViewMixin):
|
|||
else:
|
||||
raise utils_exceptions.UserNotFoundError()
|
||||
|
||||
class ConfirmPasswordView(JWTGenericViewMixin):
|
||||
"""View for applying newly set password"""
|
||||
|
||||
permission_classes = (permissions.AllowAny,)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
uidb64 = kwargs.get('uidb64')
|
||||
token = kwargs.get('token')
|
||||
uid = force_text(urlsafe_base64_decode(uidb64))
|
||||
user_qs = models.User.objects.filter(pk=uid)
|
||||
if user_qs.exists():
|
||||
user = user_qs.first()
|
||||
if not GMTokenGenerator(GMTokenGenerator.CONFIRM_PASSWORD).check_token(
|
||||
user, token):
|
||||
raise utils_exceptions.NotValidTokenError()
|
||||
user.confirm_password()
|
||||
tokens = user.create_jwt_tokens()
|
||||
return self._put_cookies_in_response(
|
||||
cookies=self._put_data_in_cookies(
|
||||
access_token=tokens.get('access_token'),
|
||||
refresh_token=tokens.get('refresh_token')),
|
||||
response=Response(status=status.HTTP_200_OK))
|
||||
else:
|
||||
raise utils_exceptions.UserNotFoundError()
|
||||
|
||||
|
||||
|
||||
# Firebase Cloud Messaging
|
||||
class FCMDeviceViewSet(generics.GenericAPIView):
|
||||
|
|
|
|||
|
|
@ -108,8 +108,8 @@ class LoginByUsernameOrEmailSerializer(SourceSerializerMixin,
|
|||
"""Override validate method"""
|
||||
username_or_email = attrs.pop('username_or_email')
|
||||
password = attrs.pop('password')
|
||||
user_qs = account_models.User.objects.filter(Q(username=username_or_email) |
|
||||
Q(email=username_or_email))
|
||||
user_qs = account_models.User.objects.filter(password_confirmed=True)\
|
||||
.filter(Q(username=username_or_email) | Q(email=username_or_email))
|
||||
if not user_qs.exists():
|
||||
raise utils_exceptions.WrongAuthCredentials()
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ class LastableService(AbstractBookingService):
|
|||
super().check_whether_booking_available(restaurant_id, date)
|
||||
url = f'{self.url}v1/restaurant/{restaurant_id}/offers'
|
||||
r = requests.get(url, headers=self.get_common_headers(), proxies=self.proxies)
|
||||
response = json.loads(r.content)['data']
|
||||
response = json.loads(r.content).get('data')
|
||||
if not status.is_success(r.status_code) or not response:
|
||||
return False
|
||||
self.response = response
|
||||
|
|
|
|||
|
|
@ -35,9 +35,9 @@ class CheckWhetherBookingAvailable(generics.GenericAPIView):
|
|||
|
||||
response = {
|
||||
'available': is_booking_available,
|
||||
'type': service.service,
|
||||
'type': service.service if service else None,
|
||||
}
|
||||
response.update({'details': service.response} if service.response else {})
|
||||
response.update({'details': service.response} if service and service.response else {})
|
||||
return Response(data=response, status=200)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -11,13 +11,11 @@ from django.db import models
|
|||
from django.db.models import When, Case, F, ExpressionWrapper, Subquery, Q
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from elasticsearch_dsl import Q
|
||||
from phonenumber_field.modelfields import PhoneNumberField
|
||||
|
||||
from collection.models import Collection
|
||||
from tag.models import Tag, TagCategory
|
||||
from main.models import Award, MetaDataContent
|
||||
from location.models import Address
|
||||
from main.models import Award
|
||||
from review.models import Review
|
||||
from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin,
|
||||
TranslatedFieldsMixin, BaseAttributes)
|
||||
|
|
@ -107,15 +105,15 @@ class EstablishmentQuerySet(models.QuerySet):
|
|||
else:
|
||||
return self.none()
|
||||
|
||||
def es_search(self, value, locale=None):
|
||||
"""Search text via ElasticSearch."""
|
||||
from search_indexes.documents import EstablishmentDocument
|
||||
search = EstablishmentDocument.search().filter(
|
||||
Q('match', name=value) |
|
||||
Q('match', **{f'description.{locale}': value})
|
||||
).execute()
|
||||
ids = [result.meta.id for result in search]
|
||||
return self.filter(id__in=ids)
|
||||
# def es_search(self, value, locale=None):
|
||||
# """Search text via ElasticSearch."""
|
||||
# from search_indexes.documents import EstablishmentDocument
|
||||
# search = EstablishmentDocument.search().filter(
|
||||
# Elastic_Q('match', name=value) |
|
||||
# Elastic_Q('match', **{f'description.{locale}': value})
|
||||
# ).execute()
|
||||
# ids = [result.meta.id for result in search]
|
||||
# return self.filter(id__in=ids)
|
||||
|
||||
def by_country_code(self, code):
|
||||
"""Return establishments by country code"""
|
||||
|
|
@ -189,7 +187,8 @@ class EstablishmentQuerySet(models.QuerySet):
|
|||
return self.filter(id__in=subquery_filter_by_distance) \
|
||||
.annotate_intermediate_public_mark() \
|
||||
.annotate_mark_similarity(mark=establishment.public_mark) \
|
||||
.order_by('mark_similarity')
|
||||
.order_by('mark_similarity') \
|
||||
.distinct('mark_similarity', 'id')
|
||||
else:
|
||||
return self.none()
|
||||
|
||||
|
|
|
|||
|
|
@ -258,12 +258,14 @@ class GMTokenGenerator(PasswordResetTokenGenerator):
|
|||
RESET_PASSWORD = 1
|
||||
CHANGE_PASSWORD = 2
|
||||
CONFIRM_EMAIL = 3
|
||||
CONFIRM_PASSWORD = 4
|
||||
|
||||
TOKEN_CHOICES = (
|
||||
CHANGE_EMAIL,
|
||||
RESET_PASSWORD,
|
||||
CHANGE_PASSWORD,
|
||||
CONFIRM_EMAIL
|
||||
CONFIRM_EMAIL,
|
||||
CONFIRM_PASSWORD,
|
||||
)
|
||||
|
||||
def __init__(self, purpose: int):
|
||||
|
|
@ -279,7 +281,8 @@ class GMTokenGenerator(PasswordResetTokenGenerator):
|
|||
self.purpose == self.CONFIRM_EMAIL:
|
||||
fields.extend([str(user.email_confirmed), str(user.email)])
|
||||
elif self.purpose == self.RESET_PASSWORD or \
|
||||
self.purpose == self.CHANGE_PASSWORD:
|
||||
self.purpose == self.CHANGE_PASSWORD or \
|
||||
self.purpose == self.CONFIRM_PASSWORD:
|
||||
fields.append(str(user.password))
|
||||
return fields
|
||||
|
||||
|
|
|
|||
|
|
@ -406,6 +406,7 @@ PASSWORD_RESET_TIMEOUT_DAYS = 1
|
|||
# TEMPLATES
|
||||
RESETTING_TOKEN_TEMPLATE = 'account/password_reset_email.html'
|
||||
CHANGE_EMAIL_TEMPLATE = 'account/change_email.html'
|
||||
CONFIRM_PASSWORD_TEMPLATE = 'account/password_confirm_email.html'
|
||||
CONFIRM_EMAIL_TEMPLATE = 'authorization/confirm_email.html'
|
||||
NEWS_EMAIL_TEMPLATE = "news/news_email.html"
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
{% trans "Please go to the following page for confirmation new email address:" %}
|
||||
|
||||
https://{{ country_code }}.{{ domain_uri }}/registered/{{ uidb64 }}/{{ token }}/
|
||||
https://{{ country_code }}.{{ domain_uri }}/change-email-confirm/{{ uidb64 }}/{{ token }}/
|
||||
|
||||
{% trans "Thanks for using our site!" %}
|
||||
|
||||
|
|
|
|||
11
project/templates/account/password_confirm_email.html
Normal file
11
project/templates/account/password_confirm_email.html
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{% load i18n %}{% autoescape off %}
|
||||
{% blocktrans %}Confirm a password reset for your user account at {{ site_name }}.{% endblocktrans %}
|
||||
|
||||
{% trans "Please go to the following page:" %}
|
||||
|
||||
https://{{ country_code }}.{{ domain_uri }}/confirm-new-password/{{ uidb64 }}/{{ token }}/
|
||||
|
||||
{% trans "Thanks for using our site!" %}
|
||||
|
||||
{% blocktrans %}The {{ site_name }} team{% endblocktrans %}
|
||||
{% endautoescape %}
|
||||
|
|
@ -36,7 +36,7 @@
|
|||
<div class="letter__text" style="margin: 0 0 30px;line-height: 21px;letter-spacing: -0.34px; overflow-x: hidden;">
|
||||
{{ description | safe }}
|
||||
</div>
|
||||
<a href="https://{{ country_code }}.{{ domain_uri }}{% url 'web:news:rud' slug %}" style="text-decoration: none;" target="_blank">
|
||||
<a href="https://{{ country_code }}.{{ domain_uri }}/news/{{ slug }}" style="text-decoration: none;" target="_blank">
|
||||
<button class="letter__button" style="display: block;margin: 30px auto;padding: 0;font-family: "pt serif": ;, sans-serif: ;font-size: 1.25rem;letter-spacing: 1px;text-align: center;color: #000;background: #ffee29;border: 2px solid #ffee29;text-transform: uppercase;min-width: 238px;height: 46px;background-color: #ffee29;">Go to news</button>
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user