From f5d5ce922fe398d6ef68ea96614c43ee9fba66b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Tue, 3 Dec 2019 15:23:41 +0300 Subject: [PATCH 01/21] Old role migrate --- apps/account/migrations/0021_oldrole.py | 24 +++++++++++++++++++ .../migrations/0022_auto_20191203_1149.py | 18 ++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 apps/account/migrations/0021_oldrole.py create mode 100644 apps/account/migrations/0022_auto_20191203_1149.py diff --git a/apps/account/migrations/0021_oldrole.py b/apps/account/migrations/0021_oldrole.py new file mode 100644 index 00000000..9a8fd4e9 --- /dev/null +++ b/apps/account/migrations/0021_oldrole.py @@ -0,0 +1,24 @@ +# Generated by Django 2.2.7 on 2019-12-03 10:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0020_role_site'), + ] + + operations = [ + migrations.CreateModel( + name='OldRole', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('new_role', models.CharField(max_length=512, verbose_name='New role')), + ('old_role', models.CharField(max_length=512, verbose_name='Old role')), + ], + options={ + 'unique_together': {('new_role', 'old_role')}, + }, + ), + ] diff --git a/apps/account/migrations/0022_auto_20191203_1149.py b/apps/account/migrations/0022_auto_20191203_1149.py new file mode 100644 index 00000000..f6b554ba --- /dev/null +++ b/apps/account/migrations/0022_auto_20191203_1149.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.7 on 2019-12-03 11:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0021_oldrole'), + ] + + operations = [ + migrations.AlterField( + model_name='oldrole', + name='old_role', + field=models.CharField(max_length=512, null=True, verbose_name='Old role'), + ), + ] From 5ecb5d214b51df9c8058edc1995099096db2e0de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Tue, 3 Dec 2019 15:24:02 +0300 Subject: [PATCH 02/21] Old role migrate1 --- .../management/commands/add_affilations.py | 45 +++++++++++++++++++ apps/account/models.py | 8 ++++ 2 files changed, 53 insertions(+) create mode 100644 apps/account/management/commands/add_affilations.py diff --git a/apps/account/management/commands/add_affilations.py b/apps/account/management/commands/add_affilations.py new file mode 100644 index 00000000..4485d6f1 --- /dev/null +++ b/apps/account/management/commands/add_affilations.py @@ -0,0 +1,45 @@ +from account.models import OldRole +from django.core.management.base import BaseCommand +from django.db import connections +from establishment.management.commands.add_position import namedtuplefetchall +from tqdm import tqdm + + +class Command(BaseCommand): + help = '''Add site affilations from old db to new db. + Run after migrate {}!!!''' + + def map_role_sql(self): + with connections['legacy'].cursor() as cursor: + cursor.execute(''' + select distinct + case when role = 'news_editor' then 'CONTENT_PAGE_MANAGER' + when role in ('reviewer', 'reviwer', 'reviewer_manager') then 'REVIEWER_MANGER' + when role = 'admin' then 'SUPERUSER' + when role ='community_manager' then 'COUNTRY_ADMIN' + when role = 'site_admin' then 'COUNTRY_ADMIN' + when role = 'wine_reviewer' then 'WINERY_REVIEWER' + when role in ('salesman', 'sales_man') then 'SALES_MAN' + when role = 'seller' then 'SELLER' + else role + end as new_role, + case when role = 'GUEST' then null else role end as role + from + ( + SELECT + DISTINCT + COALESCE(role, 'GUEST') as role + FROM site_affiliations AS sa + ) t + ''') + return namedtuplefetchall(cursor) + + def handle(self, *args, **kwargs): + objects = [] + OldRole.objects.all().delete() + for s in tqdm(self.map_role_sql(), desc='Add permissions old'): + objects.append( + OldRole(new_role=s.new_role, old_role=s.role) + ) + OldRole.objects.bulk_create(objects) + self.stdout.write(self.style.WARNING(f'Migrated old roles.')) \ No newline at end of file diff --git a/apps/account/models.py b/apps/account/models.py index c212ffda..d422b772 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -291,3 +291,11 @@ class UserRole(ProjectBaseMixin): 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) + + +class OldRole(models.Model): + new_role = models.CharField(verbose_name=_('New role'), max_length=512) + old_role = models.CharField(verbose_name=_('Old role'), max_length=512, null=True) + + class Meta: + unique_together = ('new_role', 'old_role') \ No newline at end of file From 4797225beb75f8d19b34fb838c2d371537bd5805 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Tue, 3 Dec 2019 16:40:50 +0300 Subject: [PATCH 03/21] model --- apps/account/models.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/account/models.py b/apps/account/models.py index d422b772..0a3cd3a6 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -32,6 +32,9 @@ class Role(ProjectBaseMixin): ESTABLISHMENT_MANAGER = 5 REVIEWER_MANGER = 6 RESTAURANT_REVIEWER = 7 + SALES_MAN = 8 + WINERY_REVIEWER = 9 + SELLER = 10 ROLE_CHOICES = ( (STANDARD_USER, 'Standard user'), @@ -40,7 +43,10 @@ class Role(ProjectBaseMixin): (CONTENT_PAGE_MANAGER, 'Content page manager'), (ESTABLISHMENT_MANAGER, 'Establishment manager'), (REVIEWER_MANGER, 'Reviewer manager'), - (RESTAURANT_REVIEWER, 'Restaurant reviewer') + (RESTAURANT_REVIEWER, 'Restaurant reviewer'), + (SALES_MAN, 'Sales man'), + (WINERY_REVIEWER, 'Winery reviewer'), + (SELLER, 'Seller') ) role = models.PositiveIntegerField(verbose_name=_('Role'), choices=ROLE_CHOICES, null=False, blank=False) From fee8a3b1209e165e988975e9a21c5e04842d93a0 Mon Sep 17 00:00:00 2001 From: dormantman Date: Tue, 3 Dec 2019 17:16:02 +0300 Subject: [PATCH 04/21] Correction of finding the center of points with positive --- apps/search_indexes/filters.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/apps/search_indexes/filters.py b/apps/search_indexes/filters.py index 4537124d..ce77bc06 100644 --- a/apps/search_indexes/filters.py +++ b/apps/search_indexes/filters.py @@ -21,15 +21,9 @@ class CustomGeoSpatialFilteringFilterBackend(GeoSpatialFilteringFilterBackend): 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 + 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 From cc7754a9d78cba712fad35afdd4581c7289e8fa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Tue, 3 Dec 2019 17:58:46 +0300 Subject: [PATCH 05/21] Old role migrate1 --- .../management/commands/add_affilations.py | 52 ++++++++++++++++--- 1 file changed, 46 insertions(+), 6 deletions(-) diff --git a/apps/account/management/commands/add_affilations.py b/apps/account/management/commands/add_affilations.py index 4485d6f1..550847f2 100644 --- a/apps/account/management/commands/add_affilations.py +++ b/apps/account/management/commands/add_affilations.py @@ -1,4 +1,5 @@ -from account.models import OldRole +from account.models import OldRole, Role +from main.models import SiteSettings from django.core.management.base import BaseCommand from django.db import connections from establishment.management.commands.add_position import namedtuplefetchall @@ -34,12 +35,51 @@ class Command(BaseCommand): ''') return namedtuplefetchall(cursor) - def handle(self, *args, **kwargs): + def add_old_roles(self): objects = [] OldRole.objects.all().delete() for s in tqdm(self.map_role_sql(), desc='Add permissions old'): - objects.append( - OldRole(new_role=s.new_role, old_role=s.role) - ) + objects.append( + OldRole(new_role=s.new_role, old_role=s.role) + ) OldRole.objects.bulk_create(objects) - self.stdout.write(self.style.WARNING(f'Migrated old roles.')) \ No newline at end of file + self.stdout.write(self.style.WARNING(f'Migrated old roles.')) + + def site_role_sql(self): + with connections['legacy'].cursor() as cursor: + cursor.execute(''' + select site_id, + role + from + ( + SELECT + DISTINCT + site_id, + COALESCE(role, 'GUEST') as role + FROM site_affiliations AS sa + ) t + where t.role not in ('admin', 'GUEST') + ''') + return namedtuplefetchall(cursor) + + def add_site_role(self): + objects = [] + for s in tqdm(self.site_role_sql(), desc='Add site role'): + old_role = OldRole.objects.get(old_role=s.role) + role_choice = getattr(Role, old_role.new_role) + sites = SiteSettings.objects.filter(old_id=s.site_id) + for site in sites: + role = Role.objects.filter(site=site, role=role_choice) + if not role.exists(): + objects.append( + Role(site=site, role=role_choice) + ) + + Role.objects.bulk_create(objects) + self.stdout.write(self.style.WARNING(f'Added site roles.')) + + + + def handle(self, *args, **kwargs): + # self.add_old_roles() + self.add_site_role() \ No newline at end of file From 7cf34f0741738ac03c16980e001c46adc38f1668 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Tue, 3 Dec 2019 18:30:33 +0300 Subject: [PATCH 06/21] fix --- apps/main/serializers.py | 69 ++++++++++++---------------------------- apps/main/views/back.py | 4 +-- 2 files changed, 23 insertions(+), 50 deletions(-) diff --git a/apps/main/serializers.py b/apps/main/serializers.py index b93382d5..a41a643f 100644 --- a/apps/main/serializers.py +++ b/apps/main/serializers.py @@ -17,10 +17,11 @@ class FeatureSerializer(serializers.ModelSerializer): fields = ( 'id', 'slug', - 'priority' + 'priority', + 'route', + 'site_settings', ) - class CurrencySerializer(ProjectModelSerializer): """Currency serializer.""" @@ -36,20 +37,24 @@ class CurrencySerializer(ProjectModelSerializer): class SiteFeatureSerializer(serializers.ModelSerializer): - """Site feature serializer.""" + 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 = ( - 'id', - 'site_settings', - 'feature', - 'published', - 'main', - 'nested' - ) + fields = ('main', + 'id', + 'slug', + 'priority', + 'route', + 'source', + 'nested', + ) class SiteSettingsSerializer(serializers.ModelSerializer): @@ -95,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' ] @@ -125,30 +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): - """Feature serializer.""" - - class Meta: - """Meta class.""" - - model = models.Feature - fields = ( - 'id', - 'slug', - 'priority', - 'route', - 'site_settings', - ) class AwardBaseSerializer(serializers.ModelSerializer): diff --git a/apps/main/views/back.py b/apps/main/views/back.py index adc0196a..1faf79a0 100644 --- a/apps/main/views/back.py +++ b/apps/main/views/back.py @@ -61,9 +61,9 @@ class SiteFeatureRUDBackView(generics.RetrieveUpdateDestroyAPIView): 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 From 848008cae13c05b15b04cebd3540174529487584 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 3 Dec 2019 19:02:41 +0300 Subject: [PATCH 07/21] tags dynamic filters --- apps/search_indexes/filters.py | 122 ++++++++++++++++++++++++++------- 1 file changed, 99 insertions(+), 23 deletions(-) diff --git a/apps/search_indexes/filters.py b/apps/search_indexes/filters.py index ce77bc06..3101fdc2 100644 --- a/apps/search_indexes/filters.py +++ b/apps/search_indexes/filters.py @@ -4,6 +4,7 @@ from django_elasticsearch_dsl_drf.filter_backends import SearchFilterBackend, \ FacetedSearchFilterBackend, GeoSpatialFilteringFilterBackend from search_indexes.utils import OBJECT_FIELD_PROPERTIES from six import iteritems +from functools import reduce class CustomGeoSpatialFilteringFilterBackend(GeoSpatialFilteringFilterBackend): @@ -56,10 +57,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): @@ -71,29 +92,84 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend): 'global' ).bucket(__field, agg) else: - qs = queryset.__copy__() - qs.query = queryset.query._clone() - filterer = makefilter(__facet) - for param_type in ['must', 'must_not', 'should']: - if qs.query._proxied._params.get(param_type): - qs.query._proxied._params[param_type] = list( - filter( - filterer, qs.query._proxied._params[param_type] + if __field != 'tag' or not request.query_params.getlist('tags_id__in'): + qs = queryset.__copy__() + qs.query = queryset.query._clone() + 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( + 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') - facet_name = '_filter_' + __field - qs.aggs.bucket( - facet_name, - 'filter', - filter=agg_filter - ).bucket(__field, agg) - view.paginator.facets_computed.update({facet_name: qs.execute().aggregations[facet_name]}) + 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') + facet_name = '_filter_' + __field + qs.aggs.bucket( + facet_name, + 'filter', + filter=agg_filter + ).bucket(__field, agg) + view.paginator.facets_computed.update({facet_name: qs.execute().aggregations[facet_name]}) + else: + tag_facets = [] + facet_name = '_filter_' + __field + for category_tags_ids in request.query_params.getlist('tags_id__in'): + tags_to_remove = category_tags_ids.split('__') + 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]) + view.paginator.facets_computed.update({facet_name: self.merge_buckets(tag_facets)}) return queryset + @staticmethod + def merge_buckets(buckets: list): + """Reduces all buckets preserving class""" + result_bucket = buckets[0] + for bucket in buckets[1:]: + for tag in bucket.tag.buckets._l_: + if tag not in result_bucket.tag.buckets._l_: + result_bucket.tag.buckets._l_.append(tag) + def reducer(prev, cur): + try: + index = list(map(lambda x: x['key'], prev)).index(cur['key']) + if cur['doc_count'] < prev[index]['doc_count']: + prev[index]['doc_count'] = cur['doc_count'] + except ValueError: + prev.append(cur) + return prev + + result_bucket.tag.buckets._l_ = list(reduce( + reducer, result_bucket.tag.buckets._l_, [] + )) + result_bucket.doc_count = reduce( + lambda prev, cur: prev + cur['doc_count'], + result_bucket.tag.buckets._l_, + 0 + ) + return result_bucket + class CustomSearchFilterBackend(SearchFilterBackend): """Custom SearchFilterBackend.""" From 7596652a3f3d5944fec5528baa991e836f58fd95 Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 3 Dec 2019 19:31:05 +0300 Subject: [PATCH 08/21] category tag tags filter by obj type --- apps/tag/serializers.py | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/apps/tag/serializers.py b/apps/tag/serializers.py index 2cc818a9..5b6f390c 100644 --- a/apps/tag/serializers.py +++ b/apps/tag/serializers.py @@ -1,9 +1,12 @@ """Tag serializers.""" from rest_framework import serializers +from rest_framework.fields import SerializerMethodField + from establishment.models import (Establishment, EstablishmentType, EstablishmentSubType) from news.models import News, NewsType from tag import models +from tag.models import Tag from utils.exceptions import (ObjectAlreadyAdded, BindingObjectNotFound, RemovedBindingObjectNotFound) from utils.serializers import TranslatedField @@ -12,6 +15,9 @@ from utils.serializers import TranslatedField class TagBaseSerializer(serializers.ModelSerializer): """Serializer for model Tag.""" + def get_extra_kwargs(self): + return super().get_extra_kwargs() + label_translated = TranslatedField() index_name = serializers.CharField(source='value', read_only=True, allow_null=True) @@ -37,6 +43,7 @@ class TagBackOfficeSerializer(TagBaseSerializer): 'category' ) + class TagCategoryProductSerializer(serializers.ModelSerializer): """SHORT Serializer for TagCategory""" @@ -57,7 +64,7 @@ class TagCategoryBaseSerializer(serializers.ModelSerializer): """Serializer for model TagCategory.""" label_translated = TranslatedField() - tags = TagBaseSerializer(many=True, read_only=True) + tags = SerializerMethodField() class Meta: """Meta class.""" @@ -70,6 +77,22 @@ class TagCategoryBaseSerializer(serializers.ModelSerializer): 'tags', ) + def get_tags(self, obj): + query_params = dict(self.context['request'].query_params) + if len(query_params) > 1: + return None + + tags = Tag.objects.all() + + if 'establishment_type' in query_params: + types = query_params['establishment_type'] + tags = tags.filter(establishments__establishment_type__index_name__in=types).distinct() + elif 'product_type' in query_params: + types = query_params['product_type'] + tags = tags.filter(products__product_type__index_name__in=types).distinct() + + return TagBaseSerializer(instance=tags, many=True, read_only=True).data + class TagCategoryShortSerializer(serializers.ModelSerializer): """Serializer for model TagCategory.""" @@ -174,15 +197,15 @@ class TagCategoryBindObjectSerializer(serializers.Serializer): attrs['tag_category'] = tag_category if obj_type == self.ESTABLISHMENT_TYPE: - establishment_type = EstablishmentType.objects.filter(pk=obj_id).\ + establishment_type = EstablishmentType.objects.filter(pk=obj_id). \ first() if not establishment_type: raise BindingObjectNotFound() - if request.method == 'POST' and tag_category.establishment_types.\ + if request.method == 'POST' and tag_category.establishment_types. \ filter(pk=establishment_type.pk).exists(): raise ObjectAlreadyAdded() - if request.method == 'DELETE' and not tag_category.\ - establishment_types.filter(pk=establishment_type.pk).\ + if request.method == 'DELETE' and not tag_category. \ + establishment_types.filter(pk=establishment_type.pk). \ exists(): raise RemovedBindingObjectNotFound() attrs['related_object'] = establishment_type @@ -190,10 +213,10 @@ class TagCategoryBindObjectSerializer(serializers.Serializer): news_type = NewsType.objects.filter(pk=obj_id).first() if not news_type: raise BindingObjectNotFound() - if request.method == 'POST' and tag_category.news_types.\ + if request.method == 'POST' and tag_category.news_types. \ filter(pk=news_type.pk).exists(): raise ObjectAlreadyAdded() - if request.method == 'DELETE' and not tag_category.news_types.\ + if request.method == 'DELETE' and not tag_category.news_types. \ filter(pk=news_type.pk).exists(): raise RemovedBindingObjectNotFound() attrs['related_object'] = news_type From c00075feec564c87e13cdb6fbe8da998c89c45dd Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 3 Dec 2019 19:39:20 +0300 Subject: [PATCH 09/21] empty booking response --- apps/booking/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/booking/views.py b/apps/booking/views.py index 06ef9273..b3f85bff 100644 --- a/apps/booking/views.py +++ b/apps/booking/views.py @@ -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') From 013cd864c29d621a1e25cddb821d384e5fa6a935 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 3 Dec 2019 20:48:35 +0300 Subject: [PATCH 10/21] tags dynamic filters #2 --- apps/search_indexes/filters.py | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/apps/search_indexes/filters.py b/apps/search_indexes/filters.py index 3101fdc2..a12c37f7 100644 --- a/apps/search_indexes/filters.py +++ b/apps/search_indexes/filters.py @@ -149,25 +149,23 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend): result_bucket = buckets[0] for bucket in buckets[1:]: for tag in bucket.tag.buckets._l_: - if tag not in result_bucket.tag.buckets._l_: - result_bucket.tag.buckets._l_.append(tag) + result_bucket.tag.buckets.append(tag) + def reducer(prev, cur): - try: - index = list(map(lambda x: x['key'], prev)).index(cur['key']) - if cur['doc_count'] < prev[index]['doc_count']: - prev[index]['doc_count'] = cur['doc_count'] - except ValueError: + """Unique by key""" + if not len(list(filter(lambda x: x['key'] == cur['key'], prev))): prev.append(cur) return prev - result_bucket.tag.buckets._l_ = list(reduce( - reducer, result_bucket.tag.buckets._l_, [] - )) - result_bucket.doc_count = reduce( - lambda prev, cur: prev + cur['doc_count'], + buckets_count = len(buckets) + result_bucket.tag.buckets = list(filter(lambda t: t is not None, [ + tag if len(list(filter(lambda t: t['key'] == tag['key'], result_bucket.tag.buckets._l_))) == buckets_count else None + for tag in result_bucket.tag.buckets._l_])) # here we remove tags which don't present in any bucket + result_bucket.tag.buckets = list(reduce( + reducer, result_bucket.tag.buckets._l_, - 0 - ) + [] + )) return result_bucket From 9c84842c111fd5a65a970369f36ad34ed2d6344c Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 3 Dec 2019 22:35:38 +0300 Subject: [PATCH 11/21] tags dynamic filters #3 --- apps/search_indexes/filters.py | 37 ++++++++++++---------------------- 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/apps/search_indexes/filters.py b/apps/search_indexes/filters.py index a12c37f7..ec67fe7a 100644 --- a/apps/search_indexes/filters.py +++ b/apps/search_indexes/filters.py @@ -4,7 +4,7 @@ from django_elasticsearch_dsl_drf.filter_backends import SearchFilterBackend, \ FacetedSearchFilterBackend, GeoSpatialFilteringFilterBackend from search_indexes.utils import OBJECT_FIELD_PROPERTIES from six import iteritems -from functools import reduce +from tag.models import TagCategory class CustomGeoSpatialFilteringFilterBackend(GeoSpatialFilteringFilterBackend): @@ -92,7 +92,7 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend): 'global' ).bucket(__field, agg) else: - if __field != 'tag' or not request.query_params.getlist('tags_id__in'): + if __field != 'tag': qs = queryset.__copy__() qs.query = queryset.query._clone() filterer = make_filter(__facet) @@ -116,9 +116,11 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend): view.paginator.facets_computed.update({facet_name: qs.execute().aggregations[facet_name]}) else: tag_facets = [] + preserve_ids = [] facet_name = '_filter_' + __field - for category_tags_ids in request.query_params.getlist('tags_id__in'): - tags_to_remove = category_tags_ids.split('__') + for category in TagCategory.objects.prefetch_related('tags').filter(public=True, + value_type=TagCategory.LIST): + tags_to_remove = list(map(lambda t: t.id, category.tags.all())) qs = queryset.__copy__() qs.query = queryset.query._clone() filterer = make_tags_filter(__facet, tags_to_remove) @@ -140,32 +142,19 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend): filter=agg_filter ).bucket(__field, agg) tag_facets.append(qs.execute().aggregations[facet_name]) - view.paginator.facets_computed.update({facet_name: self.merge_buckets(tag_facets)}) + preserve_ids.append(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): + def merge_buckets(buckets: list, presrve_ids: list): """Reduces all buckets preserving class""" result_bucket = buckets[0] - for bucket in buckets[1:]: + result_bucket.tag.buckets = list(filter(lambda x: x in presrve_ids[0], result_bucket.tag.buckets._l_)) + for bucket, ids in list(zip(buckets, presrve_ids))[1:]: for tag in bucket.tag.buckets._l_: - result_bucket.tag.buckets.append(tag) - - def reducer(prev, cur): - """Unique by key""" - if not len(list(filter(lambda x: x['key'] == cur['key'], prev))): - prev.append(cur) - return prev - - buckets_count = len(buckets) - result_bucket.tag.buckets = list(filter(lambda t: t is not None, [ - tag if len(list(filter(lambda t: t['key'] == tag['key'], result_bucket.tag.buckets._l_))) == buckets_count else None - for tag in result_bucket.tag.buckets._l_])) # here we remove tags which don't present in any bucket - result_bucket.tag.buckets = list(reduce( - reducer, - result_bucket.tag.buckets._l_, - [] - )) + if tag['key'] in ids: + result_bucket.tag.buckets.append(tag) return result_bucket From eaf5c41e1db19fbfc60246f2eef04a55be0b8b3b Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 3 Dec 2019 22:53:31 +0300 Subject: [PATCH 12/21] tags dynamic filters #4 --- apps/search_indexes/filters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/search_indexes/filters.py b/apps/search_indexes/filters.py index ec67fe7a..8040e4b4 100644 --- a/apps/search_indexes/filters.py +++ b/apps/search_indexes/filters.py @@ -150,7 +150,7 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend): def merge_buckets(buckets: list, presrve_ids: list): """Reduces all buckets preserving class""" result_bucket = buckets[0] - result_bucket.tag.buckets = list(filter(lambda x: x in presrve_ids[0], result_bucket.tag.buckets._l_)) + result_bucket.tag.buckets = list(filter(lambda x: x['key'] in presrve_ids[0], result_bucket.tag.buckets._l_)) for bucket, ids in list(zip(buckets, presrve_ids))[1:]: for tag in bucket.tag.buckets._l_: if tag['key'] in ids: From f9fb2aa17ea661f7a1aae97872d624b9f8e0bd5b Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 3 Dec 2019 23:49:45 +0300 Subject: [PATCH 13/21] tags dynamic filters #4 --- apps/search_indexes/filters.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/search_indexes/filters.py b/apps/search_indexes/filters.py index 8040e4b4..bc48b029 100644 --- a/apps/search_indexes/filters.py +++ b/apps/search_indexes/filters.py @@ -120,7 +120,7 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend): facet_name = '_filter_' + __field for category in TagCategory.objects.prefetch_related('tags').filter(public=True, value_type=TagCategory.LIST): - tags_to_remove = list(map(lambda t: t.id, category.tags.all())) + tags_to_remove = list(map(lambda t: str(t.id), category.tags.all())) qs = queryset.__copy__() qs.query = queryset.query._clone() filterer = make_tags_filter(__facet, tags_to_remove) @@ -142,16 +142,16 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend): filter=agg_filter ).bucket(__field, agg) tag_facets.append(qs.execute().aggregations[facet_name]) - preserve_ids.append(tags_to_remove) + 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, presrve_ids: list): + 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 presrve_ids[0], result_bucket.tag.buckets._l_)) - for bucket, ids in list(zip(buckets, presrve_ids))[1:]: + 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) From 9f882ca65643aa0455b2892737f89588f51d2107 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 4 Dec 2019 00:33:18 +0300 Subject: [PATCH 14/21] tags dynamic filters #4 (boost search via ES) --- apps/search_indexes/documents/__init__.py | 2 ++ apps/search_indexes/documents/tag_category.py | 33 +++++++++++++++++++ apps/search_indexes/filters.py | 9 +++-- project/settings/development.py | 1 + project/settings/local.py | 1 + project/settings/production.py | 1 + project/settings/stage.py | 1 + 7 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 apps/search_indexes/documents/tag_category.py diff --git a/apps/search_indexes/documents/__init__.py b/apps/search_indexes/documents/__init__.py index c357f29e..60a9b580 100644 --- a/apps/search_indexes/documents/__init__.py +++ b/apps/search_indexes/documents/__init__.py @@ -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', ] \ No newline at end of file diff --git a/apps/search_indexes/documents/tag_category.py b/apps/search_indexes/documents/tag_category.py new file mode 100644 index 00000000..ed69c8b7 --- /dev/null +++ b/apps/search_indexes/documents/tag_category.py @@ -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() diff --git a/apps/search_indexes/filters.py b/apps/search_indexes/filters.py index bc48b029..27df0699 100644 --- a/apps/search_indexes/filters.py +++ b/apps/search_indexes/filters.py @@ -4,6 +4,7 @@ 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 @@ -118,9 +119,11 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend): tag_facets = [] preserve_ids = [] facet_name = '_filter_' + __field - for category in TagCategory.objects.prefetch_related('tags').filter(public=True, - value_type=TagCategory.LIST): - tags_to_remove = list(map(lambda t: str(t.id), category.tags.all())) + all_tag_categories = TagCategoryDocument.search() \ + .filter('term', public=True) \ + .filter('term', value_type=TagCategory.LIST) + 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) diff --git a/project/settings/development.py b/project/settings/development.py index f850aad7..88b88789 100644 --- a/project/settings/development.py +++ b/project/settings/development.py @@ -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 diff --git a/project/settings/local.py b/project/settings/local.py index 6a592a46..644098bb 100644 --- a/project/settings/local.py +++ b/project/settings/local.py @@ -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 diff --git a/project/settings/production.py b/project/settings/production.py index 7ef2dc62..3020c6f0 100644 --- a/project/settings/production.py +++ b/project/settings/production.py @@ -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( diff --git a/project/settings/stage.py b/project/settings/stage.py index 95285034..dbc6844a 100644 --- a/project/settings/stage.py +++ b/project/settings/stage.py @@ -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' From f5079940148d135ba361a0dbc084a3aa31588075 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 4 Dec 2019 00:49:46 +0300 Subject: [PATCH 15/21] tags dynamic filters #5 (add wine-colors) --- apps/search_indexes/filters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/search_indexes/filters.py b/apps/search_indexes/filters.py index 27df0699..644b47fd 100644 --- a/apps/search_indexes/filters.py +++ b/apps/search_indexes/filters.py @@ -121,7 +121,7 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend): facet_name = '_filter_' + __field all_tag_categories = TagCategoryDocument.search() \ .filter('term', public=True) \ - .filter('term', value_type=TagCategory.LIST) + .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__() From 7590a73c5de31145658c6c114a8bce8ab91ad2b7 Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 4 Dec 2019 09:23:21 +0300 Subject: [PATCH 16/21] tags fix mtm --- apps/tag/models.py | 2 +- apps/tag/serializers.py | 21 +++++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/apps/tag/models.py b/apps/tag/models.py index 12517815..b718d83c 100644 --- a/apps/tag/models.py +++ b/apps/tag/models.py @@ -47,7 +47,7 @@ class Tag(TranslatedFieldsMixin, models.Model): old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None) old_id_meta_product = models.PositiveIntegerField(_('old id metadata product'), - blank=True, null=True, default=None) + blank=True, null=True, default=None) objects = TagQuerySet.as_manager() diff --git a/apps/tag/serializers.py b/apps/tag/serializers.py index 5b6f390c..eb73291f 100644 --- a/apps/tag/serializers.py +++ b/apps/tag/serializers.py @@ -2,11 +2,9 @@ from rest_framework import serializers from rest_framework.fields import SerializerMethodField -from establishment.models import (Establishment, EstablishmentType, - EstablishmentSubType) +from establishment.models import (Establishment, EstablishmentType) from news.models import News, NewsType from tag import models -from tag.models import Tag from utils.exceptions import (ObjectAlreadyAdded, BindingObjectNotFound, RemovedBindingObjectNotFound) from utils.serializers import TranslatedField @@ -79,18 +77,21 @@ class TagCategoryBaseSerializer(serializers.ModelSerializer): def get_tags(self, obj): query_params = dict(self.context['request'].query_params) + if len(query_params) > 1: - return None - - tags = Tag.objects.all() + return [] + params = {} if 'establishment_type' in query_params: - types = query_params['establishment_type'] - tags = tags.filter(establishments__establishment_type__index_name__in=types).distinct() + params = { + 'establishments__isnull': False, + } elif 'product_type' in query_params: - types = query_params['product_type'] - tags = tags.filter(products__product_type__index_name__in=types).distinct() + params = { + 'products__isnull': False, + } + tags = obj.tags.filter(**params).distinct() return TagBaseSerializer(instance=tags, many=True, read_only=True).data From 96e08a0900cd492669e3e3ab3a629b6738649731 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 4 Dec 2019 12:24:23 +0300 Subject: [PATCH 17/21] Migrate complete --- .../management/commands/add_affilations.py | 77 +++++++++++++++++-- .../migrations/0023_auto_20191204_0916.py | 22 ++++++ apps/account/models.py | 2 + 3 files changed, 96 insertions(+), 5 deletions(-) create mode 100644 apps/account/migrations/0023_auto_20191204_0916.py diff --git a/apps/account/management/commands/add_affilations.py b/apps/account/management/commands/add_affilations.py index 550847f2..591e676d 100644 --- a/apps/account/management/commands/add_affilations.py +++ b/apps/account/management/commands/add_affilations.py @@ -1,14 +1,15 @@ -from account.models import OldRole, Role +from account.models import OldRole, Role, User, UserRole from main.models import SiteSettings from django.core.management.base import BaseCommand -from django.db import connections +from django.db import connections, transaction +from django.db.models import Prefetch from establishment.management.commands.add_position import namedtuplefetchall from tqdm import tqdm class Command(BaseCommand): help = '''Add site affilations from old db to new db. - Run after migrate {}!!!''' + Run after migrate account models!!!''' def map_role_sql(self): with connections['legacy'].cursor() as cursor: @@ -78,8 +79,74 @@ class Command(BaseCommand): Role.objects.bulk_create(objects) self.stdout.write(self.style.WARNING(f'Added site roles.')) + def update_site_role(self): + roles = Role.objects.filter(country__isnull=True).select_related('site')\ + .filter(site__id__isnull=False).select_for_update() + with transaction.atomic(): + for role in tqdm(roles, desc='Update role country'): + role.country = role.site.country + role.save() + self.stdout.write(self.style.WARNING(f'Updated site roles.')) + def user_role_sql(self): + with connections['legacy'].cursor() as cursor: + cursor.execute(''' + select t.* + from + ( + SELECT + site_id, + account_id, + COALESCE(role, 'GUEST') as role + FROM site_affiliations AS sa + ) t + join accounts a on a.id = t.account_id + where t.role not in ('admin', 'GUEST') + ''') + return namedtuplefetchall(cursor) + + def add_role_user(self): + for s in tqdm(self.user_role_sql(), desc='Add role to user'): + sites = SiteSettings.objects.filter(old_id=s.site_id) + old_role = OldRole.objects.get(old_role=s.role) + role_choice = getattr(Role, old_role.new_role) + roles = Role.objects.filter(site__in=[site for site in sites], role=role_choice) + users = User.objects.filter(old_id=s.account_id) + for user in users: + for role in roles: + user_role = UserRole.objects.get_or_create(user=user, + role=role) + self.stdout.write(self.style.WARNING(f'Added users roles.')) + + def superuser_role_sql(self): + with connections['legacy'].cursor() as cursor: + cursor.execute(''' + select t.* + from + ( + SELECT + site_id, + account_id, + COALESCE(role, 'GUEST') as role + FROM site_affiliations AS sa + ) t + join accounts a on a.id = t.account_id + where t.role in ('admin') + ''') + return namedtuplefetchall(cursor) + + def add_superuser(self): + for s in tqdm(self.superuser_role_sql(), desc='Add superuser'): + users = User.objects.filter(old_id=s.account_id).select_for_update() + with transaction.atomic(): + for user in users: + user.is_superuser = True + user.save() + self.stdout.write(self.style.WARNING(f'Added superuser.')) def handle(self, *args, **kwargs): - # self.add_old_roles() - self.add_site_role() \ No newline at end of file + self.add_old_roles() + self.add_site_role() + self.update_site_role() + self.add_role_user() + self.add_superuser() \ No newline at end of file diff --git a/apps/account/migrations/0023_auto_20191204_0916.py b/apps/account/migrations/0023_auto_20191204_0916.py new file mode 100644 index 00000000..68d313a0 --- /dev/null +++ b/apps/account/migrations/0023_auto_20191204_0916.py @@ -0,0 +1,22 @@ +# Generated by Django 2.2.7 on 2019-12-04 09:16 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0022_auto_20191203_1149'), + ] + + operations = [ + migrations.AlterField( + model_name='role', + name='role', + field=models.PositiveIntegerField(choices=[(1, 'Standard user'), (2, 'Comments moderator'), (3, 'Country admin'), (4, 'Content page manager'), (5, 'Establishment manager'), (6, 'Reviewer manager'), (7, 'Restaurant reviewer'), (8, 'Sales man'), (9, 'Winery reviewer'), (10, 'Seller')], verbose_name='Role'), + ), + migrations.AlterUniqueTogether( + name='userrole', + unique_together={('user', 'role')}, + ), + ] diff --git a/apps/account/models.py b/apps/account/models.py index 0a3cd3a6..3d3de56e 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -297,6 +297,8 @@ class UserRole(ProjectBaseMixin): 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) + class Meta: + unique_together = ['user', 'role'] class OldRole(models.Model): From 4d36fc0f0cf6b953324f9ff12181cd5c7f51d005 Mon Sep 17 00:00:00 2001 From: Dmitriy Kuzmenko Date: Wed, 4 Dec 2019 12:38:07 +0300 Subject: [PATCH 18/21] fix fabric --- fabfile.py | 2 +- make_data_migration.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fabfile.py b/fabfile.py index e8a52f85..f9896200 100644 --- a/fabfile.py +++ b/fabfile.py @@ -54,7 +54,7 @@ def collectstatic(): def deploy(branch=None): role = env.roles[0] - if env.roledefs[role]['branch'] != 'develop': + if env.roledefs[role]['branch'] == 'develop': fetch() install_requirements() migrate() diff --git a/make_data_migration.sh b/make_data_migration.sh index c92f74e7..bed1afb7 100755 --- a/make_data_migration.sh +++ b/make_data_migration.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash ./manage.py transfer -a -#./manage.py transfer -d +./manage.py transfer -d ./manage.py transfer -e ./manage.py transfer --fill_city_gallery ./manage.py transfer -l From c5992afec0e096b7f477d574652aeba74b29cfe0 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 4 Dec 2019 13:50:55 +0300 Subject: [PATCH 19/21] add opening_at to schedule search results --- apps/search_indexes/serializers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index 67a2bff0..7bd3995a 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -168,6 +168,7 @@ class ScheduleDocumentSerializer(serializers.Serializer): weekday = serializers.IntegerField() weekday_display = serializers.CharField() closed_at = serializers.CharField() + opening_at = serializers.CharField() class InFavoritesMixin(DocumentSerializer): From 552e08e24249de183d24a966a5d4adeed96d28b9 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 4 Dec 2019 13:52:39 +0300 Subject: [PATCH 20/21] opening_at for establishment --- apps/search_indexes/documents/establishment.py | 1 + apps/timetable/models.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index 9e1d0a82..aca81f3f 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -116,6 +116,7 @@ class EstablishmentDocument(Document): 'weekday': fields.IntegerField(attr='weekday'), 'weekday_display': fields.KeywordField(attr='get_weekday_display'), 'closed_at': fields.KeywordField(attr='closed_at_str'), + 'opening_at': fields.KeywordField(attr='opening_at_str'), } )) address = fields.ObjectField( diff --git a/apps/timetable/models.py b/apps/timetable/models.py index cf7f8d94..90a6ae38 100644 --- a/apps/timetable/models.py +++ b/apps/timetable/models.py @@ -39,6 +39,10 @@ class Timetable(ProjectBaseMixin): def closed_at_str(self): return str(self.closed_at) if self.closed_at else None + @property + def opening_at_str(self): + return str(self.opening_at) if self.opening_at else None + @property def opening_time(self): return self.opening_at or self.lunch_start or self.dinner_start From b2fb341c101e28410a71624b7ecf014c78a23347 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 4 Dec 2019 17:13:38 +0300 Subject: [PATCH 21/21] fix wines faceted search --- apps/search_indexes/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index a5b952d7..5fa845f2 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -346,7 +346,7 @@ class ProductDocumentViewSet(BaseDocumentViewSet): faceted_search_fields = { 'tag': { - 'field': 'wine_colors.id', + 'field': 'tags.id', 'enabled': True, 'facet': TermsFacet, 'options': {