Merge remote-tracking branch 'origin/develop' into feature/backoffice
This commit is contained in:
commit
10d9b52bec
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -19,3 +19,6 @@ logs/
|
|||
/datadir/
|
||||
/_files/
|
||||
/geoip_db/
|
||||
|
||||
# dev
|
||||
./docker-compose.override.yml
|
||||
|
|
@ -15,11 +15,13 @@ class UserAdmin(BaseUserAdmin):
|
|||
list_filter = ('is_active', 'is_staff', 'is_superuser', 'email_confirmed',
|
||||
'groups',)
|
||||
search_fields = ('email', 'first_name', 'last_name')
|
||||
readonly_fields = ('last_login', 'date_joined',)
|
||||
readonly_fields = ('last_login', 'date_joined', 'image_preview', 'cropped_image_preview')
|
||||
fieldsets = (
|
||||
(None, {'fields': ('email', 'password',)}),
|
||||
(_('Personal info'), {
|
||||
'fields': ('username', 'first_name', 'last_name', 'image')}),
|
||||
'fields': ('username', 'first_name', 'last_name',
|
||||
'image_url', 'image_preview',
|
||||
'cropped_image_url', 'cropped_image_preview',)}),
|
||||
(_('Subscription'), {
|
||||
'fields': (
|
||||
'newsletter',
|
||||
|
|
@ -47,11 +49,14 @@ class UserAdmin(BaseUserAdmin):
|
|||
|
||||
short_name.short_description = _('Name')
|
||||
|
||||
def image_preview(self, obj):
|
||||
"""Get user image preview"""
|
||||
return obj.image_tag
|
||||
|
||||
@admin.register(models.ResetPasswordToken)
|
||||
class ResetPasswordToken(admin.ModelAdmin):
|
||||
"""Model admin for ResetPasswordToken"""
|
||||
list_display = ('id', 'user', 'expiry_datetime')
|
||||
list_filter = ('expiry_datetime', 'user')
|
||||
search_fields = ('user', )
|
||||
readonly_fields = ('user', 'key', )
|
||||
image_preview.short_description = 'Image preview'
|
||||
|
||||
def cropped_image_preview(self, obj):
|
||||
"""Get user cropped image preview"""
|
||||
return obj.cropped_image_tag
|
||||
|
||||
cropped_image_preview.short_description = 'Cropped image preview'
|
||||
|
|
|
|||
16
apps/account/migrations/0006_delete_resetpasswordtoken.py
Normal file
16
apps/account/migrations/0006_delete_resetpasswordtoken.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# Generated by Django 2.2.4 on 2019-09-12 11:42
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('account', '0005_user_cropped_image'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name='ResetPasswordToken',
|
||||
),
|
||||
]
|
||||
21
apps/account/migrations/0007_auto_20190912_1323.py
Normal file
21
apps/account/migrations/0007_auto_20190912_1323.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# Generated by Django 2.2.4 on 2019-09-12 13:23
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('account', '0006_delete_resetpasswordtoken'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='user',
|
||||
name='cropped_image',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='user',
|
||||
name='image',
|
||||
),
|
||||
]
|
||||
23
apps/account/migrations/0008_auto_20190912_1325.py
Normal file
23
apps/account/migrations/0008_auto_20190912_1325.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 2.2.4 on 2019-09-12 13:25
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('account', '0007_auto_20190912_1323'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='cropped_image_url',
|
||||
field=models.URLField(blank=True, default=None, null=True, verbose_name='Cropped image URL path'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='image_url',
|
||||
field=models.URLField(blank=True, default=None, null=True, verbose_name='Image URL path'),
|
||||
),
|
||||
]
|
||||
|
|
@ -7,13 +7,12 @@ from django.db import models
|
|||
from django.template.loader import render_to_string
|
||||
from django.utils import timezone
|
||||
from django.utils.encoding import force_bytes
|
||||
from django.utils.html import mark_safe
|
||||
from django.utils.http import urlsafe_base64_encode
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from easy_thumbnails.fields import ThumbnailerImageField
|
||||
from rest_framework.authtoken.models import Token
|
||||
|
||||
from authorization.models import Application
|
||||
from utils.methods import image_path
|
||||
from utils.models import GMTokenGenerator
|
||||
from utils.models import ImageMixin, ProjectBaseMixin, PlatformMixin
|
||||
from utils.tokens import GMRefreshToken
|
||||
|
|
@ -52,16 +51,13 @@ class UserQuerySet(models.QuerySet):
|
|||
return self.filter(oauth2_provider_refreshtoken__token=token,
|
||||
oauth2_provider_refreshtoken__expires__gt=timezone.now())
|
||||
|
||||
def by_username(self, username: str):
|
||||
"""Filter users by username."""
|
||||
return self.filter(username=username)
|
||||
|
||||
|
||||
class User(ImageMixin, AbstractUser):
|
||||
class User(AbstractUser):
|
||||
"""Base user model."""
|
||||
cropped_image = ThumbnailerImageField(upload_to=image_path,
|
||||
blank=True, null=True, default=None,
|
||||
verbose_name=_('Crop image'))
|
||||
image_url = models.URLField(verbose_name=_('Image URL path'),
|
||||
blank=True, null=True, default=None)
|
||||
cropped_image_url = models.URLField(verbose_name=_('Cropped image URL path'),
|
||||
blank=True, null=True, default=None)
|
||||
email = models.EmailField(_('email address'), blank=True,
|
||||
null=True, default=None)
|
||||
email_confirmed = models.BooleanField(_('email status'), default=False)
|
||||
|
|
@ -98,9 +94,9 @@ class User(ImageMixin, AbstractUser):
|
|||
self.is_active = switcher
|
||||
self.save()
|
||||
|
||||
def create_jwt_tokens(self, source: int):
|
||||
def create_jwt_tokens(self, source: int = None):
|
||||
"""Create JWT tokens for user"""
|
||||
token = GMRefreshToken.for_user_by_source(self, source)
|
||||
token = GMRefreshToken.for_user(self, source)
|
||||
return {
|
||||
'access_token': str(token.access_token),
|
||||
'refresh_token': str(token),
|
||||
|
|
@ -151,7 +147,7 @@ class User(ImageMixin, AbstractUser):
|
|||
@property
|
||||
def reset_password_token(self):
|
||||
"""Make a token for finish signup."""
|
||||
return GMTokenGenerator(purpose=GMTokenGenerator.RESET_PASSWORD).make_token(self)
|
||||
return password_token_generator.make_token(self)
|
||||
|
||||
@property
|
||||
def get_user_uidb64(self):
|
||||
|
|
@ -159,112 +155,43 @@ class User(ImageMixin, AbstractUser):
|
|||
return urlsafe_base64_encode(force_bytes(self.pk))
|
||||
|
||||
@property
|
||||
def confirm_email_template(self):
|
||||
"""Get confirm email template"""
|
||||
return render_to_string(
|
||||
template_name=settings.CONFIRM_EMAIL_TEMPLATE,
|
||||
context={'token': self.confirm_email_token,
|
||||
'uidb64': self.get_user_uidb64,
|
||||
'domain_uri': settings.DOMAIN_URI,
|
||||
'site_name': settings.SITE_NAME})
|
||||
def base_template(self):
|
||||
"""Base email template"""
|
||||
return {'domain_uri': settings.DOMAIN_URI,
|
||||
'uidb64': self.get_user_uidb64,
|
||||
'site_name': settings.SITE_NAME}
|
||||
|
||||
@property
|
||||
def change_email_template(self):
|
||||
"""Get change email template"""
|
||||
return render_to_string(
|
||||
template_name=settings.CHANGE_EMAIL_TEMPLATE,
|
||||
context={'token': self.change_email_token,
|
||||
'uidb64': self.get_user_uidb64,
|
||||
'domain_uri': settings.DOMAIN_URI,
|
||||
'site_name': settings.SITE_NAME})
|
||||
|
||||
|
||||
class ResetPasswordTokenQuerySet(models.QuerySet):
|
||||
"""Reset password token query set"""
|
||||
|
||||
def expired(self):
|
||||
"""Show only expired"""
|
||||
return self.filter(expiry_datetime__lt=timezone.now())
|
||||
|
||||
def valid(self):
|
||||
"""Show only valid"""
|
||||
return self.filter(expiry_datetime__gt=timezone.now())
|
||||
|
||||
def by_user(self, user):
|
||||
"""Show obj by user"""
|
||||
return self.filter(user=user)
|
||||
|
||||
|
||||
class ResetPasswordToken(PlatformMixin, ProjectBaseMixin):
|
||||
"""Reset password model"""
|
||||
|
||||
user = models.ForeignKey(User,
|
||||
related_name='password_reset_tokens',
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name=_('The User which is associated to '
|
||||
'this password reset token'))
|
||||
# Key field, though it is not the primary key of the model
|
||||
key = models.CharField(max_length=255,
|
||||
verbose_name=_('Key'))
|
||||
|
||||
ip_address = models.GenericIPAddressField(default='',
|
||||
blank=True, null=True,
|
||||
verbose_name=_('The IP address of this session'))
|
||||
|
||||
expiry_datetime = models.DateTimeField(blank=True, null=True,
|
||||
verbose_name=_('Expiration datetime'))
|
||||
|
||||
objects = ResetPasswordTokenQuerySet.as_manager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Password Reset Token")
|
||||
verbose_name_plural = _("Password Reset Tokens")
|
||||
|
||||
def __str__(self):
|
||||
return "Password reset token for user {user}".format(user=self.user)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""Override save method"""
|
||||
if not self.expiry_datetime:
|
||||
self.expiry_datetime = (
|
||||
timezone.now() +
|
||||
timezone.timedelta(hours=self.get_resetting_token_expiration)
|
||||
)
|
||||
if not self.key:
|
||||
self.key = self.generate_token
|
||||
return super(ResetPasswordToken, self).save(*args, **kwargs)
|
||||
def image_tag(self):
|
||||
return mark_safe(f'<img src="{self.image_url}" />')
|
||||
|
||||
@property
|
||||
def get_resetting_token_expiration(self):
|
||||
"""Get resetting token expiration"""
|
||||
return settings.RESETTING_TOKEN_EXPIRATION
|
||||
def cropped_image_tag(self):
|
||||
return mark_safe(f'<img src="{self.cropped_image_url}" />')
|
||||
|
||||
@property
|
||||
def is_valid(self):
|
||||
"""Check if valid token or not"""
|
||||
return timezone.now() > self.expiry_datetime
|
||||
|
||||
@property
|
||||
def generate_token(self):
|
||||
"""Generates a pseudo random code"""
|
||||
return password_token_generator.make_token(self.user)
|
||||
|
||||
@property
|
||||
def reset_password_template(self):
|
||||
def reset_password_template(self, country_code):
|
||||
"""Get reset password template"""
|
||||
context = {'token': self.reset_password_token,
|
||||
'country_code': country_code}
|
||||
context.update(self.base_template)
|
||||
return render_to_string(
|
||||
template_name=settings.RESETTING_TOKEN_TEMPLATE,
|
||||
context={'token': self.key,
|
||||
'uidb64': self.user.get_user_uidb64,
|
||||
'domain_uri': settings.DOMAIN_URI,
|
||||
'site_name': settings.SITE_NAME})
|
||||
context=context)
|
||||
|
||||
@staticmethod
|
||||
def token_is_valid(user, token):
|
||||
"""Check if token is valid"""
|
||||
return password_token_generator.check_token(user, token)
|
||||
def confirm_email_template(self, country_code):
|
||||
"""Get confirm email template"""
|
||||
context = {'token': self.confirm_email_token,
|
||||
'country_code': country_code}
|
||||
context.update(self.base_template)
|
||||
return render_to_string(
|
||||
template_name=settings.CONFIRM_EMAIL_TEMPLATE,
|
||||
context=context)
|
||||
|
||||
def overdue(self):
|
||||
"""Overdue instance"""
|
||||
self.expiry_datetime = timezone.now()
|
||||
self.save()
|
||||
def change_email_template(self, country_code):
|
||||
"""Get change email template"""
|
||||
context = {'token': self.change_email_token,
|
||||
'country_code': country_code}
|
||||
context.update(self.base_template)
|
||||
return render_to_string(
|
||||
template_name=settings.CHANGE_EMAIL_TEMPLATE,
|
||||
context=context)
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@ from django.contrib.auth import password_validation as password_validators
|
|||
from fcm_django.models import FCMDevice
|
||||
from rest_framework import exceptions
|
||||
from rest_framework import serializers
|
||||
from rest_framework import validators as rest_validators
|
||||
|
||||
from account import models, tasks
|
||||
from utils import exceptions as utils_exceptions
|
||||
from utils import methods as utils_methods
|
||||
|
||||
|
||||
# User serializers
|
||||
|
|
@ -17,12 +19,16 @@ class UserSerializer(serializers.ModelSerializer):
|
|||
fullname = serializers.CharField(source='get_full_name', read_only=True)
|
||||
|
||||
# REQUEST
|
||||
username = serializers.CharField(required=False)
|
||||
username = serializers.CharField(
|
||||
validators=(rest_validators.UniqueValidator(queryset=models.User.objects.all()),),
|
||||
required=False)
|
||||
first_name = serializers.CharField(required=False, write_only=True)
|
||||
last_name = serializers.CharField(required=False, write_only=True)
|
||||
image = serializers.ImageField(required=False)
|
||||
cropped_image = serializers.ImageField(required=False)
|
||||
email = serializers.EmailField(required=False)
|
||||
image_url = serializers.URLField(required=False)
|
||||
cropped_image_url = serializers.URLField(required=False)
|
||||
email = serializers.EmailField(
|
||||
validators=(rest_validators.UniqueValidator(queryset=models.User.objects.all()),),
|
||||
required=False)
|
||||
newsletter = serializers.BooleanField(required=False)
|
||||
|
||||
class Meta:
|
||||
|
|
@ -32,8 +38,8 @@ class UserSerializer(serializers.ModelSerializer):
|
|||
'first_name',
|
||||
'last_name',
|
||||
'fullname',
|
||||
'cropped_image',
|
||||
'image',
|
||||
'cropped_image_url',
|
||||
'image_url',
|
||||
'email',
|
||||
'email_confirmed',
|
||||
'newsletter',
|
||||
|
|
@ -42,33 +48,31 @@ class UserSerializer(serializers.ModelSerializer):
|
|||
def validate_email(self, value):
|
||||
"""Validate email value"""
|
||||
if value == self.instance.email:
|
||||
raise serializers.ValidationError()
|
||||
raise serializers.ValidationError(detail='Equal email address.')
|
||||
return value
|
||||
|
||||
def validate_username(self, value):
|
||||
"""Validate username"""
|
||||
if models.User.objects.by_username(username=value).exists():
|
||||
raise serializers.ValidationError()
|
||||
"""Custom username validation"""
|
||||
valid = utils_methods.username_validator(username=value)
|
||||
if not valid:
|
||||
raise utils_exceptions.NotValidUsernameError()
|
||||
return value
|
||||
|
||||
def validate(self, attrs):
|
||||
if ('cropped_image' in attrs or 'image' in attrs) and \
|
||||
('cropped_image' not in attrs or 'image' not in attrs):
|
||||
raise utils_exceptions.UserUpdateUploadImageError()
|
||||
return attrs
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
"""
|
||||
Override update method
|
||||
"""
|
||||
if 'email' in validated_data:
|
||||
validated_data['email_confirmed'] = False
|
||||
"""Override update method"""
|
||||
instance = super().update(instance, validated_data)
|
||||
# Send verification link on user email for change email address
|
||||
if settings.USE_CELERY:
|
||||
tasks.confirm_new_email_address.delay(instance.id)
|
||||
else:
|
||||
tasks.confirm_new_email_address(instance.id)
|
||||
if 'email' in validated_data:
|
||||
instance.email_confirmed = False
|
||||
instance.save()
|
||||
# Send verification link on user email for change email address
|
||||
if settings.USE_CELERY:
|
||||
tasks.change_email_address.delay(
|
||||
user_id=instance.id,
|
||||
country_code=self.context.get('request').country_code)
|
||||
else:
|
||||
tasks.change_email_address(
|
||||
user_id=instance.id,
|
||||
country_code=self.context.get('request').country_code)
|
||||
return instance
|
||||
|
||||
|
||||
|
|
@ -156,7 +160,7 @@ class ConfirmEmailSerializer(serializers.ModelSerializer):
|
|||
"""Override validate method"""
|
||||
email_confirmed = self.instance.email_confirmed
|
||||
if email_confirmed:
|
||||
raise serializers.ValidationError()
|
||||
raise utils_exceptions.EmailConfirmedError()
|
||||
return attrs
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
|
|
|
|||
|
|
@ -1,61 +1,49 @@
|
|||
"""Serializers for account web"""
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import password_validation as password_validators
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from account import models, tasks
|
||||
from account import models
|
||||
from utils import exceptions as utils_exceptions
|
||||
from utils.methods import username_validator
|
||||
|
||||
|
||||
class PasswordResetSerializer(serializers.ModelSerializer):
|
||||
class PasswordResetSerializer(serializers.Serializer):
|
||||
"""Serializer from model PasswordReset"""
|
||||
username_or_email = serializers.CharField(required=False,
|
||||
write_only=True,)
|
||||
|
||||
class Meta:
|
||||
"""Meta class"""
|
||||
model = models.ResetPasswordToken
|
||||
fields = (
|
||||
'username_or_email',
|
||||
)
|
||||
@property
|
||||
def request(self):
|
||||
"""Get request from context"""
|
||||
return self.context.get('request')
|
||||
|
||||
def validate(self, attrs):
|
||||
"""Override validate method"""
|
||||
user = self.context.get('request').user
|
||||
user = self.request.user
|
||||
username_or_email = attrs.get('username_or_email')
|
||||
|
||||
if user.is_anonymous:
|
||||
username_or_email = attrs.get('username_or_email')
|
||||
if not user.is_authenticated:
|
||||
if not username_or_email:
|
||||
raise serializers.ValidationError(_('Username or Email not requested'))
|
||||
# Check user in DB
|
||||
user_qs = models.User.objects.filter(Q(email=username_or_email) |
|
||||
Q(username=username_or_email))
|
||||
if user_qs.exists():
|
||||
attrs['user'] = user_qs.first()
|
||||
raise serializers.ValidationError(_('username or email not in request body.'))
|
||||
|
||||
filters = {}
|
||||
if username_validator(username_or_email):
|
||||
filters.update({'username__icontains': username_or_email})
|
||||
else:
|
||||
raise utils_exceptions.UserNotFoundError()
|
||||
else:
|
||||
attrs['user'] = user
|
||||
filters.update({'email__icontains': username_or_email})
|
||||
|
||||
if filters:
|
||||
filters.update({'is_active': True})
|
||||
user_qs = models.User.objects.filter(**filters)
|
||||
|
||||
if not user_qs.exists():
|
||||
raise utils_exceptions.UserNotFoundError()
|
||||
user = user_qs.first()
|
||||
|
||||
attrs['user'] = user
|
||||
return attrs
|
||||
|
||||
def create(self, validated_data, *args, **kwargs):
|
||||
"""Override create method"""
|
||||
user = validated_data.pop('user')
|
||||
ip_address = self.context.get('request').META.get('REMOTE_ADDR')
|
||||
|
||||
obj = models.ResetPasswordToken.objects.create(
|
||||
user=user,
|
||||
ip_address=ip_address,
|
||||
source=models.ResetPasswordToken.WEB
|
||||
)
|
||||
if settings.USE_CELERY:
|
||||
tasks.send_reset_password_email.delay(obj.id)
|
||||
else:
|
||||
tasks.send_reset_password_email(obj.id)
|
||||
return obj
|
||||
|
||||
|
||||
class PasswordResetConfirmSerializer(serializers.ModelSerializer):
|
||||
"""Serializer for model User"""
|
||||
|
|
@ -64,30 +52,24 @@ class PasswordResetConfirmSerializer(serializers.ModelSerializer):
|
|||
|
||||
class Meta:
|
||||
"""Meta class"""
|
||||
model = models.ResetPasswordToken
|
||||
model = models.User
|
||||
fields = ('password', )
|
||||
|
||||
def validate(self, attrs):
|
||||
"""Override validate method"""
|
||||
user = self.instance.user
|
||||
password = attrs.get('password')
|
||||
def validate_password(self, value):
|
||||
"""Password validation method."""
|
||||
try:
|
||||
# Compare new password with the old ones
|
||||
if user.check_password(raw_password=password):
|
||||
if self.instance.check_password(raw_password=value):
|
||||
raise utils_exceptions.PasswordsAreEqual()
|
||||
# Validate password
|
||||
password_validators.validate_password(password=password)
|
||||
password_validators.validate_password(password=value)
|
||||
except serializers.ValidationError as e:
|
||||
raise serializers.ValidationError(str(e))
|
||||
else:
|
||||
return attrs
|
||||
return value
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
"""Override update method"""
|
||||
# Update user password from instance
|
||||
instance.user.set_password(validated_data.get('password'))
|
||||
instance.user.save()
|
||||
|
||||
# Overdue instance
|
||||
instance.overdue()
|
||||
instance.set_password(validated_data.get('password'))
|
||||
instance.save()
|
||||
return instance
|
||||
|
|
|
|||
|
|
@ -11,26 +11,37 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
@shared_task
|
||||
def send_reset_password_email(request_id):
|
||||
def send_reset_password_email(user_id, country_code):
|
||||
"""Send email to user for reset password."""
|
||||
try:
|
||||
obj = models.ResetPasswordToken.objects.get(id=request_id)
|
||||
user = obj.user
|
||||
user = models.User.objects.get(id=user_id)
|
||||
user.send_email(subject=_('Password resetting'),
|
||||
message=obj.reset_password_template)
|
||||
message=user.reset_password_template(country_code))
|
||||
except:
|
||||
logger.error(f'METHOD_NAME: {send_reset_password_email.__name__}\n'
|
||||
f'DETAIL: Exception occurred for ResetPasswordToken instance: '
|
||||
f'{request_id}')
|
||||
f'DETAIL: Exception occurred for reset password: '
|
||||
f'{user_id}')
|
||||
|
||||
|
||||
@shared_task
|
||||
def confirm_new_email_address(user_id):
|
||||
def confirm_new_email_address(user_id, country_code):
|
||||
"""Send email to user new email."""
|
||||
try:
|
||||
user = models.User.objects.get(id=user_id)
|
||||
user.send_email(subject=_('Validate new email address'),
|
||||
message=user.change_email_template)
|
||||
message=user.confirm_email_template(country_code))
|
||||
except:
|
||||
logger.error(f'METHOD_NAME: {confirm_new_email_address.__name__}\n'
|
||||
f'DETAIL: Exception occurred for user: {user_id}')
|
||||
|
||||
|
||||
@shared_task
|
||||
def change_email_address(user_id, country_code):
|
||||
"""Send email to user new email."""
|
||||
try:
|
||||
user = models.User.objects.get(id=user_id)
|
||||
user.send_email(subject=_('Validate new email address'),
|
||||
message=user.change_email_template(country_code))
|
||||
except:
|
||||
logger.error(f'METHOD_NAME: {change_email_address.__name__}\n'
|
||||
f'DETAIL: Exception occurred for user: {user_id}')
|
||||
|
|
|
|||
|
|
@ -8,7 +8,5 @@ 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-email/confirm/<uidb64>/<token>/', views.ChangeEmailConfirmView.as_view(),
|
||||
name='change-email-confirm'),
|
||||
path('confirm-email/', views.ConfirmEmailView.as_view(), name='confirm-email'),
|
||||
path('email/confirm/<uidb64>/<token>/', views.ConfirmEmailView.as_view(), name='confirm-email'),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -8,10 +8,8 @@ app_name = 'account'
|
|||
|
||||
urlpatterns_api = [
|
||||
path('reset-password/', views.PasswordResetView.as_view(), name='password-reset'),
|
||||
path('form/reset-password/<uidb64>/<token>/', views.FormPasswordResetConfirmView.as_view(),
|
||||
name='form-password-reset-confirm'),
|
||||
path('form/reset-password/success/', views.FormPasswordResetSuccessView.as_view(),
|
||||
name='form-password-reset-success'),
|
||||
path('reset-password/confirm/<uidb64>/<token>/', views.PasswordResetConfirmView.as_view(),
|
||||
name='password-reset-confirm'),
|
||||
]
|
||||
|
||||
urlpatterns = urlpatterns_api + \
|
||||
|
|
|
|||
|
|
@ -11,8 +11,7 @@ from account import models
|
|||
from account.serializers import common as serializers
|
||||
from utils import exceptions as utils_exceptions
|
||||
from utils.models import GMTokenGenerator
|
||||
from utils.views import (JWTUpdateAPIView,
|
||||
JWTGenericViewMixin)
|
||||
from utils.views import JWTGenericViewMixin
|
||||
|
||||
|
||||
# User views
|
||||
|
|
@ -25,7 +24,7 @@ class UserRetrieveUpdateView(generics.RetrieveUpdateAPIView):
|
|||
return self.request.user
|
||||
|
||||
|
||||
class ChangePasswordView(JWTUpdateAPIView):
|
||||
class ChangePasswordView(generics.GenericAPIView):
|
||||
"""Change password view"""
|
||||
serializer_class = serializers.ChangePasswordSerializer
|
||||
queryset = models.User.objects.active()
|
||||
|
|
@ -39,13 +38,13 @@ class ChangePasswordView(JWTUpdateAPIView):
|
|||
return Response(status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class ConfirmEmailView(JWTGenericViewMixin):
|
||||
class SendConfirmationEmailView(JWTGenericViewMixin):
|
||||
"""Confirm email view."""
|
||||
serializer_class = serializers.ConfirmEmailSerializer
|
||||
queryset = models.User.objects.all()
|
||||
|
||||
def patch(self, request, *args, **kwargs):
|
||||
"""Implement POST-method"""
|
||||
"""Implement PATCH-method"""
|
||||
# Get user instance
|
||||
instance = self.request.user
|
||||
|
||||
|
|
@ -55,7 +54,7 @@ class ConfirmEmailView(JWTGenericViewMixin):
|
|||
return Response(status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class ChangeEmailConfirmView(JWTGenericViewMixin):
|
||||
class ConfirmEmailView(JWTGenericViewMixin):
|
||||
"""View for confirm changing email"""
|
||||
|
||||
permission_classes = (permissions.AllowAny,)
|
||||
|
|
@ -68,12 +67,18 @@ class ChangeEmailConfirmView(JWTGenericViewMixin):
|
|||
user_qs = models.User.objects.filter(pk=uid)
|
||||
if user_qs.exists():
|
||||
user = user_qs.first()
|
||||
if not GMTokenGenerator(GMTokenGenerator.CHANGE_EMAIL).check_token(
|
||||
if not GMTokenGenerator(GMTokenGenerator.CONFIRM_EMAIL).check_token(
|
||||
user, token):
|
||||
raise utils_exceptions.NotValidTokenError()
|
||||
# Approve email status
|
||||
user.confirm_email()
|
||||
return Response(status=status.HTTP_200_OK)
|
||||
# Create tokens
|
||||
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()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,46 +1,43 @@
|
|||
"""Web account views"""
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.tokens import default_token_generator
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.contrib.auth.tokens import default_token_generator as password_token_generator
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.http import urlsafe_base64_decode
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.decorators.cache import never_cache
|
||||
from django.views.decorators.debug import sensitive_post_parameters
|
||||
from django.views.generic.edit import FormView
|
||||
from rest_framework import permissions
|
||||
from rest_framework import status
|
||||
from rest_framework import views
|
||||
from rest_framework import permissions, status, generics
|
||||
from rest_framework.response import Response
|
||||
|
||||
from account import models
|
||||
from account.forms import SetPasswordForm
|
||||
from account import tasks, models
|
||||
from account.serializers import web as serializers
|
||||
from utils import exceptions as utils_exceptions
|
||||
from utils.models import GMTokenGenerator
|
||||
from utils.views import (JWTCreateAPIView,
|
||||
JWTGenericViewMixin)
|
||||
from utils.views import JWTGenericViewMixin
|
||||
|
||||
|
||||
class PasswordResetView(JWTCreateAPIView):
|
||||
class PasswordResetView(generics.GenericAPIView):
|
||||
"""View for resetting user password"""
|
||||
permission_classes = (permissions.AllowAny, )
|
||||
serializer_class = serializers.PasswordResetSerializer
|
||||
queryset = models.ResetPasswordToken.objects.valid()
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
"""Override create method"""
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
if serializer.validated_data.get('user'):
|
||||
user = serializer.validated_data.pop('user')
|
||||
if settings.USE_CELERY:
|
||||
tasks.send_reset_password_email.delay(user_id=user.id,
|
||||
country_code=self.request.country_code)
|
||||
else:
|
||||
tasks.send_reset_password_email(user_id=user.id,
|
||||
country_code=self.request.country_code)
|
||||
return Response(status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class PasswordResetConfirmView(JWTGenericViewMixin):
|
||||
"""View for confirmation new password"""
|
||||
serializer_class = serializers.PasswordResetConfirmSerializer
|
||||
permission_classes = (permissions.AllowAny,)
|
||||
|
||||
def get_queryset(self):
|
||||
"""Override get_queryset method"""
|
||||
return models.ResetPasswordToken.objects.valid()
|
||||
queryset = models.User.objects.active()
|
||||
|
||||
def get_object(self):
|
||||
"""Override get_object method
|
||||
|
|
@ -51,128 +48,27 @@ class PasswordResetConfirmView(JWTGenericViewMixin):
|
|||
user_id = force_text(urlsafe_base64_decode(uidb64))
|
||||
token = self.kwargs.get('token')
|
||||
|
||||
filter_kwargs = {'key': token, 'user_id': user_id}
|
||||
obj = get_object_or_404(queryset, **filter_kwargs)
|
||||
obj = get_object_or_404(queryset, id=user_id)
|
||||
|
||||
if not GMTokenGenerator(GMTokenGenerator.RESET_PASSWORD).check_token(
|
||||
user=obj.user, token=token):
|
||||
raise utils_exceptions.NotValidAccessTokenError()
|
||||
if not password_token_generator.check_token(user=obj, token=token):
|
||||
raise utils_exceptions.NotValidTokenError()
|
||||
|
||||
# May raise a permission denied
|
||||
self.check_object_permissions(self.request, obj)
|
||||
|
||||
return obj
|
||||
|
||||
def put(self, request, *args, **kwargs):
|
||||
"""Implement PUT method"""
|
||||
def patch(self, request, *args, **kwargs):
|
||||
"""Implement PATCH method"""
|
||||
instance = self.get_object()
|
||||
serializer = self.get_serializer(instance=instance,
|
||||
data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
return Response(status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
# Form view
|
||||
class PasswordContextMixin:
|
||||
extra_context = None
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({
|
||||
'title': self.title,
|
||||
**(self.extra_context or {})
|
||||
})
|
||||
return context
|
||||
|
||||
|
||||
class FormPasswordResetSuccessView(views.APIView):
|
||||
"""View for successful reset password"""
|
||||
|
||||
permission_classes = (permissions.AllowAny, )
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""Implement GET-method"""
|
||||
return Response(status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class FormPasswordResetConfirmView(PasswordContextMixin, FormView):
|
||||
|
||||
INTERNAL_RESET_URL_TOKEN = 'set-password'
|
||||
INTERNAL_RESET_SESSION_TOKEN = '_password_reset_token'
|
||||
|
||||
form_class = SetPasswordForm
|
||||
post_reset_login = False
|
||||
post_reset_login_backend = None
|
||||
success_url = reverse_lazy('web:account:form-password-reset-success')
|
||||
template_name = settings.CONFIRMATION_PASSWORD_RESET_TEMPLATE
|
||||
title = _('Enter new password')
|
||||
token_generator = default_token_generator
|
||||
|
||||
@method_decorator(sensitive_post_parameters())
|
||||
@method_decorator(never_cache)
|
||||
def dispatch(self, *args, **kwargs):
|
||||
assert 'uidb64' in kwargs and 'token' in kwargs
|
||||
|
||||
self.validlink = False
|
||||
self.user = self.get_user(kwargs['uidb64'])
|
||||
|
||||
if self.user is not None:
|
||||
token = kwargs['token']
|
||||
if token == self.INTERNAL_RESET_URL_TOKEN:
|
||||
session_token = self.request.session.get(self.INTERNAL_RESET_SESSION_TOKEN)
|
||||
if self.token_generator.check_token(self.user, session_token):
|
||||
# If the token is valid, display the password reset form.
|
||||
self.validlink = True
|
||||
return super().dispatch(*args, **kwargs)
|
||||
else:
|
||||
if self.token_generator.check_token(self.user, token):
|
||||
# Store the token in the session and redirect to the
|
||||
# password reset form at a URL without the token. That
|
||||
# avoids the possibility of leaking the token in the
|
||||
# HTTP Referer header.
|
||||
self.request.session[self.INTERNAL_RESET_SESSION_TOKEN] = token
|
||||
redirect_url = self.request.path.replace(token, self.INTERNAL_RESET_URL_TOKEN)
|
||||
return HttpResponseRedirect(redirect_url)
|
||||
|
||||
# Display the "Password reset unsuccessful" page.
|
||||
return self.render_to_response(self.get_context_data())
|
||||
|
||||
def get_user(self, uidb64):
|
||||
try:
|
||||
# urlsafe_base64_decode() decodes to bytestring
|
||||
uid = urlsafe_base64_decode(uidb64).decode()
|
||||
user = models.User.objects.get(pk=uid)
|
||||
except (TypeError, ValueError, OverflowError, models.User.DoesNotExist, ValidationError):
|
||||
user = None
|
||||
return user
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs['user'] = self.user
|
||||
return kwargs
|
||||
|
||||
def form_valid(self, form):
|
||||
# Saving form
|
||||
form.save()
|
||||
user = form.user
|
||||
|
||||
# Expire user tokens
|
||||
user.expire_access_tokens()
|
||||
user.expire_refresh_tokens()
|
||||
|
||||
# Pop session token
|
||||
del self.request.session[self.INTERNAL_RESET_SESSION_TOKEN]
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
if self.validlink:
|
||||
context['validlink'] = True
|
||||
else:
|
||||
context.update({
|
||||
'form': None,
|
||||
'title': _('Password reset unsuccessful'),
|
||||
'validlink': False,
|
||||
})
|
||||
return context
|
||||
# Create tokens
|
||||
tokens = instance.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))
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@ class Application(PlatformMixin, AbstractApplication):
|
|||
|
||||
class JWTAccessTokenManager(models.Manager):
|
||||
"""Manager for AccessToken model."""
|
||||
def add_to_db(self, user, access_token: AccessToken,
|
||||
refresh_token: RefreshToken):
|
||||
|
||||
def make(self, user, access_token: AccessToken, refresh_token: RefreshToken):
|
||||
"""Create generated tokens to DB"""
|
||||
refresh_token_qs = JWTRefreshToken.objects.filter(user=user,
|
||||
jti=refresh_token.payload.get('jti'))
|
||||
|
|
@ -106,18 +106,17 @@ class JWTAccessToken(ProjectBaseMixin):
|
|||
|
||||
class JWTRefreshTokenManager(models.Manager):
|
||||
"""Manager for model RefreshToken."""
|
||||
|
||||
def add_to_db(self, user, token: RefreshToken, source: int):
|
||||
"""Added generated refresh token to db"""
|
||||
def make(self, user, token: RefreshToken, source: int):
|
||||
"""Make method"""
|
||||
jti = token[settings.SIMPLE_JWT.get('JTI_CLAIM')]
|
||||
exp = token['exp']
|
||||
obj = self.model(
|
||||
user=user,
|
||||
jti=jti,
|
||||
source=source,
|
||||
created_at=token.current_time,
|
||||
expires_at=utils.datetime_from_epoch(exp),
|
||||
)
|
||||
expires_at=utils.datetime_from_epoch(exp))
|
||||
if source:
|
||||
obj.source = source
|
||||
obj.save()
|
||||
return obj
|
||||
|
||||
|
|
|
|||
|
|
@ -19,14 +19,9 @@ from utils.tokens import GMRefreshToken
|
|||
class SignupSerializer(serializers.ModelSerializer):
|
||||
"""Signup serializer serializer mixin"""
|
||||
# REQUEST
|
||||
username = serializers.CharField(
|
||||
validators=(rest_validators.UniqueValidator(queryset=account_models.User.objects.all()),),
|
||||
write_only=True
|
||||
)
|
||||
username = serializers.CharField(write_only=True)
|
||||
password = serializers.CharField(write_only=True)
|
||||
email = serializers.EmailField(
|
||||
validators=(rest_validators.UniqueValidator(queryset=account_models.User.objects.all()),),
|
||||
write_only=True)
|
||||
email = serializers.EmailField(write_only=True)
|
||||
newsletter = serializers.BooleanField(write_only=True)
|
||||
|
||||
class Meta:
|
||||
|
|
@ -38,34 +33,46 @@ class SignupSerializer(serializers.ModelSerializer):
|
|||
'newsletter'
|
||||
)
|
||||
|
||||
def validate_username(self, data):
|
||||
def validate_username(self, value):
|
||||
"""Custom username validation"""
|
||||
valid = utils_methods.username_validator(username=data)
|
||||
valid = utils_methods.username_validator(username=value)
|
||||
if not valid:
|
||||
raise utils_exceptions.NotValidUsernameError()
|
||||
return data
|
||||
if account_models.User.objects.filter(username__icontains=value).exists():
|
||||
raise serializers.ValidationError()
|
||||
return value
|
||||
|
||||
def validate_password(self, data):
|
||||
def validate_email(self, value):
|
||||
"""Validate email"""
|
||||
if account_models.User.objects.filter(email__icontains=value).exists():
|
||||
raise serializers.ValidationError()
|
||||
return value
|
||||
|
||||
def validate_password(self, value):
|
||||
"""Custom password validation"""
|
||||
try:
|
||||
password_validators.validate_password(password=data)
|
||||
password_validators.validate_password(password=value)
|
||||
except serializers.ValidationError as e:
|
||||
raise serializers.ValidationError(str(e))
|
||||
else:
|
||||
return data
|
||||
return value
|
||||
|
||||
def create(self, validated_data):
|
||||
"""Override create method"""
|
||||
obj = account_models.User.objects.make(
|
||||
username=validated_data.get('username'),
|
||||
password=validated_data.get('password'),
|
||||
email=validated_data.get('email'),
|
||||
email=validated_data.get('email').lower(),
|
||||
newsletter=validated_data.get('newsletter'))
|
||||
# Send verification link on user email
|
||||
if settings.USE_CELERY:
|
||||
tasks.send_confirm_email.delay(obj.id)
|
||||
tasks.send_confirm_email.delay(
|
||||
user_id=obj.id,
|
||||
country_code=self.context.get('request').country_code)
|
||||
else:
|
||||
tasks.send_confirm_email(obj.id)
|
||||
tasks.send_confirm_email(
|
||||
user_id=obj.id,
|
||||
country_code=self.context.get('request').country_code)
|
||||
return obj
|
||||
|
||||
|
||||
|
|
@ -108,7 +115,7 @@ class LoginByUsernameOrEmailSerializer(SourceSerializerMixin,
|
|||
authentication = authenticate(username=user.get_username(),
|
||||
password=password)
|
||||
if not authentication:
|
||||
raise utils_exceptions.WrongAuthCredentials()
|
||||
raise utils_exceptions.UserNotFoundError()
|
||||
self.instance = user
|
||||
return attrs
|
||||
|
||||
|
|
@ -129,14 +136,10 @@ class RefreshTokenSerializer(SourceSerializerMixin):
|
|||
refresh_token = serializers.CharField(read_only=True)
|
||||
access_token = serializers.CharField(read_only=True)
|
||||
|
||||
def get_request(self):
|
||||
"""Return request"""
|
||||
return self.context.get('request')
|
||||
|
||||
def validate(self, attrs):
|
||||
"""Override validate method"""
|
||||
|
||||
cookie_refresh_token = self.get_request().COOKIES.get('refresh_token')
|
||||
cookie_refresh_token = self.context.get('request').COOKIES.get('refresh_token')
|
||||
# Check if refresh_token in COOKIES
|
||||
if not cookie_refresh_token:
|
||||
raise utils_exceptions.NotValidRefreshTokenError()
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
@shared_task
|
||||
def send_confirm_email(user_id):
|
||||
def send_confirm_email(user_id, country_code):
|
||||
"""Send verification email to user."""
|
||||
try:
|
||||
obj = account_models.User.objects.get(id=user_id)
|
||||
obj.send_email(subject=_('Email confirmation'),
|
||||
message=obj.confirm_email_template)
|
||||
message=obj.confirm_email_template(country_code))
|
||||
except:
|
||||
logger.error(f'METHOD_NAME: {send_confirm_email.__name__}\n'
|
||||
f'DETAIL: Exception occurred for user: {user_id}')
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ urlpatterns_oauth2 = [
|
|||
|
||||
urlpatterns_jwt = [
|
||||
path('signup/', views.SignUpView.as_view(), name='signup'),
|
||||
path('signup/confirm/<uidb64>/<token>/', views.VerifyEmailConfirmView.as_view(),
|
||||
path('signup/confirm/<uidb64>/<token>/', views.ConfirmationEmailView.as_view(),
|
||||
name='signup-confirm'),
|
||||
path('login/', views.LoginByUsernameOrEmailView.as_view(), name='login'),
|
||||
path('logout/', views.LogoutView.as_view(), name="logout"),
|
||||
|
|
|
|||
|
|
@ -24,13 +24,12 @@ from authorization.serializers import common as serializers
|
|||
from utils import exceptions as utils_exceptions
|
||||
from utils.models import GMTokenGenerator
|
||||
from utils.permissions import IsAuthenticatedAndTokenIsValid
|
||||
from utils.views import (JWTGenericViewMixin,
|
||||
JWTCreateAPIView)
|
||||
from utils.views import JWTGenericViewMixin
|
||||
|
||||
|
||||
# Mixins
|
||||
# JWTAuthView mixin
|
||||
class JWTAuthViewMixin(JWTCreateAPIView):
|
||||
class JWTAuthViewMixin(JWTGenericViewMixin):
|
||||
"""Mixin for authentication views"""
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
|
|
@ -151,7 +150,7 @@ class OAuth2SignUpView(OAuth2ViewMixin, JWTGenericViewMixin):
|
|||
|
||||
# JWT
|
||||
# Sign in via username and password
|
||||
class SignUpView(JWTCreateAPIView):
|
||||
class SignUpView(generics.GenericAPIView):
|
||||
"""View for classic signup"""
|
||||
permission_classes = (permissions.AllowAny, )
|
||||
serializer_class = serializers.SignupSerializer
|
||||
|
|
@ -164,7 +163,7 @@ class SignUpView(JWTCreateAPIView):
|
|||
return Response(status=status.HTTP_201_CREATED)
|
||||
|
||||
|
||||
class VerifyEmailConfirmView(JWTGenericViewMixin):
|
||||
class ConfirmationEmailView(JWTGenericViewMixin):
|
||||
"""View for confirmation email"""
|
||||
|
||||
permission_classes = (permissions.AllowAny, )
|
||||
|
|
@ -182,7 +181,15 @@ class VerifyEmailConfirmView(JWTGenericViewMixin):
|
|||
raise utils_exceptions.NotValidTokenError()
|
||||
# Approve email status
|
||||
user.confirm_email()
|
||||
return Response(status=status.HTTP_200_OK)
|
||||
response = Response(status=status.HTTP_200_OK)
|
||||
|
||||
# Create tokens
|
||||
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)
|
||||
else:
|
||||
raise utils_exceptions.UserNotFoundError()
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ class CommentSerializer(serializers.ModelSerializer):
|
|||
nickname = serializers.CharField(read_only=True,
|
||||
source='user.username')
|
||||
is_mine = serializers.BooleanField(read_only=True)
|
||||
profile_pic = serializers.SerializerMethodField()
|
||||
profile_pic = serializers.URLField(read_only=True,
|
||||
source='user.cropped_image_url')
|
||||
|
||||
class Meta:
|
||||
"""Serializer for model Comment"""
|
||||
|
|
@ -24,7 +25,3 @@ class CommentSerializer(serializers.ModelSerializer):
|
|||
'nickname',
|
||||
'profile_pic'
|
||||
]
|
||||
|
||||
def get_profile_pic(self, obj):
|
||||
"""Get profile picture URL"""
|
||||
return obj.user.get_full_image_url(request=self.context.get('request'))
|
||||
|
|
|
|||
|
|
@ -102,6 +102,20 @@ class EstablishmentQuerySet(models.QuerySet):
|
|||
default=False,
|
||||
output_field=models.BooleanField(default=False)))
|
||||
|
||||
def by_distance_from_point(self, center, radius, unit='m'):
|
||||
"""
|
||||
Returns nearest establishments
|
||||
|
||||
:param center: point from which to find nearby establishments
|
||||
:param radius: the maximum distance within the radius of which to look for establishments
|
||||
:return: all establishments within the specified radius of specified point
|
||||
:param unit: length unit e.g. m, km. Default is 'm'.
|
||||
"""
|
||||
|
||||
from django.contrib.gis.measure import Distance
|
||||
kwargs = {unit: radius}
|
||||
return self.filter(address__coordinates__distance_lte=(center, Distance(**kwargs)))
|
||||
|
||||
|
||||
class Establishment(ProjectBaseMixin, ImageMixin, TranslatedFieldsMixin):
|
||||
"""Establishment model."""
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ from comment.serializers import common as comment_serializers
|
|||
from establishment import models
|
||||
from favorites.models import Favorites
|
||||
from location.serializers import AddressSerializer
|
||||
from main.models import MetaDataContent
|
||||
from main.serializers import MetaDataContentSerializer, AwardSerializer, CurrencySerializer
|
||||
from review import models as review_models
|
||||
from timetable.models import Timetable
|
||||
|
|
@ -148,7 +149,7 @@ class EstablishmentBaseSerializer(serializers.ModelSerializer):
|
|||
subtypes = EstablishmentSubTypeSerializer(many=True)
|
||||
address = AddressSerializer()
|
||||
tags = MetaDataContentSerializer(many=True)
|
||||
preview_image = serializers.SerializerMethodField()
|
||||
preview_image = serializers.ImageField(source='image')
|
||||
|
||||
class Meta:
|
||||
"""Meta class."""
|
||||
|
|
@ -338,3 +339,16 @@ class EstablishmentFavoritesCreateSerializer(serializers.ModelSerializer):
|
|||
'content_object': validated_data.pop('establishment')
|
||||
})
|
||||
return super().create(validated_data)
|
||||
|
||||
|
||||
class EstablishmentTagListSerializer(serializers.ModelSerializer):
|
||||
"""List establishment tag serializer."""
|
||||
label_translated = serializers.CharField(
|
||||
source='metadata.label_translated', read_only=True, allow_null=True)
|
||||
|
||||
class Meta:
|
||||
"""Meta class."""
|
||||
model = MetaDataContent
|
||||
fields = [
|
||||
'label_translated',
|
||||
]
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ from establishment import views
|
|||
|
||||
app_name = 'establishment'
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.EstablishmentListView.as_view(), name='list'),
|
||||
path('tags/', views.EstablishmentTagListView.as_view(), name='tags'),
|
||||
path('<int:pk>/', views.EstablishmentRetrieveView.as_view(), name='detail'),
|
||||
path('<int:pk>/comments/', views.EstablishmentCommentListView.as_view(), name='list-comments'),
|
||||
path('<int:pk>/comments/create/', views.EstablishmentCommentCreateView.as_view(),
|
||||
|
|
@ -15,5 +15,5 @@ urlpatterns = [
|
|||
path('<int:pk>/comments/<int:comment_id>/', views.EstablishmentCommentRUDView.as_view(),
|
||||
name='rud-comment'),
|
||||
path('<int:pk>/favorites/', views.EstablishmentFavoritesCreateDestroyView.as_view(),
|
||||
name='add-favorites'),
|
||||
]
|
||||
name='add-favorites')
|
||||
]
|
||||
|
|
|
|||
11
apps/establishment/urls/mobile.py
Normal file
11
apps/establishment/urls/mobile.py
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
"""Establishment url patterns."""
|
||||
from django.urls import path
|
||||
|
||||
from establishment import views
|
||||
from establishment.urls.common import urlpatterns as common_urlpatterns
|
||||
|
||||
urlpatterns = [
|
||||
path('geo/', views.EstablishmentNearestRetrieveView.as_view(), name='nearest-establishments-list')
|
||||
]
|
||||
|
||||
urlpatterns.extend(common_urlpatterns)
|
||||
|
|
@ -6,6 +6,7 @@ from rest_framework import generics, permissions
|
|||
from comment import models as comment_models
|
||||
from establishment import filters
|
||||
from establishment import models, serializers
|
||||
from main.models import MetaDataContent
|
||||
from utils.views import JWTGenericViewMixin
|
||||
from establishment.views import EstablishmentMixin
|
||||
|
||||
|
|
@ -108,3 +109,34 @@ class EstablishmentFavoritesCreateDestroyView(generics.CreateAPIView, generics.D
|
|||
# May raise a permission denied
|
||||
self.check_object_permissions(self.request, obj)
|
||||
return obj
|
||||
|
||||
|
||||
class EstablishmentNearestRetrieveView(EstablishmentMixin, JWTGenericViewMixin, generics.ListAPIView):
|
||||
"""Resource for getting list of nearest establishments."""
|
||||
serializer_class = serializers.EstablishmentListSerializer
|
||||
filter_class = filters.EstablishmentFilter
|
||||
|
||||
def get_queryset(self):
|
||||
"""Overrided method 'get_queryset'."""
|
||||
from django.contrib.gis.geos import Point
|
||||
|
||||
center = Point(float(self.request.query_params["lat"]), float(self.request.query_params["lon"]))
|
||||
radius = float(self.request.query_params["radius"])
|
||||
unit = self.request.query_params.get("unit", None)
|
||||
by_distance_from_point_kwargs = {"center": center, "radius": radius, "unit": unit}
|
||||
return super(EstablishmentNearestRetrieveView, self).get_queryset() \
|
||||
.by_distance_from_point(**{k: v for k, v in by_distance_from_point_kwargs.items() if v is not None}) \
|
||||
.by_country_code(code=self.request.country_code) \
|
||||
.annotate_in_favorites(user=self.request.user)
|
||||
|
||||
|
||||
class EstablishmentTagListView(generics.ListAPIView):
|
||||
"""List view for establishment tags."""
|
||||
serializer_class = serializers.EstablishmentTagListSerializer
|
||||
permission_classes = (permissions.AllowAny,)
|
||||
pagination_class = None
|
||||
|
||||
def get_queryset(self):
|
||||
"""Override get_queryset method"""
|
||||
return MetaDataContent.objects.by_content_type(app_label='establishment',
|
||||
model='establishment')
|
||||
|
|
|
|||
|
|
@ -8,5 +8,5 @@ app_name = 'favorites'
|
|||
urlpatterns = [
|
||||
path('establishments/', views.FavoritesEstablishmentListView.as_view(),
|
||||
name='establishment-list'),
|
||||
path('<int:pk>/', views.FavoritesDestroyView.as_view(), name='remove-from-favorites'),
|
||||
path('remove/<int:pk>/', views.FavoritesDestroyView.as_view(), name='remove-from-favorites'),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from . import models, serializers
|
|||
|
||||
class ImageUploadView(generics.CreateAPIView):
|
||||
"""Upload image to gallery"""
|
||||
permission_classes = (IsAuthenticatedAndTokenIsValid, )
|
||||
model = models.Image
|
||||
queryset = models.Image.objects.all()
|
||||
serializer_class = serializers.ImageSerializer
|
||||
permission_classes = (IsAuthenticatedAndTokenIsValid, )
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
"""News app common app."""
|
||||
from rest_framework import generics, permissions
|
||||
|
||||
from news import filters, models
|
||||
from news.serializers import common as serializers
|
||||
from utils.views import JWTGenericViewMixin, JWTListAPIView
|
||||
from utils.views import JWTGenericViewMixin
|
||||
|
||||
|
||||
class NewsMixin:
|
||||
|
|
@ -18,7 +19,7 @@ class NewsMixin:
|
|||
.order_by('-is_highlighted', '-created')
|
||||
|
||||
|
||||
class NewsListView(NewsMixin, JWTListAPIView):
|
||||
class NewsListView(NewsMixin, generics.ListAPIView):
|
||||
"""News list view."""
|
||||
|
||||
filter_class = filters.NewsListFilterSet
|
||||
|
|
|
|||
|
|
@ -32,15 +32,6 @@ class UserNotFoundError(AuthErrorMixin, ProjectBaseException):
|
|||
default_detail = _('User not found')
|
||||
|
||||
|
||||
class PasswordRequestResetExists(ProjectBaseException):
|
||||
"""
|
||||
The exception should be thrown when request for reset password
|
||||
is already exists and valid
|
||||
"""
|
||||
status_code = status.HTTP_400_BAD_REQUEST
|
||||
default_detail = _('Password request is already exists. Please wait.')
|
||||
|
||||
|
||||
class EmailSendingError(exceptions.APIException):
|
||||
"""The exception should be thrown when unable to send an email"""
|
||||
status_code = status.HTTP_400_BAD_REQUEST
|
||||
|
|
@ -137,8 +128,17 @@ class WrongAuthCredentials(AuthErrorMixin):
|
|||
|
||||
class FavoritesError(exceptions.APIException):
|
||||
"""
|
||||
The exception should be thrown when you item that user
|
||||
want add to favorites already exists.
|
||||
The exception should be thrown when item that user
|
||||
want to add to favorites is already exists.
|
||||
"""
|
||||
status_code = status.HTTP_400_BAD_REQUEST
|
||||
default_detail = _('Item is already in favorites.')
|
||||
|
||||
|
||||
class PasswordResetRequestExistedError(exceptions.APIException):
|
||||
"""
|
||||
The exception should be thrown when password reset request
|
||||
already exists and valid.
|
||||
"""
|
||||
status_code = status.HTTP_400_BAD_REQUEST
|
||||
default_detail = _('Password reset request is already exists and valid.')
|
||||
|
|
|
|||
|
|
@ -32,3 +32,13 @@ def parse_cookies(get_response):
|
|||
return response
|
||||
return middleware
|
||||
|
||||
|
||||
class CORSMiddleware:
|
||||
"""Added parameter {Access-Control-Allow-Origin: *} to response"""
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request):
|
||||
response = self.get_response(request)
|
||||
response["Access-Control-Allow-Origin"] = '*'
|
||||
return response
|
||||
|
|
|
|||
|
|
@ -33,12 +33,11 @@ class GMBlacklistMixin(BlacklistMixin):
|
|||
"""
|
||||
|
||||
@classmethod
|
||||
def for_user_by_source(cls, user, source: int):
|
||||
def for_user(cls, user, source: int = None):
|
||||
"""Create a refresh token."""
|
||||
token = super().for_user(user)
|
||||
token['user'] = user.get_user_info()
|
||||
# Create a record in DB
|
||||
JWTRefreshToken.objects.add_to_db(user=user, token=token, source=source)
|
||||
JWTRefreshToken.objects.make(user=user, token=token, source=source)
|
||||
return token
|
||||
|
||||
|
||||
|
|
@ -70,7 +69,7 @@ class GMRefreshToken(GMBlacklistMixin, GMToken, RefreshToken):
|
|||
|
||||
# Create a record in DB
|
||||
user = User.objects.get(id=self.payload.get('user_id'))
|
||||
JWTAccessToken.objects.add_to_db(user=user,
|
||||
access_token=access_token,
|
||||
refresh_token=self)
|
||||
JWTAccessToken.objects.make(user=user,
|
||||
access_token=access_token,
|
||||
refresh_token=self)
|
||||
return access_token
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from rest_framework.response import Response
|
|||
|
||||
|
||||
# JWT
|
||||
# Login base view mixin
|
||||
# Login base view mixins
|
||||
class JWTGenericViewMixin(generics.GenericAPIView):
|
||||
"""JWT view mixin"""
|
||||
|
||||
|
|
@ -16,6 +16,13 @@ class JWTGenericViewMixin(generics.GenericAPIView):
|
|||
|
||||
REFRESH_TOKEN_HTTP_ONLY = False
|
||||
REFRESH_TOKEN_SECURE = False
|
||||
|
||||
LOCALE_HTTP_ONLY = False
|
||||
LOCALE_SECURE = False
|
||||
|
||||
COUNTRY_CODE_HTTP_ONLY = False
|
||||
COUNTRY_CODE_SECURE = False
|
||||
|
||||
COOKIE = namedtuple('COOKIE', ['key', 'value', 'http_only', 'secure', 'max_age'])
|
||||
|
||||
def _put_data_in_cookies(self,
|
||||
|
|
@ -26,21 +33,32 @@ class JWTGenericViewMixin(generics.GenericAPIView):
|
|||
cookies it is list that contain namedtuples
|
||||
cookies would contain key, value and secure parameters.
|
||||
"""
|
||||
COOKIES = list()
|
||||
COOKIES = []
|
||||
|
||||
# Write to cookie access and refresh token with secure flag
|
||||
if access_token and refresh_token:
|
||||
_access_token = self.COOKIE(key='access_token',
|
||||
value=access_token,
|
||||
http_only=self.ACCESS_TOKEN_HTTP_ONLY,
|
||||
secure=self.ACCESS_TOKEN_SECURE,
|
||||
max_age=settings.COOKIES_MAX_AGE if permanent else None)
|
||||
_refresh_token = self.COOKIE(key='refresh_token',
|
||||
value=refresh_token,
|
||||
http_only=self.REFRESH_TOKEN_HTTP_ONLY,
|
||||
secure=self.REFRESH_TOKEN_SECURE,
|
||||
max_age=settings.COOKIES_MAX_AGE if permanent else None)
|
||||
COOKIES.extend((_access_token, _refresh_token))
|
||||
if hasattr(self.request, 'locale'):
|
||||
COOKIES.append(self.COOKIE(key='locale',
|
||||
value=self.request.locale,
|
||||
http_only=self.ACCESS_TOKEN_HTTP_ONLY,
|
||||
secure=self.LOCALE_SECURE,
|
||||
max_age=settings.COOKIES_MAX_AGE if permanent else None))
|
||||
if hasattr(self.request, 'country_code'):
|
||||
COOKIES.append(self.COOKIE(key='country_code',
|
||||
value=self.request.country_code,
|
||||
http_only=self.COUNTRY_CODE_HTTP_ONLY,
|
||||
secure=self.COUNTRY_CODE_SECURE,
|
||||
max_age=settings.COOKIES_MAX_AGE if permanent else None))
|
||||
if access_token:
|
||||
COOKIES.append(self.COOKIE(key='access_token',
|
||||
value=access_token,
|
||||
http_only=self.ACCESS_TOKEN_HTTP_ONLY,
|
||||
secure=self.ACCESS_TOKEN_SECURE,
|
||||
max_age=settings.COOKIES_MAX_AGE if permanent else None))
|
||||
if refresh_token:
|
||||
COOKIES.append(self.COOKIE(key='refresh_token',
|
||||
value=refresh_token,
|
||||
http_only=self.REFRESH_TOKEN_HTTP_ONLY,
|
||||
secure=self.REFRESH_TOKEN_SECURE,
|
||||
max_age=settings.COOKIES_MAX_AGE if permanent else None))
|
||||
return COOKIES
|
||||
|
||||
def _put_cookies_in_response(self, cookies: list, response: Response):
|
||||
|
|
@ -77,102 +95,3 @@ class JWTGenericViewMixin(generics.GenericAPIView):
|
|||
http_only=self.REFRESH_TOKEN_HTTP_ONLY,
|
||||
secure=self.REFRESH_TOKEN_SECURE,
|
||||
max_age=_cookies.get('max_age'))]
|
||||
|
||||
|
||||
class JWTListAPIView(JWTGenericViewMixin, generics.ListAPIView):
|
||||
"""
|
||||
Concrete view for creating a model instance.
|
||||
"""
|
||||
def get(self, request, *args, **kwargs):
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
page = self.paginate_queryset(queryset)
|
||||
if page is not None:
|
||||
serializer = self.get_serializer(page, many=True)
|
||||
response = self.get_paginated_response(serializer.data)
|
||||
else:
|
||||
serializer = self.get_serializer(queryset, many=True)
|
||||
response = Response(serializer.data)
|
||||
access_token, refresh_token = self._get_tokens_from_cookies(request)
|
||||
return self._put_cookies_in_response(
|
||||
cookies=self._put_data_in_cookies(access_token=access_token.value,
|
||||
refresh_token=refresh_token.value),
|
||||
response=response)
|
||||
|
||||
|
||||
class JWTCreateAPIView(JWTGenericViewMixin, generics.CreateAPIView):
|
||||
"""
|
||||
Concrete view for creating a model instance.
|
||||
"""
|
||||
def post(self, request, *args, **kwargs):
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
response = Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
access_token, refresh_token = self._get_tokens_from_cookies(request)
|
||||
return self._put_cookies_in_response(
|
||||
cookies=self._put_data_in_cookies(access_token=access_token.value,
|
||||
refresh_token=refresh_token.value),
|
||||
response=response)
|
||||
|
||||
|
||||
class JWTRetrieveAPIView(JWTGenericViewMixin, generics.RetrieveAPIView):
|
||||
"""
|
||||
Concrete view for retrieving a model instance.
|
||||
"""
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""Implement GET method"""
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
page = self.paginate_queryset(queryset)
|
||||
if page is not None:
|
||||
serializer = self.get_serializer(page, many=True)
|
||||
response = self.get_paginated_response(serializer.data)
|
||||
else:
|
||||
serializer = self.get_serializer(queryset, many=True)
|
||||
response = Response(serializer.data, status.HTTP_200_OK)
|
||||
access_token, refresh_token = self._get_tokens_from_cookies(request)
|
||||
return self._put_cookies_in_response(
|
||||
cookies=self._put_data_in_cookies(access_token=access_token,
|
||||
refresh_token=refresh_token),
|
||||
response=response)
|
||||
|
||||
|
||||
class JWTDestroyAPIView(JWTGenericViewMixin, generics.DestroyAPIView):
|
||||
"""
|
||||
Concrete view for deleting a model instance.
|
||||
"""
|
||||
def delete(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
instance.delete()
|
||||
response = Response(status=status.HTTP_204_NO_CONTENT)
|
||||
access_token, refresh_token = self._get_tokens_from_cookies(request)
|
||||
return self._put_cookies_in_response(
|
||||
cookies=self._put_data_in_cookies(access_token=access_token,
|
||||
refresh_token=refresh_token),
|
||||
response=response)
|
||||
|
||||
|
||||
class JWTUpdateAPIView(JWTGenericViewMixin, generics.UpdateAPIView):
|
||||
"""
|
||||
Concrete view for updating a model instance.
|
||||
"""
|
||||
def put(self, request, *args, **kwargs):
|
||||
partial = kwargs.pop('partial', False)
|
||||
instance = self.get_object()
|
||||
serializer = self.get_serializer(instance, data=request.data, partial=partial)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
if getattr(instance, '_prefetched_objects_cache', None):
|
||||
# If 'prefetch_related' has been applied to a queryset, we need to
|
||||
# forcibly invalidate the prefetch cache on the instance.
|
||||
instance._prefetched_objects_cache = {}
|
||||
response = Response(serializer.data)
|
||||
access_token, refresh_token = self._get_tokens_from_cookies(request)
|
||||
return self._put_cookies_in_response(
|
||||
cookies=self._put_data_in_cookies(access_token=access_token,
|
||||
refresh_token=refresh_token),
|
||||
response=response)
|
||||
|
||||
def patch(self, request, *args, **kwargs):
|
||||
kwargs['partial'] = True
|
||||
return self.put(request, *args, **kwargs)
|
||||
|
||||
|
|
|
|||
|
|
@ -79,7 +79,6 @@ EXTERNAL_APPS = [
|
|||
'rest_framework',
|
||||
'rest_framework.authtoken',
|
||||
'easy_select2',
|
||||
'corsheaders',
|
||||
'oauth2_provider',
|
||||
'social_django',
|
||||
'rest_framework_social_oauth2',
|
||||
|
|
@ -87,6 +86,7 @@ EXTERNAL_APPS = [
|
|||
'rest_framework_simplejwt.token_blacklist',
|
||||
'solo',
|
||||
'phonenumber_field',
|
||||
'corsheaders',
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -333,10 +333,6 @@ THUMBNAIL_ALIASES = {
|
|||
# Password reset
|
||||
RESETTING_TOKEN_EXPIRATION = 24 # hours
|
||||
|
||||
# CORS Config
|
||||
CORS_ORIGIN_ALLOW_ALL = True
|
||||
CORS_ALLOW_CREDENTIALS = True
|
||||
|
||||
|
||||
GEOIP_PATH = os.path.join(PROJECT_ROOT, 'geoip_db')
|
||||
|
||||
|
|
@ -373,7 +369,6 @@ PASSWORD_RESET_TIMEOUT_DAYS = 1
|
|||
|
||||
|
||||
# TEMPLATES
|
||||
CONFIRMATION_PASSWORD_RESET_TEMPLATE = 'account/password_reset_confirm.html'
|
||||
RESETTING_TOKEN_TEMPLATE = 'account/password_reset_email.html'
|
||||
CHANGE_EMAIL_TEMPLATE = 'account/change_email.html'
|
||||
CONFIRM_EMAIL_TEMPLATE = 'authorization/confirm_email.html'
|
||||
|
|
@ -381,6 +376,12 @@ CONFIRM_EMAIL_TEMPLATE = 'authorization/confirm_email.html'
|
|||
|
||||
# COOKIES
|
||||
COOKIES_MAX_AGE = 86400 # 24 hours
|
||||
SESSION_COOKIE_SAMESITE = None
|
||||
|
||||
|
||||
# CORS
|
||||
CORS_ORIGIN_ALLOW_ALL = True
|
||||
CORS_ALLOW_CREDENTIALS = True
|
||||
|
||||
|
||||
# UPLOAD FILES
|
||||
|
|
|
|||
13
project/settings/stage.py
Normal file
13
project/settings/stage.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
"""Stage settings."""
|
||||
from .base import *
|
||||
|
||||
ALLOWED_HOSTS = ['gm-stage.id-east.ru', '95.213.204.126']
|
||||
|
||||
SEND_SMS = False
|
||||
SMS_CODE_SHOW = True
|
||||
USE_CELERY = False
|
||||
|
||||
SCHEMA_URI = 'https'
|
||||
DEFAULT_SUBDOMAIN = 'www'
|
||||
SITE_DOMAIN_URI = 'id-east.ru'
|
||||
DOMAIN_URI = 'gm-stage.id-east.ru'
|
||||
|
|
@ -2,9 +2,8 @@
|
|||
{% blocktrans %}You're receiving this email because you want to change email address at {{ site_name }}.{% endblocktrans %}
|
||||
|
||||
{% trans "Please go to the following page for confirmation new email address:" %}
|
||||
{% block reset_link %}
|
||||
http://{{ domain_uri }}{% url 'web:account:change-email-confirm' uidb64=uidb64 token=token %}
|
||||
{% endblock %}
|
||||
|
||||
<a href="https://{{ country_code }}.{{ domain_uri }}/registered/{{ uidb64 }}/{{ token }}/">https://{{ country_code }}.{{ domain_uri }}/registered/{{ uidb64 }}/{{ token }}/</a>
|
||||
|
||||
{% trans "Thanks for using our site!" %}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
{% load i18n static %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if validlink %}
|
||||
|
||||
<p>{% trans "Please enter your new password twice so we can verify you typed it in correctly." %}</p>
|
||||
|
||||
<form method="post">{% csrf_token %}
|
||||
<fieldset class="module aligned">
|
||||
<div class="form-row field-password1">
|
||||
{{ form.new_password1.errors }}
|
||||
<label for="id_new_password1">{% trans 'New password:' %}</label>
|
||||
{{ form.new_password1 }}
|
||||
</div>
|
||||
<div class="form-row field-password2">
|
||||
{{ form.new_password2.errors }}
|
||||
<label for="id_new_password2">{% trans 'Confirm password:' %}</label>
|
||||
{{ form.new_password2 }}
|
||||
</div>
|
||||
<input type="submit" value="{% trans 'Change my password' %}">
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
{% else %}
|
||||
|
||||
<p>{% trans "The password reset link was invalid, possibly because it has already been used. Please request a new password reset." %}</p>
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -2,9 +2,8 @@
|
|||
{% blocktrans %}You're receiving this email because you requested a password reset for your user account at {{ site_name }}.{% endblocktrans %}
|
||||
|
||||
{% trans "Please go to the following page and choose a new password:" %}
|
||||
{% block reset_link %}
|
||||
http://{{ domain_uri }}{% url 'web:account:form-password-reset-confirm' uidb64=uidb64 token=token %}
|
||||
{% endblock %}
|
||||
|
||||
<a href="https://{{ country_code }}.{{ domain_uri }}/recovery/{{ uidb64 }}/{{ token }}/">https://{{ country_code }}.{{ domain_uri }}/recovery/{{ uidb64 }}/{{ token }}/</a>
|
||||
|
||||
{% trans "Thanks for using our site!" %}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,8 @@
|
|||
{% blocktrans %}You're receiving this email because you trying to register new account at {{ site_name }}.{% endblocktrans %}
|
||||
|
||||
{% trans "Please confirm your email address to complete the registration:" %}
|
||||
{% block signup_confirm %}
|
||||
http://{{ domain_uri }}{% url 'auth:signup-confirm' uidb64=uidb64 token=token %}
|
||||
{% endblock %}
|
||||
|
||||
<a href="https://{{ country_code }}.{{ domain_uri }}/registered/{{ uidb64 }}/{{ token }}/">https://{{ country_code }}.{{ domain_uri }}/registered/{{ uidb64 }}/{{ token }}/</a>
|
||||
|
||||
{% trans "Thanks for using our site!" %}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@ from django.urls import path, include
|
|||
app_name = 'mobile'
|
||||
|
||||
urlpatterns = [
|
||||
path('establishments/', include('establishment.urls.mobile')),
|
||||
# path('account/', include('account.urls.web')),
|
||||
# path('advertisement/', include('advertisement.urls.web')),
|
||||
# path('collection/', include('collection.urls.web')),
|
||||
# path('establishments/', include('establishment.urls.web')),
|
||||
# path('news/', include('news.urls.web')),
|
||||
# path('partner/', include('partner.urls.web')),
|
||||
]
|
||||
]
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user