Merge branch 'develop' into feature/fix-country-region-city-transfer

This commit is contained in:
Anatoly 2019-12-12 10:51:03 +03:00
commit 3dfd674aa4
18 changed files with 259 additions and 84 deletions

View File

@ -10,4 +10,5 @@ urlpatterns = [
path('user-role/', views.UserRoleLstView.as_view(), name='user-role-list-create'),
path('user/', views.UserLstView.as_view(), name='user-create-list'),
path('user/<int:id>/', views.UserRUDView.as_view(), name='user-rud'),
path('user/<int:id>/csv', views.get_user_csv, name='user-csv'),
]

View File

@ -1,6 +1,9 @@
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import generics, permissions
from rest_framework.filters import OrderingFilter
import csv
from django.http import HttpResponse, HttpResponseNotFound
from rest_framework.authtoken.models import Token
from account import models
from account.models import User
@ -46,3 +49,69 @@ class UserRUDView(generics.RetrieveUpdateDestroyAPIView):
serializer_class = serializers.BackDetailUserSerializer
permission_classes = (permissions.IsAdminUser,)
lookup_field = 'id'
def get_user_csv(request, id):
# fields = ["id", "uuid", "nickname", "locale", "country_code", "city", "role", "consent_purpose", "consent_at",
# "last_seen_at", "created_at", "updated_at", "email", "is_admin", "ezuser_id", "ez_user_id",
# "encrypted_password", "reset_password_token", "reset_password_sent_at", "remember_created_at",
# "sign_in_count", "current_sign_in_at", "last_sign_in_at", "current_sign_in_ip", "last_sign_in_ip",
# "confirmation_token", "confirmed_at", "confirmation_sent_at", "unconfirmed_email", "webpush_subscription"]
# uuid == id
#
# Не найдены:
# consent_purpose
# consent_at
# ezuser_id
# ez_user_id
# remember_created_at
# sign_in_count
# current_sign_in_at
# current_sign_in_ip
# last_sign_in_ip
# confirmed_at
# confirmation_sent_at
# webpush_subscription
#
# country_code не получить - клиент не привязан к стране
try:
user = User.objects.get(id=id)
except User.DoesNotExist:
return HttpResponseNotFound("User not found")
try:
roles = " ".join([role for role in user.roles])
except:
roles = ""
token, _ = Token.objects.get_or_create(user=user)
fields = {
"id": user.id,
"uuid": user.id,
"username": getattr(user, "username", ""),
"locale": getattr(user, "locale", ""),
"city": getattr(user, "city", ""),
"role": roles,
"created_at": getattr(user, "date_joined", ""),
"updated_at": user.last_login,
"email": user.email,
"is_admin": user.is_superuser,
"encrypted_password": user.password,
"reset_password_token": token.key,
"reset_password_sent_at": token.created, # TODO: не уверен в назначении поля, лучше проверить
"last_sign_in_at": user.last_login, # Повтор?
"confirmation_token": user.confirm_email_token,
"unconfirmed_email": 1 if user.unconfirmed_email else 0
}
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = f'attachment; filename="{user.email}.csv"'
writer = csv.writer(response)
writer.writerow(fields.keys())
writer.writerow(fields.values())
return response

View File

@ -0,0 +1,17 @@
# Generated by Django 2.2.7 on 2019-12-11 15:28
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('gallery', '0006_merge_20191027_1758'),
]
operations = [
migrations.AlterModelOptions(
name='image',
options={'ordering': ['-modified'], 'verbose_name': 'Image', 'verbose_name_plural': 'Images'},
),
]

View File

@ -34,6 +34,7 @@ class Image(ProjectBaseMixin, SORLImageMixin, PlatformMixin):
"""Meta class."""
verbose_name = _('Image')
verbose_name_plural = _('Images')
ordering = ['-modified']
def __str__(self):
"""String representation"""

View File

@ -1,8 +1,9 @@
from django.conf import settings
from django.core.validators import MinValueValidator, MaxValueValidator
from django.core.files.base import ContentFile
from rest_framework import serializers
from sorl.thumbnail.parsers import parse_crop
from sorl.thumbnail.parsers import ThumbnailParseError
from sorl.thumbnail import get_thumbnail
from sorl.thumbnail.parsers import parse_crop, ThumbnailParseError
from django.utils.translation import gettext_lazy as _
from . import models
@ -88,15 +89,23 @@ class CropImageSerializer(ImageSerializer):
quality = validated_data.pop('quality')
crop = validated_data.pop('crop')
crop_params = {
'geometry': f'{width}x{height}',
'quality': quality,
'crop': crop,
}
cropped_image = self._image.get_cropped_image(**crop_params)
image = self._image
image.pk = None
crop_params['geometry_string'] = crop_params.pop('geometry')
resized = get_thumbnail(self._image.image, **crop_params)
image.image.save(resized.name, ContentFile(resized.read()), True)
image.save()
if image and width and height:
setattr(image,
'cropped_image',
image.get_cropped_image(
geometry=f'{width}x{height}',
quality=quality,
crop=crop))
cropped_image)
return image
@property

View File

@ -10,6 +10,7 @@ urlpatterns = [
path('addresses/<int:pk>/', views.AddressRUDView.as_view(), name='address-RUD'),
path('cities/', views.CityListCreateView.as_view(), name='city-list-create'),
path('cities/all/', views.CityListSearchView.as_view(), name='city-list-create'),
path('cities/<int:pk>/', views.CityRUDView.as_view(), name='city-retrieve'),
path('cities/<int:pk>/gallery/', views.CityGalleryListView.as_view(),
name='gallery-list'),

View File

@ -37,6 +37,15 @@ class CityListCreateView(common.CityViewMixin, generics.ListCreateAPIView):
filter_class = filters.CityBackFilter
class CityListSearchView(common.CityViewMixin, generics.ListCreateAPIView):
"""Create view for model City."""
serializer_class = serializers.CitySerializer
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
queryset = models.City.objects.all()
filter_class = filters.CityBackFilter
pagination_class = None
class CityRUDView(common.CityViewMixin, generics.RetrieveUpdateDestroyAPIView):
"""RUD view for model City."""
serializer_class = serializers.CitySerializer

View File

@ -70,9 +70,6 @@ class CarouselListView(generics.ListAPIView):
def get_queryset(self):
country_code = self.request.country_code
if hasattr(settings, 'CAROUSEL_ITEMS') and country_code in settings.INTERNATIONAL_COUNTRY_CODES:
qs = models.Carousel.objects.filter(id__in=settings.CAROUSEL_ITEMS)
return qs
qs = models.Carousel.objects.is_parsed().active()
if country_code:
qs = qs.by_country_code(country_code)

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.7 on 2019-12-11 15:28
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('gallery', '0007_auto_20191211_1528'),
('news', '0040_remove_news_slug'),
]
operations = [
migrations.AlterUniqueTogether(
name='newsgallery',
unique_together={('news', 'image')},
),
]

View File

@ -320,4 +320,4 @@ class NewsGallery(IntermediateGalleryModelMixin):
"""NewsGallery meta class."""
verbose_name = _('news gallery')
verbose_name_plural = _('news galleries')
unique_together = (('news', 'is_main'), ('news', 'image'))
unique_together = [['news', 'image'],]

View File

@ -243,6 +243,16 @@ class NewsBackOfficeGallerySerializer(serializers.ModelSerializer):
"""Get url kwargs from request."""
return self.context.get('request').parser_context.get('kwargs')
def create(self, validated_data):
news_pk = self.get_request_kwargs().get('pk')
image_id = self.get_request_kwargs().get('image_id')
qs = models.NewsGallery.objects.filter(image_id=image_id, news_id=news_pk)
instance = qs.first()
if instance:
qs.update(**validated_data)
return instance
return super().create(validated_data)
def validate(self, attrs):
"""Override validate method."""
news_pk = self.get_request_kwargs().get('pk')
@ -259,8 +269,8 @@ class NewsBackOfficeGallerySerializer(serializers.ModelSerializer):
news = news_qs.first()
image = image_qs.first()
if image in news.gallery.all():
raise serializers.ValidationError({'detail': _('Image is already added.')})
# if image in news.gallery.all():
# raise serializers.ValidationError({'detail': _('Image is already added.')})
attrs['news'] = news
attrs['image'] = image

View File

@ -171,7 +171,7 @@ class Product(GalleryModelMixin, TranslatedFieldsMixin, BaseAttributes,
default=None, help_text='{"en-GB":"some text"}')
available = models.BooleanField(_('available'), default=True)
product_type = models.ForeignKey(ProductType, on_delete=models.PROTECT,
null=True,
null=True, blank=True, default=None,
related_name='products', verbose_name=_('Type'))
subtypes = models.ManyToManyField(ProductSubType, blank=True,
related_name='products',

View File

@ -5,7 +5,7 @@ from search_indexes.utils import OBJECT_FIELD_PROPERTIES
from product import models
ProductIndex = Index(settings.ELASTICSEARCH_INDEX_NAMES.get(__name__, 'product'))
ProductIndex.settings(number_of_shards=5, number_of_replicas=2, mapping={'total_fields':{'limit': 3000}})
ProductIndex.settings(number_of_shards=5, number_of_replicas=2, mapping={'total_fields': {'limit': 3000}})
@ProductIndex.doc_type
@ -14,12 +14,13 @@ class ProductDocument(Document):
description = fields.ObjectField(attr='description_indexing',
properties=OBJECT_FIELD_PROPERTIES)
product_type = fields.ObjectField(properties={
'id': fields.IntegerField(),
'name': fields.ObjectField(attr='name_indexing', properties=OBJECT_FIELD_PROPERTIES),
'index_name': fields.KeywordField(),
'use_subtypes': fields.BooleanField(),
})
product_type = fields.ObjectField(
properties={
'id': fields.IntegerField(),
'name': fields.ObjectField(attr='name_indexing', properties=OBJECT_FIELD_PROPERTIES),
'index_name': fields.KeywordField(),
},
)
subtypes = fields.ObjectField(
properties={
'id': fields.IntegerField(),
@ -54,15 +55,12 @@ class ProductDocument(Document):
),
'address': fields.ObjectField(
properties={
'city': fields.ObjectField(
properties={
'country': fields.ObjectField(
properties={
'code': fields.KeywordField()
}
)
}
)
'id': fields.IntegerField(),
'street_name_1': fields.TextField(
fields={'raw': fields.KeywordField()}
),
'postal_code': fields.KeywordField(),
'coordinates': fields.GeoPointField(attr='location_field_indexing'),
}
)
}
@ -155,7 +153,7 @@ class ProductDocument(Document):
name_ru = fields.TextField(attr='display_name', analyzer='russian')
name_fr = fields.TextField(attr='display_name', analyzer='french')
favorites_for_users = fields.ListField(field=fields.IntegerField())
created = fields.DateField(attr='created') # publishing date (?)
created = fields.DateField(attr='created') # publishing date (?)
class Django:
model = models.Product

View File

@ -40,6 +40,8 @@ class ProductSubtypeDocumentSerializer(serializers.Serializer):
name_translated = serializers.SerializerMethodField()
def get_name_translated(self, obj):
if isinstance(obj, dict):
return get_translated_value(obj.get('name'))
return get_translated_value(obj.name)
@ -93,7 +95,7 @@ class TagDocumentSerializer(serializers.Serializer):
return get_translated_value(obj.label)
class ProductTypeDocumentSerializer(serializers.Serializer):
class ProductTypeSerializer(serializers.Serializer):
"""Product type ES document serializer."""
id = serializers.IntegerField()
@ -102,8 +104,13 @@ class ProductTypeDocumentSerializer(serializers.Serializer):
@staticmethod
def get_name_translated(obj):
if isinstance(obj, dict):
return get_translated_value(obj.get('name'))
return get_translated_value(obj.name)
def get_attribute(self, instance):
return instance.product_type if instance and instance.product_type else None
class CityDocumentShortSerializer(serializers.Serializer):
"""City serializer for ES Document,"""
@ -114,7 +121,6 @@ class CityDocumentShortSerializer(serializers.Serializer):
class CountryDocumentSerializer(serializers.Serializer):
id = serializers.IntegerField()
code = serializers.CharField(allow_null=True)
svg_image = serializers.CharField()
@ -122,11 +128,12 @@ class CountryDocumentSerializer(serializers.Serializer):
@staticmethod
def get_name_translated(obj):
if isinstance(obj, dict):
return get_translated_value(obj.get('name'))
return get_translated_value(obj.name)
class AnotherCityDocumentShortSerializer(CityDocumentShortSerializer):
country = CountryDocumentSerializer()
def to_representation(self, instance):
@ -136,19 +143,6 @@ class AnotherCityDocumentShortSerializer(CityDocumentShortSerializer):
return None
class ProductEstablishmentDocumentSerializer(serializers.Serializer):
"""Related to Product Establishment ES document serializer."""
id = serializers.IntegerField()
name = serializers.CharField()
slug = serializers.CharField()
index_name = serializers.CharField()
city = AnotherCityDocumentShortSerializer()
def get_attribute(self, instance):
return instance.establishment if instance and instance.establishment else None
class AddressDocumentSerializer(serializers.Serializer):
"""Address serializer for ES Document."""
@ -171,6 +165,28 @@ class AddressDocumentSerializer(serializers.Serializer):
return None
class PSAddressDocumentSerializer(serializers.Serializer):
"""Address serializer for ES Document."""
id = serializers.IntegerField()
street_name_1 = serializers.CharField()
postal_code = serializers.CharField()
class ProductEstablishmentSerializer(serializers.Serializer):
"""Related to Product Establishment ES document serializer."""
id = serializers.IntegerField()
name = serializers.CharField()
slug = serializers.CharField()
index_name = serializers.CharField()
city = AnotherCityDocumentShortSerializer()
address = PSAddressDocumentSerializer(allow_null=True)
def get_attribute(self, instance):
return instance.establishment if instance and instance.establishment else None
class ScheduleDocumentSerializer(serializers.Serializer):
"""Schedule serializer for ES Document"""
@ -285,15 +301,15 @@ class EstablishmentDocumentSerializer(InFavoritesMixin, DocumentSerializer):
)
class ProductDocumentSerializer(InFavoritesMixin, DocumentSerializer):
class ProductDocumentSerializer(InFavoritesMixin):
"""Product document serializer"""
tags = TagsDocumentSerializer(many=True, source='related_tags')
subtypes = ProductSubtypeDocumentSerializer(many=True, allow_null=True)
wine_colors = TagDocumentSerializer(many=True)
grape_variety = TagDocumentSerializer(many=True)
product_type = ProductTypeDocumentSerializer(allow_null=True)
establishment_detail = ProductEstablishmentDocumentSerializer(source='establishment', allow_null=True)
product_type = ProductTypeSerializer(allow_null=True)
establishment_detail = ProductEstablishmentSerializer(source='establishment', allow_null=True)
wine_origins = WineOriginSerializer(many=True)
class Meta:
@ -317,7 +333,6 @@ class ProductDocumentSerializer(InFavoritesMixin, DocumentSerializer):
'subtypes',
'wine_colors',
'grape_variety',
'establishment_detail',
'average_price',
'created',
'wine_origins',

View File

@ -12,8 +12,8 @@ def update_document(sender, **kwargs):
instance = kwargs['instance']
app_label_model_name_to_filter = {
('location','country'): 'address__city__country',
('location','city'): 'address__city',
('location', 'country'): 'address__city__country',
('location', 'city'): 'address__city',
('location', 'address'): 'address',
# todo: remove after migration
('establishment', 'establishmenttype'): 'establishment_type',
@ -34,8 +34,8 @@ def update_news(sender, **kwargs):
model_name = sender._meta.model_name
instance = kwargs['instance']
app_label_model_name_to_filter = {
('location','country'): 'country',
('news','newstype'): 'news_type',
('location', 'country'): 'country',
('news', 'newstype'): 'news_type',
('tag', 'tag'): 'tags',
}
filter_name = app_label_model_name_to_filter.get((app_label, model_name))
@ -52,9 +52,9 @@ def update_product(sender, **kwargs):
model_name = sender._meta.model_name
instance = kwargs['instance']
app_label_model_name_to_filter = {
('product','productstandard'): 'standards',
('product', 'productstandard'): 'standards',
('product', 'producttype'): 'product_type',
('tag','tag'): 'tags',
('tag', 'tag'): 'tags',
('location', 'wineregion'): 'wine_region',
('location', 'winesubregion'): 'wine_sub_region',
('location', 'winevillage'): 'wine_village',

View File

@ -78,9 +78,30 @@ class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
result_list = serializer.data
query_params = request.query_params
params_type = query_params['type']
params_type = query_params.get('type')
if query_params.get('establishment_type'):
params_type = query_params.get('establishment_type')
elif query_params.get('product_type'):
params_type = query_params.get('product_type')
if params_type == 'restaurant' and 'toque_number__in' in query_params:
week_days = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")
flags = ('toque_number', 'wine_region', 'works_noon', 'works_evening', 'works_now', 'works_at_weekday')
filter_flags = {flag_name: False for flag_name in flags}
additional_flags = []
if params_type == 'restaurant':
additional_flags += ['toque_number', 'works_noon', 'works_evening', 'works_now']
elif params_type == 'winery':
additional_flags += ['wine_region']
elif params_type == 'artisan':
additional_flags += ['works_now', 'works_at_weekday']
for flag_name in additional_flags:
filter_flags[flag_name] = True
if filter_flags['toque_number']:
toques = {
"index_name": "toque_number",
"label_translated": "Toques",
@ -93,28 +114,29 @@ class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
}
result_list.append(toques)
if params_type == 'winery' and 'wine_region_id__in' in query_params:
try:
wine_region_id = int(query_params['wine_region_id__in'])
if filter_flags['wine_region']:
wine_region_id = query_params.get('wine_region_id__in')
wine_regions = {
"index_name": "wine_region",
"label_translated": "Wine region",
"param_name": "wine_region_id__in",
"filters": [{
"id": obj.id,
"index_name": obj.name.lower().replace(' ', '_'),
"label_translated": obj.name
} for obj in WineRegion.objects.filter(id=wine_region_id)]
}
if str(wine_region_id).isdigit():
queryset = WineRegion.objects.filter(id=int(wine_region_id))
result_list.append(wine_regions)
else:
queryset = WineRegion.objects.all()
except ValueError:
pass
wine_regions = {
"index_name": "wine_region",
"label_translated": "Wine region",
"param_name": "wine_region_id__in",
"filters": [{
"id": obj.id,
"index_name": obj.name.lower().replace(' ', '_'),
"label_translated": obj.name
} for obj in queryset]
}
if params_type == 'restaurant' and 'works_noon__in' in query_params:
week_days = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")
result_list.append(wine_regions)
if filter_flags['works_noon']:
works_noon = {
"index_name": "works_noon",
"label_translated": "Open noon",
@ -127,8 +149,8 @@ class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
}
result_list.append(works_noon)
if params_type == 'restaurant' and 'works_evening__in' in query_params:
week_days = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")
if filter_flags['works_evening']:
works_evening = {
"index_name": "works_evening",
"label_translated": "Open evening",
@ -141,7 +163,7 @@ class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
}
result_list.append(works_evening)
if params_type in ('restaurant', 'artisan') and 'works_now' in query_params:
if filter_flags['works_now']:
works_now = {
"index_name": "open_now",
"label_translated": "Open now",
@ -150,6 +172,19 @@ class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
}
result_list.append(works_now)
if filter_flags['works_at_weekday']:
works_at_weekday = {
"index_name": "works_at_weekday",
"label_translated": "Works at weekday",
"param_name": "works_at_weekday__in",
"filters": [{
"id": weekday,
"index_name": week_days[weekday].lower(),
"label_translated": week_days[weekday]
} for weekday in range(7)]
}
result_list.append(works_at_weekday)
if 'tags_id__in' in query_params:
# filtering by params_type and tags id
# todo: result_list.append( filtering_data )

View File

@ -16,8 +16,6 @@ services:
- .:/code
# PostgreSQL database
db:
build:

View File

@ -516,9 +516,6 @@ PHONENUMBER_DEFAULT_REGION = "FR"
FALLBACK_LOCALE = 'en-GB'
# TMP TODO remove it later
# Временный хардкод для демонстрации > 15 ноября, потом удалить!
CAROUSEL_ITEMS = [465]
ESTABLISHMENT_CHOSEN_TAGS = ['gastronomic', 'en_vogue', 'terrace', 'streetfood', 'business', 'bar_cocktail', 'brunch', 'pop']
NEWS_CHOSEN_TAGS = ['eat', 'drink', 'cook', 'style', 'international', 'event', 'partnership']
INTERNATIONAL_COUNTRY_CODES = ['www', 'main', 'next']