Merge branch 'develop' into fix/user-role-err

This commit is contained in:
Виктор Гладких 2019-12-03 15:36:41 +03:00
commit 933f21cdaa
21 changed files with 372 additions and 51 deletions

113
.dockerignore Normal file
View File

@ -0,0 +1,113 @@
# Created by .ignore support plugin (hsz.mobi)
### Python template
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
_*/
.git/
.idea/
_files/

View File

@ -1,8 +1,9 @@
FROM python:3.7
ENV PYTHONUNBUFFERED 1
RUN apt-get update; apt-get --assume-yes install binutils libproj-dev gdal-bin gettext
RUN mkdir /code
RUN mkdir /code /requirements
ADD ./requirements /requirements
RUN pip install --no-cache-dir -r /requirements/base.txt && \
pip install --no-cache-dir -r /requirements/development.txt
WORKDIR /code
ADD . /code/
RUN pip install --no-cache-dir -r /code/requirements/base.txt && \
pip install --no-cache-dir -r /code/requirements/development.txt
ADD . /code/

View File

@ -92,7 +92,11 @@ class UserBaseSerializer(serializers.ModelSerializer):
model = models.User
fields = (
'id',
'fullname',
'first_name',
'last_name',
'email',
'cropped_image_url',
'image_url',
)

View File

@ -0,0 +1,36 @@
from django.core.management.base import BaseCommand
from tqdm import tqdm
from establishment.models import Establishment
from transfer.models import Establishments
from transfer.serializers.establishment import EstablishmentSerializer
from timetable.models import Timetable
from django.db import transaction
class Command(BaseCommand):
help = 'Fix scheduler'
@transaction.atomic
def handle(self, *args, **kwargs):
count = 0
establishments = Establishment.objects.all()
old_est_list = Establishments.objects.prefetch_related(
'schedules_set',
)
# remove old records of Timetable
Timetable.objects.all().delete()
for est in tqdm(establishments, desc="Fix scheduler"):
old_est = old_est_list.filter(id=est.old_id).first()
if old_est and old_est.schedules_set.exists():
old_schedule = old_est.schedules_set.first()
timetable = old_schedule.timetable
if timetable:
new_schedules = EstablishmentSerializer.get_schedules(timetable)
est.schedule.add(*new_schedules)
est.save()
count += 1
self.stdout.write(self.style.WARNING(f'Update {count} objects.'))

View File

@ -14,7 +14,7 @@ from django.contrib.postgres.fields import ArrayField
from django.core.exceptions import ValidationError
from django.core.validators import MinValueValidator, MaxValueValidator
from django.db import models
from django.db.models import When, Case, F, ExpressionWrapper, Subquery, Q
from django.db.models import When, Case, F, ExpressionWrapper, Subquery, Q, Prefetch
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from phonenumber_field.modelfields import PhoneNumberField
@ -23,6 +23,7 @@ from timezone_field import TimeZoneField
from collection.models import Collection
from location.models import Address
from main.models import Award, Currency
from tag.models import Tag
from review.models import Review
from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin,
TranslatedFieldsMixin, BaseAttributes, GalleryModelMixin,
@ -321,6 +322,13 @@ class EstablishmentQuerySet(models.QuerySet):
"""Exclude countries."""
return self.exclude(address__city__country__in=countries)
def with_certain_tag_category_related(self, index_name, attr_name):
"""Includes extra tags."""
return self.prefetch_related(
Prefetch('tags', queryset=Tag.objects.filter(category__index_name=index_name),
to_attr=attr_name)
)
class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin,
TranslatedFieldsMixin, HasTagsMixin, FavoritesMixin):
@ -541,6 +549,11 @@ class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin,
time_at_est_tz = now_at_est_tz.time()
return schedule_for_today.ending_time > time_at_est_tz > schedule_for_today.opening_time
@property
def timezone_as_str(self):
""" Returns tz in str format"""
return self.tz.localize(datetime.now()).strftime('%z')
@property
def tags_indexing(self):
return [{'id': tag.metadata.id,
@ -589,6 +602,18 @@ class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin,
if qs.exists():
return qs.first().image
@property
def restaurant_category_indexing(self):
return self.tags.filter(category__index_name='category')
@property
def restaurant_cuisine_indexing(self):
return self.tags.filter(category__index_name='cuisine')
@property
def artisan_category_indexing(self):
return self.tags.filter(category__index_name='shop_category')
class EstablishmentNoteQuerySet(models.QuerySet):
"""QuerySet for model EstablishmentNote."""

View File

@ -286,7 +286,7 @@ class EstablishmentBaseSerializer(ProjectModelSerializer):
preview_image = serializers.URLField(source='preview_image_url',
allow_null=True,
read_only=True)
tz = serializers.CharField(read_only=True, source='timezone_as_str')
new_image = ImageBaseSerializer(source='crop_main_image', allow_null=True, read_only=True)
class Meta:
@ -311,6 +311,7 @@ class EstablishmentBaseSerializer(ProjectModelSerializer):
'image',
'preview_image',
'new_image',
'tz',
]
@ -319,12 +320,18 @@ class EstablishmentListRetrieveSerializer(EstablishmentBaseSerializer):
address = AddressDetailSerializer()
schedule = ScheduleRUDSerializer(many=True, allow_null=True)
restaurant_category = TagBaseSerializer(read_only=True, many=True, allow_null=True)
restaurant_cuisine = TagBaseSerializer(read_only=True, many=True, allow_null=True)
artisan_category = TagBaseSerializer(read_only=True, many=True, allow_null=True)
class Meta(EstablishmentBaseSerializer.Meta):
"""Meta class."""
fields = EstablishmentBaseSerializer.Meta.fields + [
'schedule',
'restaurant_category',
'restaurant_cuisine',
'artisan_category',
]

View File

@ -35,7 +35,10 @@ class EstablishmentListView(EstablishmentMixinView, generics.ListAPIView):
def get_queryset(self):
return super().get_queryset().with_schedule() \
.with_extended_address_related().with_currency_related()
.with_extended_address_related().with_currency_related() \
.with_certain_tag_category_related('category', 'restaurant_category') \
.with_certain_tag_category_related('cuisine', 'restaurant_cuisine') \
.with_certain_tag_category_related('shop_category', 'artisan_category')
class EstablishmentRetrieveView(EstablishmentMixinView, generics.RetrieveAPIView):

View File

@ -21,27 +21,6 @@ class FeatureSerializer(serializers.ModelSerializer):
)
class SiteFeatureSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(source='feature.id')
slug = serializers.CharField(source='feature.slug')
priority = serializers.IntegerField(source='feature.priority')
route = serializers.CharField(source='feature.route.name')
source = serializers.IntegerField(source='feature.source')
nested = RecursiveFieldSerializer(many=True, allow_null=True)
class Meta:
"""Meta class."""
model = models.SiteFeature
fields = ('main',
'id',
'slug',
'priority',
'route',
'source',
'nested',
)
class CurrencySerializer(ProjectModelSerializer):
"""Currency serializer."""
@ -56,6 +35,23 @@ class CurrencySerializer(ProjectModelSerializer):
]
class SiteFeatureSerializer(serializers.ModelSerializer):
"""Site feature serializer."""
class Meta:
"""Meta class."""
model = models.SiteFeature
fields = (
'id',
'site_settings',
'feature',
'published',
'main',
'nested'
)
class SiteSettingsSerializer(serializers.ModelSerializer):
"""Site settings serializer."""
@ -108,7 +104,14 @@ class SiteSerializer(serializers.ModelSerializer):
fields = [
'subdomain',
'site_url',
'country'
'country',
'default_site',
'pinterest_page_url',
'twitter_page_url',
'facebook_page_url',
'instagram_page_url',
'contact_email',
'currency'
]
@ -132,19 +135,20 @@ class SiteBackOfficeSerializer(SiteSerializer):
]
# class SiteFeatureSerializer(serializers.ModelSerializer):
# """Site feature serializer."""
#
# class Meta:
# """Meta class."""
#
# model = models.SiteFeature
# fields = (
# 'id',
# 'published',
# 'site_settings',
# 'feature',
# )
class FeatureSerializer(serializers.ModelSerializer):
"""Feature serializer."""
class Meta:
"""Meta class."""
model = models.Feature
fields = (
'id',
'slug',
'priority',
'route',
'site_settings',
)
class AwardBaseSerializer(serializers.ModelSerializer):

View File

@ -9,7 +9,15 @@ urlpatterns = [
path('awards/', views.AwardLstView.as_view(), name='awards-list-create'),
path('awards/<int:id>/', views.AwardRUDView.as_view(), name='awards-rud'),
path('content_type/', views.ContentTypeView.as_view(), name='content_type-list'),
path('sites/', views.SiteListBackOfficeView.as_view(), name='site-list'),
path('sites/', views.SiteListBackOfficeView.as_view(), name='site-list-create'),
path('site-settings/<subdomain>/', views.SiteSettingsBackOfficeView.as_view(),
name='site-settings'),
path('feature/', views.FeatureBackView.as_view(), name='feature-list-create'),
path('feature/<int:id>/', views.FeatureRUDBackView.as_view(), name='feature-rud'),
path('site-feature/', views.SiteFeatureBackView.as_view(),
name='site-feature-list-create'),
path('site-feature/<int:id>/', views.SiteFeatureRUDBackView.as_view(),
name='site-feature-rud'),
]

View File

@ -39,6 +39,26 @@ class ContentTypeView(generics.ListAPIView):
)
class FeatureBackView(generics.ListCreateAPIView):
"""Feature list or create View."""
serializer_class = serializers.FeatureSerializer
class SiteFeatureBackView(generics.ListCreateAPIView):
"""Feature list or create View."""
serializer_class = serializers.SiteFeatureSerializer
class FeatureRUDBackView(generics.RetrieveUpdateDestroyAPIView):
"""Feature RUD View."""
serializer_class = serializers.FeatureSerializer
class SiteFeatureRUDBackView(generics.RetrieveUpdateDestroyAPIView):
"""Feature RUD View."""
serializer_class = serializers.SiteFeatureSerializer
class SiteSettingsBackOfficeView(SiteSettingsView):
"""Site settings View."""
serializer_class = serializers.SiteSettingsBackOfficeSerializer

View File

@ -19,7 +19,7 @@ class DetermineSiteView(generics.GenericAPIView):
return Response(data={'url': url})
class SiteSettingsView(generics.RetrieveAPIView):
class SiteSettingsView(generics.RetrieveUpdateDestroyAPIView):
"""Site settings View."""
lookup_field = 'subdomain'
@ -28,7 +28,7 @@ class SiteSettingsView(generics.RetrieveAPIView):
serializer_class = serializers.SiteSettingsBackOfficeSerializer
class SiteListView(generics.ListAPIView):
class SiteListView(generics.ListCreateAPIView):
"""Site settings View."""
pagination_class = None

View File

@ -0,0 +1,29 @@
from django.core.management.base import BaseCommand
from django.db.models import F
from tqdm import tqdm
from account.models import User
from news.models import News
from transfer.models import PageTexts
class Command(BaseCommand):
help = 'Add author of News'
def handle(self, *args, **kwargs):
count = 0
news_list = News.objects.filter(created_by__isnull=True)
for news in tqdm(news_list, desc="Find author for exist news"):
old_news = PageTexts.objects.filter(id=news.old_id).annotate(
account_id=F('page__account_id'),
).first()
if old_news:
user = User.objects.filter(old_id=old_news.account_id).first()
if user:
news.created_by = user
news.modified_by = user
news.save()
count += 1
self.stdout.write(self.style.WARNING(f'Update {count} objects.'))

View File

@ -38,6 +38,7 @@ def transfer_news():
image=F('page__attachment_suffix_url'),
template=F('page__template'),
tags=GroupConcat('page__tags__id'),
account_id=F('page__account_id'),
)
serialized_data = NewsSerializer(data=list(queryset.values()), many=True)

View File

@ -49,6 +49,30 @@ class EstablishmentDocument(Document):
'value': fields.KeywordField(),
},
multi=True)
restaurant_category = fields.ObjectField(
properties={
'id': fields.IntegerField(attr='id'),
'label': fields.ObjectField(attr='label_indexing',
properties=OBJECT_FIELD_PROPERTIES),
'value': fields.KeywordField(),
},
multi=True, attr='restaurant_category_indexing')
restaurant_cuisine = fields.ObjectField(
properties={
'id': fields.IntegerField(attr='id'),
'label': fields.ObjectField(attr='label_indexing',
properties=OBJECT_FIELD_PROPERTIES),
'value': fields.KeywordField(),
},
multi=True, attr='restaurant_cuisine_indexing')
artisan_category = fields.ObjectField(
properties={
'id': fields.IntegerField(attr='id'),
'label': fields.ObjectField(attr='label_indexing',
properties=OBJECT_FIELD_PROPERTIES),
'value': fields.KeywordField(),
},
multi=True, attr='artisan_category_indexing')
visible_tags = fields.ObjectField(
properties={
'id': fields.IntegerField(attr='id'),
@ -124,6 +148,7 @@ class EstablishmentDocument(Document):
},
)
favorites_for_users = fields.ListField(field=fields.IntegerField())
tz = fields.KeywordField(attr='timezone_as_str')
class Django:

View File

@ -10,14 +10,37 @@ class CustomGeoSpatialFilteringFilterBackend(GeoSpatialFilteringFilterBackend):
"""Automatically adds centering and sorting within bounding box."""
@staticmethod
def calculate_center(a, b):
return (a[0] + b[0]) / 2, (a[1] + b[1]) / 2
def calculate_center(first, second):
if second[1] < 0 < first[1]:
reverse_first, reverse_second = 180 - abs(first[1]), 180 - abs(second[1])
diff = (reverse_first + reverse_second) / 2
if reverse_first < reverse_second:
result_part = -180 + (180 + second[1] - diff)
else:
result_part = 180 - (180 - first[1] - diff)
elif second[1] < 0 > first[1]:
diff = abs(abs(second[1]) - abs(first[1]))
if diff > 90:
reverse_first, reverse_second = 180 - abs(first[1]), 180 - abs(second[1])
result_part = (reverse_first + reverse_second) / 2
else:
result_part = (first[1] + second[1]) / 2
else:
result_part = (first[1] + second[1]) / 2
return (first[0] + second[0]) / 2, result_part
def filter_queryset(self, request, queryset, view):
ret = super().filter_queryset(request, queryset, view)
bb = request.query_params.get('location__geo_bounding_box')
if bb:
center = self.calculate_center(*map(lambda p: list(map(lambda x: float(x),p.split(','))), bb.split('__')))
center = self.calculate_center(*map(lambda point: list(map(float, point.split(','))), bb.split('__')))
request.GET._mutable = True
request.query_params.update({
'ordering': f'location__{center[0]}__{center[1]}__km'

View File

@ -229,6 +229,9 @@ class EstablishmentDocumentSerializer(InFavoritesMixin, DocumentSerializer):
establishment_subtypes = EstablishmentTypeSerializer(many=True)
address = AddressDocumentSerializer(allow_null=True)
tags = TagsDocumentSerializer(many=True, source='visible_tags')
restaurant_category = TagsDocumentSerializer(many=True, allow_null=True)
restaurant_cuisine = TagsDocumentSerializer(many=True, allow_null=True)
artisan_category = TagsDocumentSerializer(many=True, allow_null=True)
schedule = ScheduleDocumentSerializer(many=True, allow_null=True)
class Meta:
@ -247,10 +250,14 @@ class EstablishmentDocumentSerializer(InFavoritesMixin, DocumentSerializer):
'preview_image',
'address',
'tags',
'restaurant_category',
'restaurant_cuisine',
'artisan_category',
'schedule',
'works_noon',
'works_evening',
'works_at_weekday',
'tz',
# 'works_now',
# 'collections',
# 'establishment_type',

View File

@ -73,7 +73,10 @@ class TagsFilterSet(TagsBaseFilterSet):
def by_establishment_type(self, queryset, name, value):
if value == EstablishmentType.ARTISAN:
return models.Tag.objects.by_category_index_name('shop_category')[0:8]
qs = models.Tag.objects.by_category_index_name('shop_category')
if self.request.country_code and self.request.country_code not in settings.INTERNATIONAL_COUNTRY_CODES:
qs = qs.filter(establishments__address__city__country__code=self.request.country_code).distinct('id')
return qs.exclude(establishments__isnull=True)[0:8]
return queryset.by_establishment_type(value)
# TMP TODO remove it later

View File

@ -125,7 +125,7 @@ class EstablishmentSerializer(serializers.ModelSerializer):
weekdays = {
'su': Timetable.SUNDAY,
'mo': Timetable.MONDAY,
'tu': Timetable.THURSDAY,
'tu': Timetable.TUESDAY,
'we': Timetable.WEDNESDAY,
'th': Timetable.THURSDAY,
'fr': Timetable.FRIDAY,

View File

@ -7,10 +7,12 @@ from tag.models import Tag
from transfer.models import PageMetadata
from utils.legacy_parser import parse_legacy_news_content
from utils.slug_generator import generate_unique_slug
from account.models import User
class NewsSerializer(serializers.Serializer):
id = serializers.IntegerField()
account_id = serializers.IntegerField(allow_null=True)
tag_cat_id = serializers.IntegerField()
news_type_id = serializers.IntegerField()
news_title = serializers.CharField()
@ -39,6 +41,8 @@ class NewsSerializer(serializers.Serializer):
'state': self.get_state(validated_data),
'template': self.get_template(validated_data),
'country': self.get_country(validated_data),
'created_by': self.get_account(validated_data),
'modified_by': self.get_account(validated_data),
}
obj = News.objects.create(**payload)
@ -126,3 +130,8 @@ class NewsSerializer(serializers.Serializer):
else:
content = {data['locale']: data['title']}
return content
@staticmethod
def get_account(data):
"""Get account"""
return User.objects.filter(old_id=data['account_id']).first()

View File

@ -5,7 +5,7 @@ services:
mysql_db:
image: mysql:5.7
ports:
- "3306:3306"
- "3316:3306"
environment:
MYSQL_DATABASE: dev
MYSQL_USER: dev

View File

@ -99,3 +99,6 @@ TESTING = sys.argv[1:2] == ['test']
if TESTING:
ELASTICSEARCH_INDEX_NAMES = {}
ELASTICSEARCH_DSL_AUTOSYNC = False
# INSTALLED APPS
INSTALLED_APPS.append('transfer.apps.TransferConfig')