Merge branch 'feature/products_search' into 'develop'
Feature/products search See merge request gm/gm-backend!126
This commit is contained in:
commit
cf04f55bd3
|
|
@ -160,7 +160,7 @@ class WineRegionQuerySet(models.QuerySet):
|
|||
"""Wine region queryset."""
|
||||
|
||||
|
||||
class WineRegion(models.Model):
|
||||
class WineRegion(models.Model, TranslatedFieldsMixin):
|
||||
"""Wine region model."""
|
||||
name = models.CharField(_('name'), max_length=255)
|
||||
country = models.ForeignKey(Country, on_delete=models.PROTECT,
|
||||
|
|
|
|||
|
|
@ -78,6 +78,12 @@ class ProductQuerySet(models.QuerySet):
|
|||
return self.select_related('product_type', 'establishment') \
|
||||
.prefetch_related('product_type__subtypes')
|
||||
|
||||
def with_es_related(self):
|
||||
"""Returns qs with related for ES indexing."""
|
||||
return self.with_base_related().prefetch_related('tags', 'classifications', 'classifications__tags',
|
||||
'classifications__type', 'classifications_standard',
|
||||
'standards').select_related('wine_region', 'wine_sub_region')
|
||||
|
||||
def common(self):
|
||||
return self.filter(category=self.model.COMMON)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
from search_indexes.documents.establishment import EstablishmentDocument
|
||||
from search_indexes.documents.news import NewsDocument
|
||||
from search_indexes.documents.product import ProductDocument
|
||||
|
||||
|
||||
# todo: make signal to update documents on related fields
|
||||
__all__ = [
|
||||
'EstablishmentDocument',
|
||||
'NewsDocument',
|
||||
'ProductDocument',
|
||||
]
|
||||
116
apps/search_indexes/documents/product.py
Normal file
116
apps/search_indexes/documents/product.py
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
"""Product app documents."""
|
||||
from django.conf import settings
|
||||
from django_elasticsearch_dsl import Document, Index, fields
|
||||
from search_indexes.utils import OBJECT_FIELD_PROPERTIES
|
||||
from product import models
|
||||
|
||||
ProductIndex = Index(settings.ELASTICSEARCH_INDEX_NAMES.get(__name__, 'product'))
|
||||
ProductIndex.settings(number_of_shards=1, number_of_replicas=1, mapping={'total_fields':{'limit': 3000}})
|
||||
|
||||
|
||||
@ProductIndex.doc_type
|
||||
class ProductDocument(Document):
|
||||
"""Product document."""
|
||||
|
||||
description = fields.ObjectField(attr='description_indexing',
|
||||
properties=OBJECT_FIELD_PROPERTIES)
|
||||
product_type = fields.ObjectField(properties={
|
||||
'id': fields.IntegerField(),
|
||||
'name': fields.ObjectField(attr='name_indexing', properties=OBJECT_FIELD_PROPERTIES),
|
||||
'index_name': fields.KeywordField(),
|
||||
'use_subtypes': fields.BooleanField(),
|
||||
})
|
||||
subtypes = fields.ObjectField(
|
||||
properties={
|
||||
'id': fields.IntegerField(),
|
||||
'name': fields.ObjectField(attr='name_indexing', properties=OBJECT_FIELD_PROPERTIES),
|
||||
'index_name': fields.KeywordField(),
|
||||
},
|
||||
multi=True
|
||||
)
|
||||
establishment = fields.ObjectField(
|
||||
properties={
|
||||
'id': fields.IntegerField(),
|
||||
'name': fields.KeywordField(),
|
||||
'slug': fields.KeywordField(),
|
||||
# 'city' TODO: city indexing
|
||||
}
|
||||
)
|
||||
wine_colors = fields.ObjectField(
|
||||
properties={
|
||||
'id': fields.IntegerField(),
|
||||
'label': fields.ObjectField(attr='label_indexing', properties=OBJECT_FIELD_PROPERTIES),
|
||||
'value': fields.KeywordField(),
|
||||
},
|
||||
multi=True,
|
||||
)
|
||||
wine_region = fields.ObjectField(properties={
|
||||
'id': fields.IntegerField(),
|
||||
'name': fields.KeywordField(),
|
||||
'country': fields.ObjectField(properties={
|
||||
'id': fields.IntegerField(),
|
||||
'name': fields.ObjectField(attr='name_indexing',
|
||||
properties=OBJECT_FIELD_PROPERTIES),
|
||||
'code': fields.KeywordField(),
|
||||
}),
|
||||
# 'coordinates': fields.GeoPointField(),
|
||||
'description': fields.ObjectField(attr='description_indexing', properties=OBJECT_FIELD_PROPERTIES),
|
||||
})
|
||||
wine_sub_region = fields.ObjectField(properties={'name': fields.KeywordField()})
|
||||
classifications = fields.ObjectField( # TODO
|
||||
properties={
|
||||
'classification_type': fields.ObjectField(properties={}),
|
||||
'standard': fields.ObjectField(properties={
|
||||
'name': fields.KeywordField(),
|
||||
'standard_type': fields.IntegerField(),
|
||||
# 'coordinates': fields.GeoPointField(),
|
||||
}),
|
||||
'tags': fields.ObjectField(
|
||||
properties={
|
||||
'label': fields.ObjectField(attr='label_indexing', properties=OBJECT_FIELD_PROPERTIES),
|
||||
'value': fields.KeywordField(),
|
||||
},
|
||||
multi=True
|
||||
),
|
||||
},
|
||||
multi=True
|
||||
)
|
||||
standards = fields.ObjectField(
|
||||
properties={
|
||||
'id': fields.IntegerField(),
|
||||
'name': fields.KeywordField(),
|
||||
'standard_type': fields.IntegerField(),
|
||||
# 'coordinates': fields.GeoPointField(),
|
||||
},
|
||||
multi=True
|
||||
)
|
||||
wine_village = fields.ObjectField(properties={
|
||||
'name': fields.KeywordField(),
|
||||
})
|
||||
tags = fields.ObjectField(
|
||||
properties={
|
||||
'id': fields.IntegerField(),
|
||||
'label': fields.ObjectField(attr='label_indexing', properties=OBJECT_FIELD_PROPERTIES),
|
||||
'value': fields.KeywordField(),
|
||||
},
|
||||
multi=True
|
||||
)
|
||||
|
||||
class Django:
|
||||
model = models.Product
|
||||
fields = (
|
||||
'id',
|
||||
'category',
|
||||
'name',
|
||||
'available',
|
||||
'public_mark',
|
||||
'slug',
|
||||
'old_id',
|
||||
'state',
|
||||
'old_unique_key',
|
||||
'vintage',
|
||||
)
|
||||
related_models = [models.ProductType]
|
||||
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().published().with_es_related()
|
||||
|
|
@ -4,6 +4,7 @@ from elasticsearch_dsl import AttrDict
|
|||
from django_elasticsearch_dsl_drf.serializers import DocumentSerializer
|
||||
from news.serializers import NewsTypeSerializer
|
||||
from search_indexes.documents import EstablishmentDocument, NewsDocument
|
||||
from search_indexes.documents.product import ProductDocument
|
||||
from search_indexes.utils import get_translated_value
|
||||
|
||||
|
||||
|
|
@ -19,6 +20,62 @@ class TagsDocumentSerializer(serializers.Serializer):
|
|||
return get_translated_value(obj.label)
|
||||
|
||||
|
||||
class ProductSubtypeDocumentSerializer(serializers.Serializer):
|
||||
"""Product subtype serializer for ES Document."""
|
||||
|
||||
id = serializers.IntegerField()
|
||||
name_translated = serializers.SerializerMethodField()
|
||||
|
||||
@staticmethod
|
||||
def get_name_translted(obj):
|
||||
return get_translated_value(obj.name)
|
||||
|
||||
|
||||
class WineRegionCountryDocumentSerialzer(serializers.Serializer):
|
||||
"""Wine region country ES document serializer."""
|
||||
|
||||
id = serializers.IntegerField()
|
||||
code = serializers.CharField()
|
||||
name_translated = serializers.SerializerMethodField()
|
||||
|
||||
@staticmethod
|
||||
def get_name_translated(obj):
|
||||
return get_translated_value(obj.name)
|
||||
|
||||
def get_attribute(self, instance):
|
||||
return instance.country
|
||||
|
||||
|
||||
class WineRegionDocumentSerializer(serializers.Serializer):
|
||||
"""Wine region ES document serializer."""
|
||||
|
||||
id = serializers.IntegerField()
|
||||
name = serializers.CharField()
|
||||
country = WineRegionCountryDocumentSerialzer(allow_null=True)
|
||||
|
||||
|
||||
class WineColorDocumentSerializer(serializers.Serializer):
|
||||
"""Wine color ES document serializer,"""
|
||||
|
||||
id = serializers.IntegerField()
|
||||
label_translated = serializers.SerializerMethodField()
|
||||
index_name = serializers.CharField(source='value')
|
||||
|
||||
@staticmethod
|
||||
def get_label_translated(obj):
|
||||
if isinstance(obj, dict):
|
||||
return get_translated_value(obj.get('label'))
|
||||
return get_translated_value(obj.label)
|
||||
|
||||
|
||||
class ProductEstablishmentDocumentSerializer(serializers.Serializer):
|
||||
"""Related to Product Establishment ES document serializer."""
|
||||
|
||||
id = serializers.IntegerField()
|
||||
name = serializers.CharField()
|
||||
slug = serializers.CharField()
|
||||
|
||||
|
||||
class CityDocumentShortSerializer(serializers.Serializer):
|
||||
"""City serializer for ES Document,"""
|
||||
|
||||
|
|
@ -123,3 +180,41 @@ class EstablishmentDocumentSerializer(DocumentSerializer):
|
|||
# 'establishment_type',
|
||||
# 'establishment_subtypes',
|
||||
)
|
||||
|
||||
|
||||
class ProductDocumentSerializer(DocumentSerializer):
|
||||
"""Product document serializer"""
|
||||
|
||||
tags = TagsDocumentSerializer(many=True)
|
||||
subtypes = ProductSubtypeDocumentSerializer(many=True)
|
||||
wine_region = WineRegionDocumentSerializer(allow_null=True)
|
||||
wine_colors = WineColorDocumentSerializer(many=True)
|
||||
product_type = serializers.SerializerMethodField()
|
||||
establishment_detail = ProductEstablishmentDocumentSerializer(source='establishment', allow_null=True)
|
||||
|
||||
@staticmethod
|
||||
def get_product_type(obj):
|
||||
return get_translated_value(obj.product_type.name if obj.product_type else {})
|
||||
|
||||
class Meta:
|
||||
"""Meta class."""
|
||||
|
||||
document = ProductDocument
|
||||
fields = (
|
||||
'id',
|
||||
'category',
|
||||
'name',
|
||||
'available',
|
||||
'public_mark',
|
||||
'slug',
|
||||
'old_id',
|
||||
'state',
|
||||
'old_unique_key',
|
||||
'vintage',
|
||||
'tags',
|
||||
'product_type',
|
||||
'subtypes',
|
||||
'wine_region',
|
||||
'wine_colors',
|
||||
'establishment_detail',
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,44 +11,20 @@ def update_document(sender, **kwargs):
|
|||
model_name = sender._meta.model_name
|
||||
instance = kwargs['instance']
|
||||
|
||||
if app_label == 'location':
|
||||
if model_name == 'country':
|
||||
establishments = Establishment.objects.filter(
|
||||
address__city__country=instance)
|
||||
for establishment in establishments:
|
||||
registry.update(establishment)
|
||||
if model_name == 'city':
|
||||
establishments = Establishment.objects.filter(
|
||||
address__city=instance)
|
||||
for establishment in establishments:
|
||||
registry.update(establishment)
|
||||
if model_name == 'address':
|
||||
establishments = Establishment.objects.filter(
|
||||
address=instance)
|
||||
for establishment in establishments:
|
||||
registry.update(establishment)
|
||||
|
||||
if app_label == 'establishment':
|
||||
app_label_model_name_to_filter = {
|
||||
('location','country'): 'address__city__country',
|
||||
('location','city'): 'address__city',
|
||||
('location', 'address'): 'address',
|
||||
# todo: remove after migration
|
||||
from establishment import models as establishment_models
|
||||
if model_name == 'establishmenttype':
|
||||
if isinstance(instance, establishment_models.EstablishmentType):
|
||||
establishments = Establishment.objects.filter(
|
||||
establishment_type=instance)
|
||||
for establishment in establishments:
|
||||
registry.update(establishment)
|
||||
if model_name == 'establishmentsubtype':
|
||||
if isinstance(instance, establishment_models.EstablishmentSubType):
|
||||
establishments = Establishment.objects.filter(
|
||||
establishment_subtypes=instance)
|
||||
for establishment in establishments:
|
||||
registry.update(establishment)
|
||||
|
||||
if app_label == 'tag':
|
||||
if model_name == 'tag':
|
||||
establishments = Establishment.objects.filter(tags=instance)
|
||||
for establishment in establishments:
|
||||
registry.update(establishment)
|
||||
('establishment', 'establishmenttype'): 'establishment_type',
|
||||
('establishment', 'establishmentsubtype'): 'establishment_subtypes',
|
||||
('tag', 'tag'): 'tags',
|
||||
}
|
||||
filter_name = app_label_model_name_to_filter.get((app_label, model_name))
|
||||
if filter_name:
|
||||
qs = Establishment.objects.filter(**{filter_name: instance})
|
||||
for product in qs:
|
||||
registry.update(product)
|
||||
|
||||
|
||||
@receiver(post_save)
|
||||
|
|
@ -57,21 +33,35 @@ def update_news(sender, **kwargs):
|
|||
app_label = sender._meta.app_label
|
||||
model_name = sender._meta.model_name
|
||||
instance = kwargs['instance']
|
||||
app_label_model_name_to_filter = {
|
||||
('location','country'): 'country',
|
||||
('news','newstype'): 'news_type',
|
||||
('tag', 'tag'): 'tags',
|
||||
}
|
||||
filter_name = app_label_model_name_to_filter.get((app_label, model_name))
|
||||
if filter_name:
|
||||
qs = News.objects.filter(**{filter_name: instance})
|
||||
for product in qs:
|
||||
registry.update(product)
|
||||
|
||||
if app_label == 'location':
|
||||
if model_name == 'country':
|
||||
qs = News.objects.filter(country=instance)
|
||||
for news in qs:
|
||||
registry.update(news)
|
||||
|
||||
if app_label == 'news':
|
||||
if model_name == 'newstype':
|
||||
qs = News.objects.filter(news_type=instance)
|
||||
for news in qs:
|
||||
registry.update(news)
|
||||
|
||||
if app_label == 'tag':
|
||||
if model_name == 'tag':
|
||||
qs = News.objects.filter(tags=instance)
|
||||
for news in qs:
|
||||
registry.update(news)
|
||||
@receiver(post_save)
|
||||
def update_product(sender, **kwargs):
|
||||
from product.models import Product
|
||||
app_label = sender._meta.app_label
|
||||
model_name = sender._meta.model_name
|
||||
instance = kwargs['instance']
|
||||
app_label_model_name_to_filter = {
|
||||
('product','productstandard'): 'standards',
|
||||
('product', 'producttype'): 'product_type',
|
||||
('tag','tag'): 'tags',
|
||||
('location', 'wineregion'): 'wine_region',
|
||||
('location', 'winesubregion'): 'wine_sub_region',
|
||||
('location', 'winevillage'): 'wine_village',
|
||||
('establishment', 'establishment'): 'establishment',
|
||||
}
|
||||
filter_name = app_label_model_name_to_filter.get((app_label, model_name))
|
||||
if filter_name:
|
||||
qs = Product.objects.filter(**{filter_name: instance})
|
||||
for product in qs:
|
||||
registry.update(product)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ router = routers.SimpleRouter()
|
|||
router.register(r'establishments', views.EstablishmentDocumentViewSet, basename='establishment')
|
||||
router.register(r'mobile/establishments', views.EstablishmentDocumentViewSet, basename='establishment-mobile')
|
||||
router.register(r'news', views.NewsDocumentViewSet, basename='news')
|
||||
router.register(r'products', views.ProductDocumentViewSet, basename='product')
|
||||
|
||||
urlpatterns = router.urls
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from django_elasticsearch_dsl_drf.filter_backends import (
|
|||
from django_elasticsearch_dsl_drf.viewsets import BaseDocumentViewSet
|
||||
from search_indexes import serializers, filters
|
||||
from search_indexes.documents import EstablishmentDocument, NewsDocument
|
||||
from search_indexes.documents.product import ProductDocument
|
||||
from utils.pagination import ProjectMobilePagination
|
||||
|
||||
|
||||
|
|
@ -192,3 +193,71 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet):
|
|||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ProductDocumentViewSet(BaseDocumentViewSet):
|
||||
"""Product document ViewSet."""
|
||||
|
||||
document = ProductDocument
|
||||
lookup_field = 'slug'
|
||||
pagination_class = ProjectMobilePagination
|
||||
permission_classes = (permissions.AllowAny,)
|
||||
serializer_class = serializers.ProductDocumentSerializer
|
||||
|
||||
# def get_queryset(self):
|
||||
# qs = super(ProductDocumentViewSet, self).get_queryset()
|
||||
# qs = qs.filter('match', is_publish=True)
|
||||
# return qs
|
||||
|
||||
filter_backends = [
|
||||
FilteringFilterBackend,
|
||||
filters.CustomSearchFilterBackend,
|
||||
GeoSpatialFilteringFilterBackend,
|
||||
DefaultOrderingFilterBackend,
|
||||
]
|
||||
|
||||
search_fields = {
|
||||
'name': {'fuzziness': 'auto:3,4',
|
||||
'boost': '2'},
|
||||
'transliterated_name': {'fuzziness': 'auto:3,4',
|
||||
'boost': '2'},
|
||||
'index_name': {'fuzziness': 'auto:3,4',
|
||||
'boost': '2'},
|
||||
'description': {'fuzziness': 'auto'},
|
||||
}
|
||||
translated_search_fields = (
|
||||
'description',
|
||||
)
|
||||
|
||||
filter_fields = {
|
||||
'slug': 'slug',
|
||||
'tags_id': {
|
||||
'field': 'tags.id',
|
||||
'lookups': [constants.LOOKUP_QUERY_IN]
|
||||
},
|
||||
'wine_colors_id': {
|
||||
'field': 'wine_colors.id',
|
||||
'lookups': [
|
||||
constants.LOOKUP_QUERY_IN,
|
||||
constants.LOOKUP_QUERY_EXCLUDE,
|
||||
]
|
||||
},
|
||||
'wine_from_country_code': {
|
||||
'field': 'wine_region.country.code',
|
||||
},
|
||||
'for_establishment': {
|
||||
'field': 'establishment.slug',
|
||||
},
|
||||
'type': {
|
||||
'field': 'product_type.index_name',
|
||||
},
|
||||
'subtype': {
|
||||
'field': 'subtypes.index_name',
|
||||
'lookups': [
|
||||
constants.LOOKUP_QUERY_IN,
|
||||
constants.LOOKUP_QUERY_EXCLUDE,
|
||||
]
|
||||
}
|
||||
}
|
||||
geo_spatial_filter_fields = {
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ services:
|
|||
- 9200:9200
|
||||
- 9300:9300
|
||||
environment:
|
||||
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
|
||||
- "ES_JAVA_OPTS=-Xms1g -Xmx1g"
|
||||
- discovery.type=single-node
|
||||
- xpack.security.enabled=false
|
||||
|
||||
|
|
|
|||
|
|
@ -28,8 +28,9 @@ ELASTICSEARCH_DSL = {
|
|||
|
||||
|
||||
ELASTICSEARCH_INDEX_NAMES = {
|
||||
'search_indexes.documents.news': 'development_news', # temporarily disabled
|
||||
'search_indexes.documents.news': 'development_news',
|
||||
'search_indexes.documents.establishment': 'development_establishment',
|
||||
'search_indexes.documents.product': 'development_product',
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ ELASTICSEARCH_DSL = {
|
|||
ELASTICSEARCH_INDEX_NAMES = {
|
||||
# 'search_indexes.documents.news': 'local_news',
|
||||
'search_indexes.documents.establishment': 'local_establishment',
|
||||
'search_indexes.documents.product': 'local_product',
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ ELASTICSEARCH_DSL = {
|
|||
ELASTICSEARCH_INDEX_NAMES = {
|
||||
'search_indexes.documents.news': 'development_news', # temporarily disabled
|
||||
'search_indexes.documents.establishment': 'development_establishment',
|
||||
'search_indexes.documents.product': 'development_product',
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user