Merge branch 'develop' into migrate/site-affilations

This commit is contained in:
Виктор Гладких 2019-12-04 12:25:32 +03:00
commit e7deab5bbc
19 changed files with 234 additions and 108 deletions

View File

@ -293,7 +293,9 @@ class User(AbstractUser):
class UserRole(ProjectBaseMixin):
"""UserRole model."""
user = models.ForeignKey(User, verbose_name=_('User'), on_delete=models.CASCADE)
user = models.ForeignKey('account.User',
verbose_name=_('User'),
on_delete=models.CASCADE)
role = models.ForeignKey(Role, verbose_name=_('Role'), on_delete=models.SET_NULL, null=True)
establishment = models.ForeignKey(Establishment, verbose_name=_('Establishment'),
on_delete=models.SET_NULL, null=True, blank=True)

View File

@ -13,15 +13,6 @@ class RoleSerializer(serializers.ModelSerializer):
]
class UserRoleSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserRole
fields = [
'user',
'role'
]
class BackUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
@ -49,3 +40,13 @@ class BackDetailUserSerializer(BackUserSerializer):
user.set_password(validated_data['password'])
user.save()
return user
class UserRoleSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserRole
fields = [
'role',
'user',
'establishment'
]

View File

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

View File

@ -13,7 +13,7 @@ class RoleLstView(generics.ListCreateAPIView):
class UserRoleLstView(generics.ListCreateAPIView):
serializer_class = serializers.UserRoleSerializer
queryset = models.Role.objects.all()
queryset = models.UserRole.objects.all()
class UserLstView(generics.ListCreateAPIView):

View File

@ -34,7 +34,7 @@ class CheckWhetherBookingAvailable(generics.GenericAPIView):
periods = response['periods']
periods_by_name = {period['period']: period for period in periods if 'period' in period}
if not periods_by_name:
return None
return response
period_template = iter(periods_by_name.values()).__next__().copy()
period_template.pop('total_left_seats')

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

@ -38,7 +38,7 @@ class EstablishmentListView(EstablishmentMixinView, generics.ListAPIView):
.with_extended_address_related().with_currency_related() \
.with_certain_tag_category_related('category', 'restaurant_category') \
.with_certain_tag_category_related('cuisine', 'restaurant_cuisine') \
.with_ceratin_tag_category_related('shop_category', 'artisan_category')
.with_certain_tag_category_related('shop_category', 'artisan_category')
class EstablishmentRetrieveView(EstablishmentMixinView, generics.RetrieveAPIView):

View File

@ -17,9 +17,24 @@ class FeatureSerializer(serializers.ModelSerializer):
fields = (
'id',
'slug',
'priority'
'priority',
'route',
'site_settings',
)
class CurrencySerializer(ProjectModelSerializer):
"""Currency serializer."""
name_translated = TranslatedField()
class Meta:
model = models.Currency
fields = [
'id',
'name_translated',
'sign'
]
class SiteFeatureSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(source='feature.id')
@ -42,20 +57,6 @@ class SiteFeatureSerializer(serializers.ModelSerializer):
)
class CurrencySerializer(ProjectModelSerializer):
"""Currency serializer."""
name_translated = TranslatedField()
class Meta:
model = models.Currency
fields = [
'id',
'name_translated',
'sign'
]
class SiteSettingsSerializer(serializers.ModelSerializer):
"""Site settings serializer."""
@ -99,23 +100,15 @@ class SiteSettingsBackOfficeSerializer(SiteSettingsSerializer):
]
class SiteSerializer(serializers.ModelSerializer):
class SiteSerializer(SiteSettingsSerializer):
country = CountrySerializer()
class Meta:
"""Meta class."""
model = models.SiteSettings
fields = [
'subdomain',
'site_url',
'country',
'default_site',
'pinterest_page_url',
'twitter_page_url',
'facebook_page_url',
'instagram_page_url',
'contact_email',
'currency'
fields = SiteSettingsSerializer.Meta.fields + [
'id',
'country'
]
@ -129,45 +122,6 @@ class SiteShortSerializer(serializers.ModelSerializer):
]
class SiteBackOfficeSerializer(SiteSerializer):
"""Serializer for back office."""
class Meta(SiteSerializer.Meta):
"""Meta class."""
fields = SiteSerializer.Meta.fields + [
'id',
]
class FeatureSerializer(serializers.ModelSerializer):
"""Site feature serializer."""
class Meta:
"""Meta class."""
model = models.Feature
fields = (
'id',
'slug',
'priority',
'route',
'site_settings',
)
# class SiteFeatureSerializer(serializers.ModelSerializer):
# """Site feature serializer."""
#
# class Meta:
# """Meta class."""
#
# model = models.SiteFeature
# fields = (
# 'id',
# 'published',
# 'site_settings',
# 'feature',
# )
class AwardBaseSerializer(serializers.ModelSerializer):

View File

@ -13,5 +13,11 @@ urlpatterns = [
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('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

@ -44,16 +44,26 @@ class FeatureBackView(generics.ListCreateAPIView):
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
serializer_class = serializers.SiteSerializer
class SiteListBackOfficeView(SiteListView):
"""Site settings View."""
serializer_class = serializers.SiteBackOfficeSerializer
serializer_class = serializers.SiteSerializer

View File

@ -1,6 +1,7 @@
from search_indexes.documents.establishment import EstablishmentDocument
from search_indexes.documents.news import NewsDocument
from search_indexes.documents.product import ProductDocument
from search_indexes.documents.tag_category import TagCategoryDocument
from search_indexes.tasks import es_update
# todo: make signal to update documents on related fields
@ -8,5 +9,6 @@ __all__ = [
'EstablishmentDocument',
'NewsDocument',
'ProductDocument',
'TagCategoryDocument',
'es_update',
]

View File

@ -0,0 +1,33 @@
"""Product app documents."""
from django.conf import settings
from django_elasticsearch_dsl import Document, Index, fields
from tag import models
TagCategoryIndex = Index(settings.ELASTICSEARCH_INDEX_NAMES.get(__name__, 'tag_category'))
TagCategoryIndex.settings(number_of_shards=2, number_of_replicas=2)
@TagCategoryIndex.doc_type
class TagCategoryDocument(Document):
"""TagCategory document."""
tags = fields.ListField(fields.ObjectField(
properties={
'id': fields.IntegerField(),
'value': fields.KeywordField(),
},
))
class Django:
model = models.TagCategory
fields = (
'id',
'index_name',
'public',
'value_type'
)
related_models = [models.Tag]
def get_queryset(self):
return super().get_queryset().with_base_related()

View File

@ -4,6 +4,8 @@ from django_elasticsearch_dsl_drf.filter_backends import SearchFilterBackend, \
FacetedSearchFilterBackend, GeoSpatialFilteringFilterBackend
from search_indexes.utils import OBJECT_FIELD_PROPERTIES
from six import iteritems
from search_indexes.documents import TagCategoryDocument
from tag.models import TagCategory
class CustomGeoSpatialFilteringFilterBackend(GeoSpatialFilteringFilterBackend):
@ -11,7 +13,7 @@ class CustomGeoSpatialFilteringFilterBackend(GeoSpatialFilteringFilterBackend):
@staticmethod
def calculate_center(first, second):
if second[1] < 0 <= first[1]:
if second[1] < 0 < first[1]:
reverse_first, reverse_second = 180 - abs(first[1]), 180 - abs(second[1])
diff = (reverse_first + reverse_second) / 2
@ -21,6 +23,10 @@ class CustomGeoSpatialFilteringFilterBackend(GeoSpatialFilteringFilterBackend):
else:
result_part = 180 - (180 - first[1] - diff)
elif second[1] < 0 > first[1] or second[1] > 0 < first[1]:
reverse_first, reverse_second = 180 - abs(first[1]), 180 - abs(second[1])
result_part = ((reverse_first + reverse_second) / 2) * (-1 + (second[1] < 0) * 2)
else:
result_part = (first[1] + second[1]) / 2
@ -52,10 +58,30 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend):
:param view:
:return:
"""
def makefilter(cur_facet):
def myfilter(x):
def make_filter(cur_facet):
def _filter(x):
return cur_facet['facet']._params['field'] != next(iter(x._params))
return myfilter
return _filter
def make_tags_filter(cur_facet, tags_to_remove_ids):
def _filter(x):
if hasattr(x, '_params') and (x._params.get('must') or x._params.get('should')):
ret = []
for t in ['must', 'should']:
terms = x._params.get(t)
if terms:
for term in terms:
if cur_facet['facet']._params['field'] != next(iter(term._params)):
return True # different fields. preserve filter
else:
ret.append(next(iter(term._params.values())) not in tags_to_remove_ids)
return all(ret)
if cur_facet['facet']._params['field'] != next(iter(x._params)):
return True # different fields. preserve filter
else:
return next(iter(x._params.values())) not in tags_to_remove_ids
return _filter
__facets = self.construct_facets(request, view)
setattr(view.paginator, 'facets_computed', {})
for __field, __facet in iteritems(__facets):
@ -67,9 +93,10 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend):
'global'
).bucket(__field, agg)
else:
if __field != 'tag':
qs = queryset.__copy__()
qs.query = queryset.query._clone()
filterer = makefilter(__facet)
filterer = make_filter(__facet)
for param_type in ['must', 'must_not', 'should']:
if qs.query._proxied._params.get(param_type):
qs.query._proxied._params[param_type] = list(
@ -88,8 +115,51 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend):
filter=agg_filter
).bucket(__field, agg)
view.paginator.facets_computed.update({facet_name: qs.execute().aggregations[facet_name]})
else:
tag_facets = []
preserve_ids = []
facet_name = '_filter_' + __field
all_tag_categories = TagCategoryDocument.search() \
.filter('term', public=True) \
.filter(Q('term', value_type=TagCategory.LIST) | Q('match', index_name='wine-color'))
for category in all_tag_categories:
tags_to_remove = list(map(lambda t: str(t.id), category.tags))
qs = queryset.__copy__()
qs.query = queryset.query._clone()
filterer = make_tags_filter(__facet, tags_to_remove)
for param_type in ['must', 'should']:
if qs.query._proxied._params.get(param_type):
if qs.query._proxied._params.get(param_type):
qs.query._proxied._params[param_type] = list(
filter(
filterer, qs.query._proxied._params[param_type]
)
)
sh = qs.query._proxied._params.get('should')
if (not sh or not len(sh)) \
and qs.query._proxied._params.get('minimum_should_match'):
qs.query._proxied._params.pop('minimum_should_match')
qs.aggs.bucket(
facet_name,
'filter',
filter=agg_filter
).bucket(__field, agg)
tag_facets.append(qs.execute().aggregations[facet_name])
preserve_ids.append(list(map(int, tags_to_remove)))
view.paginator.facets_computed.update({facet_name: self.merge_buckets(tag_facets, preserve_ids)})
return queryset
@staticmethod
def merge_buckets(buckets: list, preserve_ids: list):
"""Reduces all buckets preserving class"""
result_bucket = buckets[0]
result_bucket.tag.buckets = list(filter(lambda x: x['key'] in preserve_ids[0], result_bucket.tag.buckets._l_))
for bucket, ids in list(zip(buckets, preserve_ids))[1:]:
for tag in bucket.tag.buckets._l_:
if tag['key'] in ids:
result_bucket.tag.buckets.append(tag)
return result_bucket
class CustomSearchFilterBackend(SearchFilterBackend):
"""Custom SearchFilterBackend."""

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

@ -42,6 +42,7 @@ ELASTICSEARCH_INDEX_NAMES = {
'search_indexes.documents.news': 'development_news',
'search_indexes.documents.establishment': 'development_establishment',
'search_indexes.documents.product': 'development_product',
'search_indexes.documents.tag_category': 'development_tag_category',
}
# ELASTICSEARCH_DSL_AUTOSYNC = False

View File

@ -92,6 +92,7 @@ ELASTICSEARCH_INDEX_NAMES = {
# 'search_indexes.documents.news': 'local_news',
'search_indexes.documents.establishment': 'local_establishment',
'search_indexes.documents.product': 'local_product',
'search_indexes.documents.tag_category': 'local_tag_category',
}
ELASTICSEARCH_DSL_AUTOSYNC = False

View File

@ -36,6 +36,7 @@ ELASTICSEARCH_INDEX_NAMES = {
'search_indexes.documents.news': 'development_news', # temporarily disabled
'search_indexes.documents.establishment': 'development_establishment',
'search_indexes.documents.product': 'development_product',
'search_indexes.documents.tag_category': 'development_tag_category',
}
sentry_sdk.init(

View File

@ -23,6 +23,7 @@ ELASTICSEARCH_DSL = {
ELASTICSEARCH_INDEX_NAMES = {
# 'search_indexes.documents.news': 'stage_news', #temporarily disabled
'search_indexes.documents.establishment': 'stage_establishment',
'search_indexes.documents.tag_category': 'stage_tag_category',
}
COOKIE_DOMAIN = '.id-east.ru'