Merge branch 'develop' into migrate/site-affilations
This commit is contained in:
commit
e7deab5bbc
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
36
apps/establishment/management/commands/fix_scheduler.py
Normal file
36
apps/establishment/management/commands/fix_scheduler.py
Normal 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.'))
|
||||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
]
|
||||
33
apps/search_indexes/documents/tag_category.py
Normal file
33
apps/search_indexes/documents/tag_category.py
Normal 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()
|
||||
|
|
@ -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."""
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user