Merge branch 'develop' of ssh://gl.id-east.ru:222/gm/gm-backend into develop

This commit is contained in:
Dmitriy Kuzmenko 2019-11-22 18:34:01 +03:00
commit 193a7ae29d
23 changed files with 428 additions and 93 deletions

View File

@ -55,3 +55,23 @@ class EstablishmentTypeTagFilter(filters.FilterSet):
fields = ( fields = (
'type_id', 'type_id',
) )
class EmployeeBackFilter(filters.FilterSet):
"""Employee filter set."""
search = filters.CharFilter(method='search_by_name_or_last_name')
class Meta:
"""Meta class."""
model = models.Employee
fields = (
'search',
)
def search_by_name_or_last_name(self, queryset, name, value):
"""Search by name or last name."""
if value not in EMPTY_VALUES:
return queryset.search_by_name_or_last_name(value)
return queryset

View File

@ -0,0 +1,28 @@
# Generated by Django 2.2.7 on 2019-11-22 11:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('establishment', '0065_establishment_purchased_products'),
]
operations = [
migrations.AddField(
model_name='employee',
name='last_name',
field=models.CharField(default=None, max_length=255, null=True, verbose_name='Last Name'),
),
migrations.AddField(
model_name='establishmentemployee',
name='status',
field=models.CharField(choices=[('I', 'Idle'), ('A', 'Accepted'), ('D', 'Declined')], default='I', max_length=1),
),
migrations.AlterField(
model_name='employee',
name='name',
field=models.CharField(max_length=255, verbose_name='Name'),
),
]

View File

@ -0,0 +1,39 @@
# Generated by Django 2.2.7 on 2019-11-22 12:44
from django.db import migrations, models
import phonenumber_field.modelfields
class Migration(migrations.Migration):
dependencies = [
('establishment', '0066_auto_20191122_1144'),
]
operations = [
migrations.AddField(
model_name='employee',
name='birth_date',
field=models.DateTimeField(default=None, null=True, verbose_name='Birth date'),
),
migrations.AddField(
model_name='employee',
name='email',
field=models.EmailField(blank=True, default=None, max_length=254, null=True, verbose_name='Email'),
),
migrations.AddField(
model_name='employee',
name='phone',
field=phonenumber_field.modelfields.PhoneNumberField(default=None, max_length=128, null=True),
),
migrations.AddField(
model_name='employee',
name='sex',
field=models.PositiveSmallIntegerField(choices=[(0, 'Male'), (1, 'Female')], default=None, null=True, verbose_name='Sex'),
),
migrations.AddField(
model_name='employee',
name='toque_number',
field=models.PositiveSmallIntegerField(default=None, null=True, verbose_name='Toque number'),
),
]

View File

@ -1,6 +1,7 @@
"""Establishment models.""" """Establishment models."""
from datetime import datetime from datetime import datetime
from functools import reduce from functools import reduce
from typing import List
from operator import or_ from operator import or_
import elasticsearch_dsl import elasticsearch_dsl
@ -118,11 +119,13 @@ class EstablishmentQuerySet(models.QuerySet):
'address__city__country') 'address__city__country')
def with_extended_related(self): def with_extended_related(self):
return self.select_related('establishment_type'). \ return self.with_extended_address_related().select_related('establishment_type'). \
prefetch_related('establishment_subtypes', 'awards', 'schedule', prefetch_related('establishment_subtypes', 'awards', 'schedule',
'phones'). \ 'phones', 'gallery', 'menu_set', 'menu_set__plate_set',
'menu_set__plate_set__currency', 'currency'). \
prefetch_actual_employees() prefetch_actual_employees()
def with_type_related(self): def with_type_related(self):
return self.prefetch_related('establishment_subtypes') return self.prefetch_related('establishment_subtypes')
@ -395,6 +398,7 @@ class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin,
verbose_name=_('Tag')) verbose_name=_('Tag'))
reviews = generic.GenericRelation(to='review.Review') reviews = generic.GenericRelation(to='review.Review')
comments = generic.GenericRelation(to='comment.Comment') comments = generic.GenericRelation(to='comment.Comment')
carousels = generic.GenericRelation(to='main.Carousel')
favorites = generic.GenericRelation(to='favorites.Favorites') favorites = generic.GenericRelation(to='favorites.Favorites')
currency = models.ForeignKey(Currency, blank=True, null=True, default=None, currency = models.ForeignKey(Currency, blank=True, null=True, default=None,
on_delete=models.PROTECT, on_delete=models.PROTECT,
@ -434,11 +438,12 @@ class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin,
@property @property
def visible_tags(self): def visible_tags(self):
return super().visible_tags\ return super().visible_tags \
.exclude(category__index_name__in=['guide', 'collection', 'purchased_item', .exclude(category__index_name__in=['guide', 'collection', 'purchased_item',
'business_tag', 'business_tags_de'])\ 'business_tag', 'business_tags_de']) \
\
# todo: recalculate toque_number
# todo: recalculate toque_number
def recalculate_toque_number(self): def recalculate_toque_number(self):
toque_number = 0 toque_number = 0
if self.address and self.public_mark: if self.address and self.public_mark:
@ -611,7 +616,6 @@ class EstablishmentNote(ProjectBaseMixin):
class EstablishmentGallery(IntermediateGalleryModelMixin): class EstablishmentGallery(IntermediateGalleryModelMixin):
establishment = models.ForeignKey(Establishment, null=True, establishment = models.ForeignKey(Establishment, null=True,
related_name='establishment_gallery', related_name='establishment_gallery',
on_delete=models.CASCADE, on_delete=models.CASCADE,
@ -662,6 +666,16 @@ class EstablishmentEmployeeQuerySet(models.QuerySet):
class EstablishmentEmployee(BaseAttributes): class EstablishmentEmployee(BaseAttributes):
"""EstablishmentEmployee model.""" """EstablishmentEmployee model."""
IDLE = 'I'
ACCEPTED = 'A'
DECLINED = 'D'
STATUS_CHOICES = (
(IDLE, 'Idle'),
(ACCEPTED, 'Accepted'),
(DECLINED, 'Declined'),
)
establishment = models.ForeignKey(Establishment, on_delete=models.PROTECT, establishment = models.ForeignKey(Establishment, on_delete=models.PROTECT,
verbose_name=_('Establishment')) verbose_name=_('Establishment'))
employee = models.ForeignKey('establishment.Employee', on_delete=models.PROTECT, employee = models.ForeignKey('establishment.Employee', on_delete=models.PROTECT,
@ -672,19 +686,53 @@ class EstablishmentEmployee(BaseAttributes):
verbose_name=_('To date')) verbose_name=_('To date'))
position = models.ForeignKey(Position, on_delete=models.PROTECT, position = models.ForeignKey(Position, on_delete=models.PROTECT,
verbose_name=_('Position')) verbose_name=_('Position'))
status = models.CharField(max_length=1, choices=STATUS_CHOICES, default=IDLE)
# old_id = affiliations_id # old_id = affiliations_id
old_id = models.IntegerField(verbose_name=_('Old id'), null=True, blank=True) old_id = models.IntegerField(verbose_name=_('Old id'), null=True, blank=True)
objects = EstablishmentEmployeeQuerySet.as_manager() objects = EstablishmentEmployeeQuerySet.as_manager()
class EmployeeQuerySet(models.QuerySet):
def _generic_search(self, value, filter_fields_names: List[str]):
"""Generic method for searching value in specified fields"""
filters = [
{f'{field}__icontains': value}
for field in filter_fields_names
]
return self.filter(reduce(lambda x, y: x | y, [models.Q(**i) for i in filters]))
def search_by_name_or_last_name(self, value):
"""Search by name or last_name."""
return self._generic_search(value, ['name', 'last_name'])
class Employee(BaseAttributes): class Employee(BaseAttributes):
"""Employee model.""" """Employee model."""
user = models.OneToOneField('account.User', on_delete=models.PROTECT, user = models.OneToOneField('account.User', on_delete=models.PROTECT,
null=True, blank=True, default=None, null=True, blank=True, default=None,
verbose_name=_('User')) verbose_name=_('User'))
name = models.CharField(max_length=255, verbose_name=_('Last name')) name = models.CharField(max_length=255, verbose_name=_('Name'))
last_name = models.CharField(max_length=255, verbose_name=_('Last Name'), null=True, default=None)
# SEX CHOICES
MALE = 0
FEMALE = 1
SEX_CHOICES = (
(MALE, _('Male')),
(FEMALE, _('Female'))
)
sex = models.PositiveSmallIntegerField(choices=SEX_CHOICES, verbose_name=_('Sex'), null=True, default=None)
birth_date = models.DateTimeField(editable=True, verbose_name=_('Birth date'), null=True, default=None)
email = models.EmailField(blank=True, null=True, default=None, verbose_name=_('Email'))
phone = PhoneNumberField(null=True, default=None)
toque_number = models.PositiveSmallIntegerField(verbose_name=_('Toque number'), null=True, default=None)
establishments = models.ManyToManyField(Establishment, related_name='employees', establishments = models.ManyToManyField(Establishment, related_name='employees',
through=EstablishmentEmployee, ) through=EstablishmentEmployee, )
awards = generic.GenericRelation(to='main.Award', related_query_name='employees') awards = generic.GenericRelation(to='main.Award', related_query_name='employees')
@ -693,6 +741,8 @@ class Employee(BaseAttributes):
# old_id = profile_id # old_id = profile_id
old_id = models.IntegerField(verbose_name=_('Old id'), null=True, blank=True) old_id = models.IntegerField(verbose_name=_('Old id'), null=True, blank=True)
objects = EmployeeQuerySet.as_manager()
class Meta: class Meta:
"""Meta class.""" """Meta class."""

View File

@ -2,8 +2,9 @@ from rest_framework import serializers
from establishment import models from establishment import models
from establishment import serializers as model_serializers from establishment import serializers as model_serializers
from location.serializers import AddressDetailSerializer from location.serializers import AddressDetailSerializer, TranslatedField
from main.models import Currency from main.models import Currency
from main.serializers import AwardSerializer
from utils.decorators import with_base_attributes from utils.decorators import with_base_attributes
from utils.serializers import TimeZoneChoiceField from utils.serializers import TimeZoneChoiceField
from gallery.models import Image from gallery.models import Image
@ -161,12 +162,53 @@ class ContactEmailBackSerializers(model_serializers.PlateSerializer):
class EmployeeBackSerializers(serializers.ModelSerializer): class EmployeeBackSerializers(serializers.ModelSerializer):
"""Employee serializers.""" """Employee serializers."""
awards = AwardSerializer(many=True)
class Meta: class Meta:
model = models.Employee model = models.Employee
fields = [ fields = [
'id', 'id',
'user', 'user',
'name' 'name',
'last_name',
'sex',
'birth_date',
'email',
'phone',
'toque_number',
'awards',
]
class PositionBackSerializer(serializers.ModelSerializer):
"""Position Back serializer."""
name_translated = TranslatedField()
class Meta:
model = models.Position
fields = [
'id',
'name_translated',
'priority',
'index_name',
]
class EstablishmentEmployeeBackSerializer(serializers.ModelSerializer):
"""Establishment Employee serializer."""
employee = EmployeeBackSerializers()
position = PositionBackSerializer()
class Meta:
model = models.EstablishmentEmployee
fields = [
'id',
'employee',
'from_date',
'to_date',
'position',
] ]

View File

@ -13,7 +13,7 @@ from review.serializers import ReviewShortSerializer
from tag.serializers import TagBaseSerializer from tag.serializers import TagBaseSerializer
from timetable.serialziers import ScheduleRUDSerializer from timetable.serialziers import ScheduleRUDSerializer
from utils import exceptions as utils_exceptions from utils import exceptions as utils_exceptions
from utils.serializers import ImageBaseSerializer from utils.serializers import ImageBaseSerializer, CarouselCreateSerializer
from utils.serializers import (ProjectModelSerializer, TranslatedField, from utils.serializers import (ProjectModelSerializer, TranslatedField,
FavoritesCreateSerializer) FavoritesCreateSerializer)
@ -168,12 +168,51 @@ class EstablishmentEmployeeSerializer(serializers.ModelSerializer):
awards = AwardSerializer(source='employee.awards', many=True) awards = AwardSerializer(source='employee.awards', many=True)
priority = serializers.IntegerField(source='position.priority') priority = serializers.IntegerField(source='position.priority')
position_index_name = serializers.CharField(source='position.index_name') position_index_name = serializers.CharField(source='position.index_name')
status = serializers.CharField()
class Meta: class Meta:
"""Meta class.""" """Meta class."""
model = models.Employee model = models.Employee
fields = ('id', 'name', 'position_translated', 'awards', 'priority', 'position_index_name') fields = ('id', 'name', 'position_translated', 'awards', 'priority', 'position_index_name', 'status')
class EstablishmentEmployeeCreateSerializer(serializers.ModelSerializer):
"""Serializer for establishment employee relation."""
class Meta:
"""Meta class."""
model = models.EstablishmentEmployee
fields = ('id',)
def _validate_entity(self, entity_id_param: str, entity_class):
entity_id = self.context.get('request').parser_context.get('kwargs').get(entity_id_param)
entity_qs = entity_class.objects.filter(id=entity_id)
if not entity_qs.exists():
raise serializers.ValidationError({'detail': _(f'{entity_class.__name__} not found.')})
return entity_qs.first()
def validate(self, attrs):
"""Override validate method"""
establishment = self._validate_entity("establishment_id", models.Establishment)
employee = self._validate_entity("employee_id", models.Employee)
position = self._validate_entity("position_id", models.Position)
attrs['establishment'] = establishment
attrs['employee'] = employee
attrs['position'] = position
return attrs
def create(self, validated_data, *args, **kwargs):
"""Override create method"""
validated_data.update({
'employee': validated_data.pop('employee'),
'establishment': validated_data.pop('establishment'),
'position': validated_data.pop("position")
})
return super().create(validated_data)
class EstablishmentShortSerializer(serializers.ModelSerializer): class EstablishmentShortSerializer(serializers.ModelSerializer):
@ -396,6 +435,22 @@ class EstablishmentCommentCreateSerializer(comment_serializers.CommentSerializer
return super().create(validated_data) return super().create(validated_data)
class EstablishmentCommentRUDSerializer(comment_serializers.CommentSerializer):
"""Retrieve/Update/Destroy comment serializer."""
class Meta:
"""Meta class."""
model = comment_models.Comment
fields = [
'id',
'created',
'text',
'mark',
'nickname',
'profile_pic',
]
class EstablishmentFavoritesCreateSerializer(FavoritesCreateSerializer): class EstablishmentFavoritesCreateSerializer(FavoritesCreateSerializer):
"""Serializer to favorite object w/ model Establishment.""" """Serializer to favorite object w/ model Establishment."""
@ -426,6 +481,27 @@ class EstablishmentFavoritesCreateSerializer(FavoritesCreateSerializer):
return super().create(validated_data) return super().create(validated_data)
class EstablishmentCarouselCreateSerializer(CarouselCreateSerializer):
"""Serializer to carousel object w/ model News."""
def validate(self, attrs):
establishment = models.Establishment.objects.filter(slug=self.slug).first()
if not establishment:
raise serializers.ValidationError({'detail': _('Object not found.')})
if establishment.carousels.exists():
raise utils_exceptions.CarouselError()
attrs['establishment'] = establishment
return attrs
def create(self, validated_data, *args, **kwargs):
validated_data.update({
'content_object': validated_data.pop('establishment')
})
return super().create(validated_data)
class CompanyBaseSerializer(serializers.ModelSerializer): class CompanyBaseSerializer(serializers.ModelSerializer):
"""Company base serializer""" """Company base serializer"""
phone_list = serializers.SerializerMethodField(source='phones', read_only=True) phone_list = serializers.SerializerMethodField(source='phones', read_only=True)

View File

@ -38,8 +38,16 @@ urlpatterns = [
path('phones/<int:pk>/', views.PhonesRUDView.as_view(), name='phones-rud'), path('phones/<int:pk>/', views.PhonesRUDView.as_view(), name='phones-rud'),
path('emails/', views.EmailListCreateView.as_view(), name='emails'), path('emails/', views.EmailListCreateView.as_view(), name='emails'),
path('emails/<int:pk>/', views.EmailRUDView.as_view(), name='emails-rud'), path('emails/<int:pk>/', views.EmailRUDView.as_view(), name='emails-rud'),
path('<int:establishment_id>/employees/', views.EstablishmentEmployeeListView.as_view(),
name='establishment-employees'),
path('employees/', views.EmployeeListCreateView.as_view(), name='employees'), path('employees/', views.EmployeeListCreateView.as_view(), name='employees'),
path('employees/<int:pk>/', views.EmployeeRUDView.as_view(), name='employees-rud'), path('employees/<int:pk>/', views.EmployeeRUDView.as_view(), name='employees-rud'),
path('<int:establishment_id>/employee/<int:employee_id>/position/<int:position_id>',
views.EstablishmentEmployeeCreateView.as_view(),
name='employees-establishment-create'),
path('<int:establishment_id>/employee/<int:employee_id>',
views.EstablishmentEmployeeDeleteView.as_view(),
name='employees-establishment-delete'),
path('types/', views.EstablishmentTypeListCreateView.as_view(), name='type-list'), path('types/', views.EstablishmentTypeListCreateView.as_view(), name='type-list'),
path('types/<int:pk>/', views.EstablishmentTypeRUDView.as_view(), name='type-rud'), path('types/<int:pk>/', views.EstablishmentTypeRUDView.as_view(), name='type-rud'),
path('subtypes/', views.EstablishmentSubtypeListCreateView.as_view(), name='subtype-list'), path('subtypes/', views.EstablishmentSubtypeListCreateView.as_view(), name='subtype-list'),

View File

@ -17,5 +17,7 @@ urlpatterns = [
path('slug/<slug:slug>/comments/<int:comment_id>/', views.EstablishmentCommentRUDView.as_view(), path('slug/<slug:slug>/comments/<int:comment_id>/', views.EstablishmentCommentRUDView.as_view(),
name='rud-comment'), name='rud-comment'),
path('slug/<slug:slug>/favorites/', views.EstablishmentFavoritesCreateDestroyView.as_view(), path('slug/<slug:slug>/favorites/', views.EstablishmentFavoritesCreateDestroyView.as_view(),
name='create-destroy-favorites') name='create-destroy-favorites'),
path('slug/<slug:slug>/carousels/', views.EstablishmentCarouselCreateDestroyView.as_view(),
name='create-destroy-carousels')
] ]

View File

@ -1,7 +1,9 @@
"""Establishment app views.""" """Establishment app views."""
from django.http import Http404, HttpResponse
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from rest_framework import generics, permissions from rest_framework import generics, permissions, status
from utils.permissions import IsCountryAdmin, IsEstablishmentManager
from establishment import filters, models, serializers from establishment import filters, models, serializers
from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer
from utils.permissions import IsCountryAdmin, IsEstablishmentManager from utils.permissions import IsCountryAdmin, IsEstablishmentManager
@ -43,8 +45,8 @@ class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView):
""" """
Returns the object the view is displaying. Returns the object the view is displaying.
""" """
establishment_pk = self.kwargs.get('pk') establishment_pk = self.kwargs['pk']
schedule_id = self.kwargs.get('schedule_id') schedule_id = self.kwargs['schedule_id']
establishment = get_object_or_404(klass=models.Establishment.objects.all(), establishment = get_object_or_404(klass=models.Establishment.objects.all(),
pk=establishment_pk) pk=establishment_pk)
@ -156,11 +158,23 @@ class EmailRUDView(generics.RetrieveUpdateDestroyAPIView):
class EmployeeListCreateView(generics.ListCreateAPIView): class EmployeeListCreateView(generics.ListCreateAPIView):
"""Emplyoee list create view.""" """Emplyoee list create view."""
permission_classes = (permissions.AllowAny, )
filter_class = filters.EmployeeBackFilter
serializer_class = serializers.EmployeeBackSerializers serializer_class = serializers.EmployeeBackSerializers
queryset = models.Employee.objects.all() queryset = models.Employee.objects.all()
pagination_class = None pagination_class = None
class EstablishmentEmployeeListView(generics.ListAPIView):
"""Establishment emplyoees list view."""
permission_classes = (permissions.AllowAny, )
serializer_class = serializers.EstablishmentEmployeeBackSerializer
def get_queryset(self):
establishment_id = self.kwargs['establishment_id']
return models.EstablishmentEmployee.objects.filter(establishment__id=establishment_id)
class EmployeeRUDView(generics.RetrieveUpdateDestroyAPIView): class EmployeeRUDView(generics.RetrieveUpdateDestroyAPIView):
"""Employee RUD view.""" """Employee RUD view."""
serializer_class = serializers.EmployeeBackSerializers serializer_class = serializers.EmployeeBackSerializers
@ -318,3 +332,27 @@ class EstablishmentNoteRUDView(EstablishmentMixinViews,
self.check_object_permissions(self.request, note) self.check_object_permissions(self.request, note)
return note return note
class EstablishmentEmployeeCreateView(generics.CreateAPIView):
serializer_class = serializers.EstablishmentEmployeeCreateSerializer
queryset = models.EstablishmentEmployee.objects.all()
# TODO send email to all admins and add endpoint for changing status
class EstablishmentEmployeeDeleteView(generics.DestroyAPIView):
def _get_object_to_delete(self, establishment_id, employee_id):
result_qs = models.EstablishmentEmployee\
.objects\
.filter(establishment_id=establishment_id, employee_id=employee_id)
if not result_qs.exists():
raise Http404
return result_qs.first()
def delete(self, request, *args, **kwargs):
establishment_id = self.kwargs["establishment_id"]
employee_id = self.kwargs["employee_id"]
object_to_delete = self._get_object_to_delete(establishment_id, employee_id)
object_to_delete.delete()
return HttpResponse(status=status.HTTP_204_NO_CONTENT)

View File

@ -9,7 +9,7 @@ from comment.serializers import CommentRUDSerializer
from establishment import filters, models, serializers from establishment import filters, models, serializers
from main import methods from main import methods
from utils.pagination import EstablishmentPortionPagination from utils.pagination import EstablishmentPortionPagination
from utils.views import FavoritesCreateDestroyMixinView from utils.views import FavoritesCreateDestroyMixinView, CarouselCreateDestroyMixinView
class EstablishmentMixinView: class EstablishmentMixinView:
@ -34,7 +34,7 @@ class EstablishmentListView(EstablishmentMixinView, generics.ListAPIView):
serializer_class = serializers.EstablishmentListRetrieveSerializer serializer_class = serializers.EstablishmentListRetrieveSerializer
def get_queryset(self): def get_queryset(self):
return super().get_queryset().with_schedule()\ return super().get_queryset().with_schedule() \
.with_extended_address_related().with_currency_related() .with_extended_address_related().with_currency_related()
@ -56,12 +56,11 @@ class EstablishmentRecentReviewListView(EstablishmentListView):
def get_queryset(self): def get_queryset(self):
"""Overridden method 'get_queryset'.""" """Overridden method 'get_queryset'."""
qs = super().get_queryset() qs = super().get_queryset()
user_ip = methods.get_user_ip(self.request)
query_params = self.request.query_params query_params = self.request.query_params
if 'longitude' in query_params and 'latitude' in query_params: if 'longitude' in query_params and 'latitude' in query_params:
longitude, latitude = query_params.get('longitude'), query_params.get('latitude') longitude, latitude = query_params.get('longitude'), query_params.get('latitude')
else: else:
longitude, latitude = methods.determine_coordinates(user_ip) longitude, latitude = methods.determine_coordinates(self.request)
if not longitude or not latitude: if not longitude or not latitude:
return qs.none() return qs.none()
point = Point(x=float(longitude), y=float(latitude), srid=settings.GEO_DEFAULT_SRID) point = Point(x=float(longitude), y=float(latitude), srid=settings.GEO_DEFAULT_SRID)
@ -106,9 +105,9 @@ class EstablishmentCommentListView(generics.ListAPIView):
establishment = get_object_or_404(models.Establishment, slug=self.kwargs['slug']) establishment = get_object_or_404(models.Establishment, slug=self.kwargs['slug'])
return comment_models.Comment.objects.by_content_type(app_label='establishment', return comment_models.Comment.objects.by_content_type(app_label='establishment',
model='establishment')\ model='establishment') \
.by_object_id(object_id=establishment.pk)\ .by_object_id(object_id=establishment.pk) \
.order_by('-created') .order_by('-created')
class EstablishmentCommentRUDView(generics.RetrieveUpdateDestroyAPIView): class EstablishmentCommentRUDView(generics.RetrieveUpdateDestroyAPIView):
@ -140,6 +139,13 @@ class EstablishmentFavoritesCreateDestroyView(FavoritesCreateDestroyMixinView):
serializer_class = serializers.EstablishmentFavoritesCreateSerializer serializer_class = serializers.EstablishmentFavoritesCreateSerializer
class EstablishmentCarouselCreateDestroyView(CarouselCreateDestroyMixinView):
"""View for create/destroy establishment from carousel."""
_model = models.Establishment
serializer_class = serializers.EstablishmentCarouselCreateSerializer
class EstablishmentNearestRetrieveView(EstablishmentListView, generics.ListAPIView): class EstablishmentNearestRetrieveView(EstablishmentListView, generics.ListAPIView):
"""Resource for getting list of nearest establishments.""" """Resource for getting list of nearest establishments."""

View File

@ -28,31 +28,25 @@ def get_user_ip(request):
return ip return ip
def determine_country_code(ip_addr): def determine_country_code(request):
"""Determine country code.""" """Determine country code."""
country_code = None META = request.META
if ip_addr: country_code = META.get('X-GeoIP-Country-Code',
try: META.get('HTTP_X_GEOIP_COUNTRY_CODE'))
geoip = GeoIP2() if isinstance(country_code, str):
country_code = geoip.country_code(ip_addr) return country_code.lower()
country_code = country_code.lower()
except GeoIP2Exception as ex:
logger.info(f'GEOIP Exception: {ex}. ip: {ip_addr}')
except Exception as ex:
logger.error(f'GEOIP Base exception: {ex}')
return country_code
def determine_coordinates(ip_addr: str) -> Tuple[Optional[float], Optional[float]]: def determine_coordinates(request):
if ip_addr: META = request.META
try: longitude = META.get('X-GeoIP-Longitude',
geoip = GeoIP2() META.get('HTTP_X_GEOIP_LONGITUDE'))
return geoip.coords(ip_addr) latitude = META.get('X-GeoIP-Latitude',
except GeoIP2Exception as ex: META.get('HTTP_X_GEOIP_LATITUDE'))
logger.warning(f'GEOIP Exception: {ex}. ip: {ip_addr}') try:
except Exception as ex: return float(longitude), float(latitude)
logger.warning(f'GEOIP Base exception: {ex}') except (TypeError, ValueError):
return None, None return None, None
def determine_user_site_url(country_code): def determine_user_site_url(country_code):
@ -76,15 +70,11 @@ def determine_user_site_url(country_code):
return site.site_url return site.site_url
def determine_user_city(ip_addr: str) -> Optional[City]: def determine_user_city(request):
try: META = request.META
geoip = GeoIP2() city = META.get('X-GeoIP-City',
return geoip.city(ip_addr) META.get('HTTP_X_GEOIP_CITY'))
except GeoIP2Exception as ex: return city
logger.warning(f'GEOIP Exception: {ex}. ip: {ip_addr}')
except Exception as ex:
logger.warning(f'GEOIP Base exception: {ex}')
return None
def determine_subdivision( def determine_subdivision(

View File

@ -86,9 +86,8 @@ class DetermineLocation(generics.GenericAPIView):
serializer_class = EmptySerializer serializer_class = EmptySerializer
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
user_ip = methods.get_user_ip(request) longitude, latitude = methods.determine_coordinates(request)
longitude, latitude = methods.determine_coordinates(user_ip) city = methods.determine_user_city(request)
city = methods.determine_user_city(user_ip)
if longitude and latitude and city: if longitude and latitude and city:
return Response(data={'latitude': latitude, 'longitude': longitude, 'city': city}) return Response(data={'latitude': latitude, 'longitude': longitude, 'city': city})
else: else:

View File

@ -14,8 +14,7 @@ class DetermineSiteView(generics.GenericAPIView):
serializer_class = EmptySerializer serializer_class = EmptySerializer
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
user_ip = methods.get_user_ip(request) country_code = methods.determine_country_code(request)
country_code = methods.determine_country_code(user_ip)
url = methods.determine_user_site_url(country_code) url = methods.determine_user_site_url(country_code)
return Response(data={'url': url}) return Response(data={'url': url})

View File

@ -11,7 +11,7 @@ from news import models
from tag.serializers import TagBaseSerializer from tag.serializers import TagBaseSerializer
from utils import exceptions as utils_exceptions from utils import exceptions as utils_exceptions
from utils.serializers import (TranslatedField, ProjectModelSerializer, from utils.serializers import (TranslatedField, ProjectModelSerializer,
FavoritesCreateSerializer, ImageBaseSerializer) FavoritesCreateSerializer, ImageBaseSerializer, CarouselCreateSerializer)
class AgendaSerializer(ProjectModelSerializer): class AgendaSerializer(ProjectModelSerializer):
@ -269,3 +269,24 @@ class NewsFavoritesCreateSerializer(FavoritesCreateSerializer):
'content_object': validated_data.pop('news') 'content_object': validated_data.pop('news')
}) })
return super().create(validated_data) return super().create(validated_data)
class NewsCarouselCreateSerializer(CarouselCreateSerializer):
"""Serializer to carousel object w/ model News."""
def validate(self, attrs):
news = models.News.objects.filter(slug=self.slug).first()
if not news:
raise serializers.ValidationError({'detail': _('Object not found.')})
if news.carousels.exists():
raise utils_exceptions.CarouselError()
attrs['news'] = news
return attrs
def create(self, validated_data, *args, **kwargs):
validated_data.update({
'content_object': validated_data.pop('news')
})
return super().create(validated_data)

View File

@ -13,4 +13,6 @@ urlpatterns = [
name='gallery-list'), name='gallery-list'),
path('<int:pk>/gallery/<int:image_id>/', views.NewsBackOfficeGalleryCreateDestroyView.as_view(), path('<int:pk>/gallery/<int:image_id>/', views.NewsBackOfficeGalleryCreateDestroyView.as_view(),
name='gallery-create-destroy'), name='gallery-create-destroy'),
path('slug/<slug:slug>/carousels/', views.NewsCarouselCreateDestroyView.as_view(),
name='create-destroy-carousels'),
] ]

View File

@ -5,5 +5,8 @@ common_urlpatterns = [
path('', views.NewsListView.as_view(), name='list'), path('', views.NewsListView.as_view(), name='list'),
path('types/', views.NewsTypeListView.as_view(), name='type'), path('types/', views.NewsTypeListView.as_view(), name='type'),
path('slug/<slug:slug>/', views.NewsDetailView.as_view(), name='rud'), path('slug/<slug:slug>/', views.NewsDetailView.as_view(), name='rud'),
path('slug/<slug:slug>/favorites/', views.NewsFavoritesCreateDestroyView.as_view(), name='create-destroy-favorites') path('slug/<slug:slug>/favorites/', views.NewsFavoritesCreateDestroyView.as_view(),
name='create-destroy-favorites'),
path('slug/<slug:slug>/carousels/', views.NewsCarouselCreateDestroyView.as_view(),
name='create-destroy-carousels'),
] ]

View File

@ -6,7 +6,7 @@ from rest_framework import generics, permissions
from news import filters, models, serializers from news import filters, models, serializers
from rating.tasks import add_rating from rating.tasks import add_rating
from utils.permissions import IsCountryAdmin, IsContentPageManager from utils.permissions import IsCountryAdmin, IsContentPageManager
from utils.views import CreateDestroyGalleryViewMixin, FavoritesCreateDestroyMixinView from utils.views import CreateDestroyGalleryViewMixin, FavoritesCreateDestroyMixinView, CarouselCreateDestroyMixinView
from utils.serializers import ImageBaseSerializer from utils.serializers import ImageBaseSerializer
@ -155,3 +155,10 @@ class NewsFavoritesCreateDestroyView(FavoritesCreateDestroyMixinView):
_model = models.News _model = models.News
serializer_class = serializers.NewsFavoritesCreateSerializer serializer_class = serializers.NewsFavoritesCreateSerializer
class NewsCarouselCreateDestroyView(CarouselCreateDestroyMixinView):
"""View for create/destroy news from carousel."""
_model = models.News
serializer_class = serializers.NewsCarouselCreateSerializer

View File

@ -135,6 +135,14 @@ class FavoritesError(exceptions.APIException):
default_detail = _('Item is already in favorites.') default_detail = _('Item is already in favorites.')
class CarouselError(exceptions.APIException):
"""
The exception should be thrown when the object is already in carousels.
"""
status_code = status.HTTP_400_BAD_REQUEST
default_detail = _('Item is already in carousels.')
class PasswordResetRequestExistedError(exceptions.APIException): class PasswordResetRequestExistedError(exceptions.APIException):
""" """
The exception should be thrown when password reset request The exception should be thrown when password reset request

View File

@ -2,10 +2,11 @@
import pytz import pytz
from django.core import exceptions from django.core import exceptions
from rest_framework import serializers from rest_framework import serializers
from utils import models
from translation.models import Language
from favorites.models import Favorites from favorites.models import Favorites
from gallery.models import Image from main.models import Carousel
from translation.models import Language
from utils import models
class EmptySerializer(serializers.Serializer): class EmptySerializer(serializers.Serializer):
@ -80,7 +81,6 @@ class FavoritesCreateSerializer(serializers.ModelSerializer):
"""Serializer to favorite object.""" """Serializer to favorite object."""
class Meta: class Meta:
"""Serializer for model Comment."""
model = Favorites model = Favorites
fields = [ fields = [
'id', 'id',
@ -101,6 +101,24 @@ class FavoritesCreateSerializer(serializers.ModelSerializer):
return self.request.parser_context.get('kwargs').get('slug') return self.request.parser_context.get('kwargs').get('slug')
class CarouselCreateSerializer(serializers.ModelSerializer):
"""Carousel to favorite object."""
class Meta:
model = Carousel
fields = [
'id',
]
@property
def request(self):
return self.context.get('request')
@property
def slug(self):
return self.request.parser_context.get('kwargs').get('slug')
class RecursiveFieldSerializer(serializers.Serializer): class RecursiveFieldSerializer(serializers.Serializer):
def to_representation(self, value): def to_representation(self, value):
serializer = self.parent.parent.__class__(value, context=self.context) serializer = self.parent.parent.__class__(value, context=self.context)

View File

@ -170,9 +170,10 @@ class CarouselCreateDestroyMixinView(BaseCreateDestroyMixinView):
Returns the object the view is displaying. Returns the object the view is displaying.
""" """
obj = self.get_base_object() obj = self.get_base_object()
carousels = get_object_or_404(obj.carousels.filter(user=self.request.user)) carousels = get_object_or_404(obj.carousels.all())
# May raise a permission denied # May raise a permission denied
self.check_object_permissions(self.request, carousels) # TODO: возможно нужны пермишены
# self.check_object_permissions(self.request, carousels)
return carousels return carousels

View File

@ -1,23 +0,0 @@
#!/bin/bash
DB_CITY_URL="https://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz"
DB_COUNTRY_URL="https://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz"
DIR_PATH="geoip_db"
ARCH_PATH="archive"
mkdir -p $DIR_PATH
cd $DIR_PATH
mkdir -p $ARCH_PATH
find . -not -path "./$ARCH_PATH/*" -type f -name "*.mmdb" -exec mv -t "./$ARCH_PATH/" {} \+
filename=$(basename $DB_CITY_URL)
wget -O $filename $DB_CITY_URL
tar xzvf "$filename"
filename=$(basename $DB_COUNTRY_URL)
wget -O $filename $DB_COUNTRY_URL
tar xzvf "$filename"
find . -mindepth 1 -type f -name "*.mmdb" -not -path "./$ARCH_PATH/*" -exec mv -t . {} \+

View File

@ -487,7 +487,6 @@ LIMITING_QUERY_OBJECTS = QUERY_OUTPUT_OBJECTS * 3
# GEO # GEO
# A Spatial Reference System Identifier # A Spatial Reference System Identifier
GEO_DEFAULT_SRID = 4326 GEO_DEFAULT_SRID = 4326
GEOIP_PATH = os.path.join(PROJECT_ROOT, 'geoip_db')
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/ # https://docs.djangoproject.com/en/2.2/howto/static-files/

View File

@ -51,4 +51,6 @@ GUESTONLINE_SERVICE = 'https://api.guestonline.fr/'
GUESTONLINE_TOKEN = '' GUESTONLINE_TOKEN = ''
LASTABLE_SERVICE = '' LASTABLE_SERVICE = ''
LASTABLE_TOKEN = '' LASTABLE_TOKEN = ''
LASTABLE_PROXY = '' LASTABLE_PROXY = ''
THUMBNAIL_FORCE_OVERWRITE = True # see: https://github.com/jazzband/sorl-thumbnail/issues/351