Merge branch 'develop' into panels
# Conflicts: # apps/main/admin.py
This commit is contained in:
commit
847412bb2f
|
|
@ -1,4 +1,5 @@
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
|
from drf_yasg.utils import swagger_auto_schema
|
||||||
from rest_framework import generics, permissions, status, serializers
|
from rest_framework import generics, permissions, status, serializers
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
|
@ -96,6 +97,13 @@ class CreatePendingBooking(generics.CreateAPIView):
|
||||||
permission_classes = (permissions.AllowAny,)
|
permission_classes = (permissions.AllowAny,)
|
||||||
serializer_class = PendingBookingSerializer
|
serializer_class = PendingBookingSerializer
|
||||||
|
|
||||||
|
@swagger_auto_schema(operation_description="Request body params\n\n"
|
||||||
|
"IN GUESTONLINE (type:G): {"
|
||||||
|
"'restaurant_id', 'booking_time', "
|
||||||
|
"'booking_date', 'booked_persons_number'}\n"
|
||||||
|
"IN LASTABLE (type:L): {'booking_time', "
|
||||||
|
"'booked_persons_number', 'offer_id' (Req), "
|
||||||
|
"'email', 'phone', 'first_name', 'last_name'}")
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
data = request.data.copy()
|
data = request.data.copy()
|
||||||
if data.get('type') == Booking.LASTABLE and data.get("offer_id") is None:
|
if data.get('type') == Booking.LASTABLE and data.get("offer_id") is None:
|
||||||
|
|
@ -135,6 +143,10 @@ class UpdatePendingBooking(generics.UpdateAPIView):
|
||||||
permission_classes = (permissions.AllowAny,)
|
permission_classes = (permissions.AllowAny,)
|
||||||
serializer_class = UpdateBookingSerializer
|
serializer_class = UpdateBookingSerializer
|
||||||
|
|
||||||
|
@swagger_auto_schema(operation_description="Request body params\n\n"
|
||||||
|
"Required: 'email', 'phone', 'last_name', "
|
||||||
|
"'first_name', 'country_code', 'pending_booking_id',"
|
||||||
|
"Not req: 'note'")
|
||||||
def patch(self, request, *args, **kwargs):
|
def patch(self, request, *args, **kwargs):
|
||||||
instance = self.get_object()
|
instance = self.get_object()
|
||||||
data = request.data.copy()
|
data = request.data.copy()
|
||||||
|
|
|
||||||
34
apps/collection/migrations/0024_auto_20191213_0859.py
Normal file
34
apps/collection/migrations/0024_auto_20191213_0859.py
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-12-13 08:59
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('gallery', '0006_merge_20191027_1758'),
|
||||||
|
('collection', '0023_advertorial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='AdvertorialGallery',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('is_main', models.BooleanField(default=False, verbose_name='Is the main image')),
|
||||||
|
('advertorial', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='advertorial_gallery', to='collection.Advertorial', verbose_name='advertorial')),
|
||||||
|
('image', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='advertorial_gallery', to='gallery.Image', verbose_name='image')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'advertorial gallery',
|
||||||
|
'verbose_name_plural': 'advertorial galleries',
|
||||||
|
'unique_together': {('advertorial', 'image')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='advertorial',
|
||||||
|
name='gallery',
|
||||||
|
field=models.ManyToManyField(through='collection.AdvertorialGallery', to='gallery.Image'),
|
||||||
|
),
|
||||||
|
]
|
||||||
27
apps/collection/migrations/0024_auto_20191215_2156.py
Normal file
27
apps/collection/migrations/0024_auto_20191215_2156.py
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-12-15 21:56
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('collection', '0023_advertorial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='collection',
|
||||||
|
name='rank',
|
||||||
|
field=models.IntegerField(default=None, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='collection',
|
||||||
|
name='start',
|
||||||
|
field=models.DateTimeField(blank=True, default=None, null=True, verbose_name='start'),
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='collection',
|
||||||
|
name='description',
|
||||||
|
)
|
||||||
|
]
|
||||||
20
apps/collection/migrations/0025_collection_description.py
Normal file
20
apps/collection/migrations/0025_collection_description.py
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-12-16 17:25
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
import utils.models
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('collection', '0024_auto_20191215_2156'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='collection',
|
||||||
|
name='description',
|
||||||
|
field=utils.models.TJSONField(blank=True, default=None,
|
||||||
|
help_text='{"en-GB":"some text"}', null=True,
|
||||||
|
verbose_name='description'),
|
||||||
|
),
|
||||||
|
]
|
||||||
14
apps/collection/migrations/0026_merge_20191217_1151.py
Normal file
14
apps/collection/migrations/0026_merge_20191217_1151.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-12-17 11:51
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('collection', '0024_auto_20191213_0859'),
|
||||||
|
('collection', '0025_collection_description'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
]
|
||||||
27
apps/collection/migrations/0027_auto_20191218_0753.py
Normal file
27
apps/collection/migrations/0027_auto_20191218_0753.py
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-12-18 07:53
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('gallery', '0007_auto_20191211_1528'),
|
||||||
|
('collection', '0026_merge_20191217_1151'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='advertorial',
|
||||||
|
name='gallery',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='guideelement',
|
||||||
|
name='label_photo',
|
||||||
|
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='gallery.Image', verbose_name='label photo'),
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='AdvertorialGallery',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -6,10 +6,12 @@ from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from utils.models import ProjectBaseMixin, URLImageMixin
|
from utils.models import (
|
||||||
from utils.models import TJSONField
|
ProjectBaseMixin, TJSONField, TranslatedFieldsMixin,
|
||||||
from utils.models import TranslatedFieldsMixin
|
URLImageMixin,
|
||||||
|
)
|
||||||
from utils.querysets import RelatedObjectsCountMixin
|
from utils.querysets import RelatedObjectsCountMixin
|
||||||
|
from utils.models import IntermediateGalleryModelMixin, GalleryModelMixin
|
||||||
|
|
||||||
|
|
||||||
# Mixins
|
# Mixins
|
||||||
|
|
@ -24,7 +26,8 @@ class CollectionNameMixin(models.Model):
|
||||||
|
|
||||||
class CollectionDateMixin(models.Model):
|
class CollectionDateMixin(models.Model):
|
||||||
"""CollectionDate mixin"""
|
"""CollectionDate mixin"""
|
||||||
start = models.DateTimeField(_('start'))
|
start = models.DateTimeField(blank=True, null=True, default=None,
|
||||||
|
verbose_name=_('start'))
|
||||||
end = models.DateTimeField(blank=True, null=True, default=None,
|
end = models.DateTimeField(blank=True, null=True, default=None,
|
||||||
verbose_name=_('end'))
|
verbose_name=_('end'))
|
||||||
|
|
||||||
|
|
@ -80,6 +83,8 @@ class Collection(ProjectBaseMixin, CollectionDateMixin,
|
||||||
verbose_name=_('Collection slug'), editable=True, null=True)
|
verbose_name=_('Collection slug'), editable=True, null=True)
|
||||||
old_id = models.IntegerField(null=True, blank=True)
|
old_id = models.IntegerField(null=True, blank=True)
|
||||||
|
|
||||||
|
rank = models.IntegerField(null=True, default=None)
|
||||||
|
|
||||||
objects = CollectionQuerySet.as_manager()
|
objects = CollectionQuerySet.as_manager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -108,20 +113,32 @@ class Collection(ProjectBaseMixin, CollectionDateMixin,
|
||||||
@property
|
@property
|
||||||
def related_object_names(self) -> list:
|
def related_object_names(self) -> list:
|
||||||
"""Return related object names."""
|
"""Return related object names."""
|
||||||
raw_object_names = []
|
raw_objects = []
|
||||||
for related_object in [related_object.name for related_object in self._related_objects]:
|
for related_object in [related_object.name for related_object in self._related_objects]:
|
||||||
instances = getattr(self, f'{related_object}')
|
instances = getattr(self, f'{related_object}')
|
||||||
if instances.exists():
|
if instances.exists():
|
||||||
for instance in instances.all():
|
for instance in instances.all():
|
||||||
raw_object_names.append(instance.slug if hasattr(instance, 'slug') else None)
|
raw_object = (instance.id, instance.slug) if hasattr(instance, 'slug') else (
|
||||||
|
instance.id, None
|
||||||
|
)
|
||||||
|
raw_objects.append(raw_object)
|
||||||
|
|
||||||
# parse slugs
|
# parse slugs
|
||||||
object_names = []
|
related_objects = []
|
||||||
|
object_names = set()
|
||||||
re_pattern = r'[\w]+'
|
re_pattern = r'[\w]+'
|
||||||
for raw_name in raw_object_names:
|
for object_id, raw_name, in raw_objects:
|
||||||
result = re.findall(re_pattern, raw_name)
|
result = re.findall(re_pattern, raw_name)
|
||||||
if result: object_names.append(' '.join(result).capitalize())
|
if result:
|
||||||
return set(object_names)
|
name = ' '.join(result).capitalize()
|
||||||
|
if name not in object_names:
|
||||||
|
related_objects.append({
|
||||||
|
'id': object_id,
|
||||||
|
'name': name
|
||||||
|
})
|
||||||
|
object_names.add(name)
|
||||||
|
|
||||||
|
return related_objects
|
||||||
|
|
||||||
|
|
||||||
class GuideTypeQuerySet(models.QuerySet):
|
class GuideTypeQuerySet(models.QuerySet):
|
||||||
|
|
@ -194,6 +211,17 @@ class Guide(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin):
|
||||||
"""String method."""
|
"""String method."""
|
||||||
return f'{self.name}'
|
return f'{self.name}'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def entities(self):
|
||||||
|
"""Return entities and its count."""
|
||||||
|
# todo: to work
|
||||||
|
return {
|
||||||
|
'Current': 0,
|
||||||
|
'Initial': 0,
|
||||||
|
'Restaurants': 0,
|
||||||
|
'Shops': 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class AdvertorialQuerySet(models.QuerySet):
|
class AdvertorialQuerySet(models.QuerySet):
|
||||||
"""QuerySet for model Advertorial."""
|
"""QuerySet for model Advertorial."""
|
||||||
|
|
@ -364,6 +392,9 @@ class GuideElement(ProjectBaseMixin, MPTTModel):
|
||||||
parent = TreeForeignKey('self', on_delete=models.CASCADE,
|
parent = TreeForeignKey('self', on_delete=models.CASCADE,
|
||||||
null=True, blank=True,
|
null=True, blank=True,
|
||||||
related_name='children')
|
related_name='children')
|
||||||
|
label_photo = models.ForeignKey('gallery.Image', on_delete=models.SET_NULL,
|
||||||
|
null=True, blank=True, default=None,
|
||||||
|
verbose_name=_('label photo'))
|
||||||
old_id = models.PositiveIntegerField(blank=True, null=True, default=None,
|
old_id = models.PositiveIntegerField(blank=True, null=True, default=None,
|
||||||
verbose_name=_('old id'))
|
verbose_name=_('old id'))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
from .back import *
|
||||||
|
from .web import *
|
||||||
|
from .common import *
|
||||||
|
|
@ -7,7 +7,8 @@ from location.models import Country
|
||||||
from location.serializers import CountrySimpleSerializer
|
from location.serializers import CountrySimpleSerializer
|
||||||
from product.models import Product
|
from product.models import Product
|
||||||
from utils.exceptions import (
|
from utils.exceptions import (
|
||||||
BindingObjectNotFound, RemovedBindingObjectNotFound, ObjectAlreadyAdded
|
BindingObjectNotFound, ObjectAlreadyAdded,
|
||||||
|
RemovedBindingObjectNotFound,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -33,13 +34,14 @@ class CollectionBackOfficeSerializer(CollectionBaseSerializer):
|
||||||
'on_top',
|
'on_top',
|
||||||
'country',
|
'country',
|
||||||
'country_id',
|
'country_id',
|
||||||
'block_size',
|
# 'block_size',
|
||||||
'description',
|
'description',
|
||||||
'slug',
|
'slug',
|
||||||
'start',
|
# 'start',
|
||||||
'end',
|
# 'end',
|
||||||
'count_related_objects',
|
'count_related_objects',
|
||||||
'related_object_names',
|
'related_object_names',
|
||||||
|
'rank',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -68,15 +70,15 @@ class CollectionBindObjectSerializer(serializers.Serializer):
|
||||||
attrs['collection'] = collection
|
attrs['collection'] = collection
|
||||||
|
|
||||||
if obj_type == self.ESTABLISHMENT:
|
if obj_type == self.ESTABLISHMENT:
|
||||||
establishment = Establishment.objects.filter(pk=obj_id).\
|
establishment = Establishment.objects.filter(pk=obj_id). \
|
||||||
first()
|
first()
|
||||||
if not establishment:
|
if not establishment:
|
||||||
raise BindingObjectNotFound()
|
raise BindingObjectNotFound()
|
||||||
if request.method == 'POST' and collection.establishments.\
|
if request.method == 'POST' and collection.establishments. \
|
||||||
filter(pk=establishment.pk).exists():
|
filter(pk=establishment.pk).exists():
|
||||||
raise ObjectAlreadyAdded()
|
raise ObjectAlreadyAdded()
|
||||||
if request.method == 'DELETE' and not collection.\
|
if request.method == 'DELETE' and not collection. \
|
||||||
establishments.filter(pk=establishment.pk).\
|
establishments.filter(pk=establishment.pk). \
|
||||||
exists():
|
exists():
|
||||||
raise RemovedBindingObjectNotFound()
|
raise RemovedBindingObjectNotFound()
|
||||||
attrs['related_object'] = establishment
|
attrs['related_object'] = establishment
|
||||||
|
|
@ -84,10 +86,10 @@ class CollectionBindObjectSerializer(serializers.Serializer):
|
||||||
product = Product.objects.filter(pk=obj_id).first()
|
product = Product.objects.filter(pk=obj_id).first()
|
||||||
if not product:
|
if not product:
|
||||||
raise BindingObjectNotFound()
|
raise BindingObjectNotFound()
|
||||||
if request.method == 'POST' and collection.products.\
|
if request.method == 'POST' and collection.products. \
|
||||||
filter(pk=product.pk).exists():
|
filter(pk=product.pk).exists():
|
||||||
raise ObjectAlreadyAdded()
|
raise ObjectAlreadyAdded()
|
||||||
if request.method == 'DELETE' and not collection.products.\
|
if request.method == 'DELETE' and not collection.products. \
|
||||||
filter(pk=product.pk).exists():
|
filter(pk=product.pk).exists():
|
||||||
raise RemovedBindingObjectNotFound()
|
raise RemovedBindingObjectNotFound()
|
||||||
attrs['related_object'] = product
|
attrs['related_object'] = product
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ from rest_framework import serializers
|
||||||
|
|
||||||
from collection import models
|
from collection import models
|
||||||
from location import models as location_models
|
from location import models as location_models
|
||||||
|
from main.serializers import SiteShortSerializer
|
||||||
from utils.serializers import TranslatedField
|
from utils.serializers import TranslatedField
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -47,8 +48,28 @@ class CollectionSerializer(CollectionBaseSerializer):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class GuideSerializer(serializers.ModelSerializer):
|
class GuideTypeBaseSerializer(serializers.ModelSerializer):
|
||||||
|
"""GuideType serializer."""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
model = models.GuideType
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class GuideBaseSerializer(serializers.ModelSerializer):
|
||||||
"""Guide serializer"""
|
"""Guide serializer"""
|
||||||
|
state_display = serializers.CharField(source='get_state_display',
|
||||||
|
read_only=True)
|
||||||
|
guide_type_detail = GuideTypeBaseSerializer(read_only=True,
|
||||||
|
source='guide_type')
|
||||||
|
site_detail = SiteShortSerializer(read_only=True,
|
||||||
|
source='site')
|
||||||
|
entities = serializers.DictField(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Guide
|
model = models.Guide
|
||||||
fields = [
|
fields = [
|
||||||
|
|
@ -56,4 +77,60 @@ class GuideSerializer(serializers.ModelSerializer):
|
||||||
'name',
|
'name',
|
||||||
'start',
|
'start',
|
||||||
'end',
|
'end',
|
||||||
|
'vintage',
|
||||||
|
'slug',
|
||||||
|
'guide_type',
|
||||||
|
'guide_type_detail',
|
||||||
|
'site',
|
||||||
|
'site_detail',
|
||||||
|
'state',
|
||||||
|
'state_display',
|
||||||
|
'entities',
|
||||||
]
|
]
|
||||||
|
extra_kwargs = {
|
||||||
|
'guide_type': {'write_only': True},
|
||||||
|
'site': {'write_only': True},
|
||||||
|
'state': {'write_only': True},
|
||||||
|
'start': {'required': True},
|
||||||
|
'slug': {'required': True},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class GuideFilterBaseSerializer(serializers.ModelSerializer):
|
||||||
|
"""GuideFilter serializer"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
model = models.GuideFilter
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'establishment_type_json',
|
||||||
|
'country_json',
|
||||||
|
'region_json',
|
||||||
|
'sub_region_json',
|
||||||
|
'wine_region_json',
|
||||||
|
'with_mark',
|
||||||
|
'locale_json',
|
||||||
|
'max_mark',
|
||||||
|
'min_mark',
|
||||||
|
'review_vintage_json',
|
||||||
|
'review_state_json',
|
||||||
|
'guide',
|
||||||
|
]
|
||||||
|
extra_kwargs = {
|
||||||
|
'guide': {'write_only': True}
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def request_kwargs(self):
|
||||||
|
"""Get url kwargs from request."""
|
||||||
|
return self.context.get('request').parser_context.get('kwargs')
|
||||||
|
|
||||||
|
def get_guide(self):
|
||||||
|
"""Get guide instance from kwargs."""
|
||||||
|
return self.request_kwargs.get()
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
"""Overridden create method."""
|
||||||
|
validated_data['guide'] = self.get_guide(validated_data.pop('guide', None))
|
||||||
|
return super().create(validated_data)
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,19 @@
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
|
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
from establishment.models import Establishment
|
|
||||||
from review.models import Review
|
|
||||||
from location.models import WineRegion, City
|
|
||||||
from product.models import Product
|
|
||||||
from transfer.models import Guides, GuideFilters, GuideSections, GuideElements, \
|
|
||||||
GuideAds
|
|
||||||
from transfer.serializers.guide import GuideSerializer, GuideFilterSerializer
|
|
||||||
from collection.models import GuideElementSection, GuideElementSectionCategory, \
|
from collection.models import GuideElementSection, GuideElementSectionCategory, \
|
||||||
GuideWineColorSection, GuideElementType, GuideElement, \
|
GuideWineColorSection, GuideElementType, GuideElement, \
|
||||||
Guide, Advertorial
|
Guide, Advertorial
|
||||||
|
from establishment.models import Establishment
|
||||||
|
from gallery.models import Image
|
||||||
|
from location.models import WineRegion, City
|
||||||
|
from product.models import Product
|
||||||
|
from review.models import Review
|
||||||
|
from transfer.models import Guides, GuideFilters, GuideSections, GuideElements, \
|
||||||
|
GuideAds, LabelPhotos
|
||||||
|
from transfer.serializers.guide import GuideSerializer, GuideFilterSerializer
|
||||||
|
from django.db.models import Subquery
|
||||||
|
|
||||||
|
|
||||||
def transfer_guide():
|
def transfer_guide():
|
||||||
|
|
@ -252,7 +256,7 @@ def transfer_guide_element_advertorials():
|
||||||
qs = GuideElement.objects.filter(old_id=old_id)
|
qs = GuideElement.objects.filter(old_id=old_id)
|
||||||
legacy_qs = GuideElements.objects.exclude(guide__isnull=True) \
|
legacy_qs = GuideElements.objects.exclude(guide__isnull=True) \
|
||||||
.exclude(guide__title__icontains='test') \
|
.exclude(guide__title__icontains='test') \
|
||||||
.filter(id=guide_ad_node_id)
|
.filter(id=old_id)
|
||||||
if qs.exists() and legacy_qs.exists():
|
if qs.exists() and legacy_qs.exists():
|
||||||
return qs.first()
|
return qs.first()
|
||||||
elif legacy_qs.exists() and not qs.exists():
|
elif legacy_qs.exists() and not qs.exists():
|
||||||
|
|
@ -285,6 +289,55 @@ def transfer_guide_element_advertorials():
|
||||||
print(f'COUNT OF CREATED OBJECTS: {len(objects_to_update)}')
|
print(f'COUNT OF CREATED OBJECTS: {len(objects_to_update)}')
|
||||||
|
|
||||||
|
|
||||||
|
def transfer_guide_element_label_photo():
|
||||||
|
"""Transfer galleries for Guide Advertorial model."""
|
||||||
|
def get_guide_element(guide_ad):
|
||||||
|
legacy_guide_element_id = guide_ad.guide_ad_node.id
|
||||||
|
|
||||||
|
legacy_guide_element_qs = GuideElements.objects.filter(id=legacy_guide_element_id)
|
||||||
|
guide_element_qs = GuideElement.objects.filter(old_id=legacy_guide_element_id)
|
||||||
|
|
||||||
|
if guide_element_qs.exists() and legacy_guide_element_qs.exists():
|
||||||
|
return guide_element_qs.first()
|
||||||
|
else:
|
||||||
|
raise ValueError(f'Guide element was not transfer correctly - '
|
||||||
|
f'{legacy_guide_element_id}.')
|
||||||
|
|
||||||
|
to_update = []
|
||||||
|
not_updated = 0
|
||||||
|
guide_element_label_photos = LabelPhotos.objects.exclude(guide_ad__isnull=True) \
|
||||||
|
.filter(guide_ad__type='GuideAdLabel') \
|
||||||
|
.distinct() \
|
||||||
|
.values_list('guide_ad', 'attachment_suffix_url')
|
||||||
|
for guide_ad_id, attachment_suffix_url in tqdm(guide_element_label_photos):
|
||||||
|
legacy_guide_element_ids = Subquery(
|
||||||
|
GuideElements.objects.exclude(guide__isnull=True)
|
||||||
|
.exclude(guide__title__icontains='test')
|
||||||
|
.values_list('id', flat=True)
|
||||||
|
)
|
||||||
|
legacy_guide_ad_qs = GuideAds.objects.filter(id=guide_ad_id,
|
||||||
|
guide_ad_node_id__in=legacy_guide_element_ids)
|
||||||
|
if legacy_guide_ad_qs.exists():
|
||||||
|
guide_element = get_guide_element(legacy_guide_ad_qs.first())
|
||||||
|
if guide_element:
|
||||||
|
image, _ = Image.objects.get_or_create(image=attachment_suffix_url,
|
||||||
|
defaults={
|
||||||
|
'image': attachment_suffix_url,
|
||||||
|
'orientation': Image.HORIZONTAL,
|
||||||
|
'title': f'{guide_element.__str__()} '
|
||||||
|
f'{guide_element.id} - '
|
||||||
|
f'{attachment_suffix_url}'})
|
||||||
|
if not guide_element.label_photo:
|
||||||
|
guide_element.label_photo = image
|
||||||
|
to_update.append(guide_element)
|
||||||
|
else:
|
||||||
|
not_updated += 1
|
||||||
|
|
||||||
|
GuideElement.objects.bulk_update(to_update, ['label_photo', ])
|
||||||
|
print(f'Added label photo to {len(to_update)} objects\n'
|
||||||
|
f'Objects {not_updated} not updated')
|
||||||
|
|
||||||
|
|
||||||
data_types = {
|
data_types = {
|
||||||
'guides': [
|
'guides': [
|
||||||
transfer_guide,
|
transfer_guide,
|
||||||
|
|
@ -305,7 +358,10 @@ data_types = {
|
||||||
transfer_guide_elements_bulk,
|
transfer_guide_elements_bulk,
|
||||||
],
|
],
|
||||||
'guide_element_advertorials': [
|
'guide_element_advertorials': [
|
||||||
transfer_guide_element_advertorials
|
transfer_guide_element_advertorials,
|
||||||
|
],
|
||||||
|
'guide_element_label_photo': [
|
||||||
|
transfer_guide_element_label_photo,
|
||||||
],
|
],
|
||||||
'guide_complete': [
|
'guide_complete': [
|
||||||
transfer_guide, # transfer guides from Guides
|
transfer_guide, # transfer guides from Guides
|
||||||
|
|
@ -315,5 +371,6 @@ data_types = {
|
||||||
transfer_guide_element_type, # partial transfer section types from GuideElements
|
transfer_guide_element_type, # partial transfer section types from GuideElements
|
||||||
transfer_guide_elements_bulk, # transfer result of GuideFilters from GuideElements
|
transfer_guide_elements_bulk, # transfer result of GuideFilters from GuideElements
|
||||||
transfer_guide_element_advertorials, # transfer advertorials that linked to GuideElements
|
transfer_guide_element_advertorials, # transfer advertorials that linked to GuideElements
|
||||||
|
transfer_guide_element_label_photo, # transfer guide element label photos
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,17 @@
|
||||||
"""Collection common urlpaths."""
|
"""Collection common urlpaths."""
|
||||||
from rest_framework.routers import SimpleRouter
|
from rest_framework.routers import SimpleRouter
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
from collection.views import back as views
|
from collection.views import back as views
|
||||||
|
|
||||||
app_name = 'collection'
|
app_name = 'collection'
|
||||||
router = SimpleRouter()
|
|
||||||
router.register(r'', views.CollectionBackOfficeViewSet)
|
|
||||||
|
|
||||||
urlpatterns = router.urls
|
router = SimpleRouter()
|
||||||
|
router.register(r'collections', views.CollectionBackOfficeViewSet)
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('guides/', views.GuideListCreateView.as_view(),
|
||||||
|
name='guide-list-create'),
|
||||||
|
path('guides/<int:pk>/filters/', views.GuideFilterCreateView.as_view(),
|
||||||
|
name='guide-filter-list-create'),
|
||||||
|
] + router.urls
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,7 @@ app_name = 'collection'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.CollectionHomePageView.as_view(), name='list'),
|
path('', views.CollectionHomePageView.as_view(), name='list'),
|
||||||
path('<slug:slug>/', views.CollectionDetailView.as_view(), name='detail'),
|
path('slug/<slug:slug>/', views.CollectionDetailView.as_view(), name='detail'),
|
||||||
path('<slug:slug>/establishments/', views.CollectionEstablishmentListView.as_view(),
|
path('slug/<slug:slug>/establishments/', views.CollectionEstablishmentListView.as_view(),
|
||||||
name='detail'),
|
name='detail'),
|
||||||
|
|
||||||
path('guides/', views.GuideListView.as_view(), name='guides-list'),
|
|
||||||
path('guides/<int:pk>/', views.GuideRetrieveView.as_view(), name='guides-detail'),
|
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
from collection.urls.common import urlpatterns as common_url_patterns
|
from collection.urls.common import urlpatterns as common_url_patterns
|
||||||
|
|
||||||
|
|
||||||
|
app_name = 'web'
|
||||||
|
|
||||||
urlpatterns_api = []
|
urlpatterns_api = []
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
from rest_framework import permissions
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
from rest_framework import viewsets, mixins
|
from rest_framework import generics
|
||||||
|
from rest_framework import mixins, permissions, viewsets
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.filters import OrderingFilter
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from collection import models
|
from collection import models, serializers
|
||||||
from collection.serializers import back as serializers
|
|
||||||
from utils.views import BindObjectMixin
|
from utils.views import BindObjectMixin
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -21,6 +24,22 @@ class CollectionViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|
||||||
|
class GuideBaseView(generics.GenericAPIView):
|
||||||
|
"""ViewSet for Guide model."""
|
||||||
|
pagination_class = None
|
||||||
|
queryset = models.Guide.objects.all()
|
||||||
|
serializer_class = serializers.GuideBaseSerializer
|
||||||
|
permission_classes = (permissions.IsAuthenticated,)
|
||||||
|
|
||||||
|
|
||||||
|
class GuideFilterBaseView(generics.GenericAPIView):
|
||||||
|
"""ViewSet for GuideFilter model."""
|
||||||
|
pagination_class = None
|
||||||
|
queryset = models.GuideFilter.objects.all()
|
||||||
|
serializer_class = serializers.GuideFilterBaseSerializer
|
||||||
|
permission_classes = (permissions.IsAuthenticated,)
|
||||||
|
|
||||||
|
|
||||||
class CollectionBackOfficeViewSet(mixins.CreateModelMixin,
|
class CollectionBackOfficeViewSet(mixins.CreateModelMixin,
|
||||||
mixins.UpdateModelMixin,
|
mixins.UpdateModelMixin,
|
||||||
mixins.DestroyModelMixin,
|
mixins.DestroyModelMixin,
|
||||||
|
|
@ -31,9 +50,13 @@ class CollectionBackOfficeViewSet(mixins.CreateModelMixin,
|
||||||
|
|
||||||
permission_classes = (permissions.IsAuthenticated,)
|
permission_classes = (permissions.IsAuthenticated,)
|
||||||
queryset = models.Collection.objects.all()
|
queryset = models.Collection.objects.all()
|
||||||
|
filter_backends = [DjangoFilterBackend, OrderingFilter]
|
||||||
serializer_class = serializers.CollectionBackOfficeSerializer
|
serializer_class = serializers.CollectionBackOfficeSerializer
|
||||||
bind_object_serializer_class = serializers.CollectionBindObjectSerializer
|
bind_object_serializer_class = serializers.CollectionBindObjectSerializer
|
||||||
|
|
||||||
|
ordering_fields = ('rank', 'start')
|
||||||
|
ordering = ('-start', )
|
||||||
|
|
||||||
def perform_binding(self, serializer):
|
def perform_binding(self, serializer):
|
||||||
data = serializer.validated_data
|
data = serializer.validated_data
|
||||||
collection = data.pop('collection')
|
collection = data.pop('collection')
|
||||||
|
|
@ -53,3 +76,19 @@ class CollectionBackOfficeViewSet(mixins.CreateModelMixin,
|
||||||
collection.establishments.remove(related_object)
|
collection.establishments.remove(related_object)
|
||||||
elif obj_type == self.bind_object_serializer_class.PRODUCT:
|
elif obj_type == self.bind_object_serializer_class.PRODUCT:
|
||||||
collection.products.remove(related_object)
|
collection.products.remove(related_object)
|
||||||
|
|
||||||
|
|
||||||
|
class GuideListCreateView(GuideBaseView,
|
||||||
|
generics.ListCreateAPIView):
|
||||||
|
"""ViewSet for Guide model for BackOffice users."""
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
super().create(request, *args, **kwargs)
|
||||||
|
return Response(status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
class GuideFilterCreateView(GuideFilterBaseView,
|
||||||
|
generics.CreateAPIView):
|
||||||
|
"""ViewSet for GuideFilter model for BackOffice users."""
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
super().create(request, *args, **kwargs)
|
||||||
|
return Response(status=status.HTTP_200_OK)
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ class CollectionViewMixin(generics.GenericAPIView):
|
||||||
"""Override get_queryset method."""
|
"""Override get_queryset method."""
|
||||||
return models.Collection.objects.published() \
|
return models.Collection.objects.published() \
|
||||||
.by_country_code(code=self.request.country_code) \
|
.by_country_code(code=self.request.country_code) \
|
||||||
.order_by('-on_top', '-modified')
|
.order_by('-on_top', '-created')
|
||||||
|
|
||||||
|
|
||||||
class GuideViewMixin(generics.GenericAPIView):
|
class GuideViewMixin(generics.GenericAPIView):
|
||||||
|
|
@ -72,10 +72,10 @@ class CollectionEstablishmentListView(CollectionListView):
|
||||||
class GuideListView(GuideViewMixin, generics.ListAPIView):
|
class GuideListView(GuideViewMixin, generics.ListAPIView):
|
||||||
"""List Guide view"""
|
"""List Guide view"""
|
||||||
permission_classes = (permissions.AllowAny,)
|
permission_classes = (permissions.AllowAny,)
|
||||||
serializer_class = serializers.GuideSerializer
|
serializer_class = serializers.GuideBaseSerializer
|
||||||
|
|
||||||
|
|
||||||
class GuideRetrieveView(GuideViewMixin, generics.RetrieveAPIView):
|
class GuideRetrieveView(GuideViewMixin, generics.RetrieveAPIView):
|
||||||
"""Retrieve Guide view"""
|
"""Retrieve Guide view"""
|
||||||
permission_classes = (permissions.AllowAny,)
|
permission_classes = (permissions.AllowAny,)
|
||||||
serializer_class = serializers.GuideSerializer
|
serializer_class = serializers.GuideBaseSerializer
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
from tqdm import tqdm
|
||||||
from establishment.models import Establishment
|
from establishment.models import Establishment
|
||||||
from transfer.models import Reviews, ReviewTexts
|
from transfer.models import Reviews, ReviewTexts
|
||||||
|
|
||||||
|
|
@ -22,7 +22,7 @@ class Command(BaseCommand):
|
||||||
'updated_at',
|
'updated_at',
|
||||||
)
|
)
|
||||||
|
|
||||||
for r_id, establishment_id, new_date in queryset:
|
for r_id, establishment_id, new_date in tqdm(queryset):
|
||||||
try:
|
try:
|
||||||
review_id, date = valid_reviews[establishment_id]
|
review_id, date = valid_reviews[establishment_id]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
|
@ -41,7 +41,7 @@ class Command(BaseCommand):
|
||||||
'text',
|
'text',
|
||||||
)
|
)
|
||||||
|
|
||||||
for es_id, locale, text in text_qs:
|
for es_id, locale, text in tqdm(text_qs):
|
||||||
establishment = Establishment.objects.filter(old_id=es_id).first()
|
establishment = Establishment.objects.filter(old_id=es_id).first()
|
||||||
if establishment:
|
if establishment:
|
||||||
description = establishment.description
|
description = establishment.description
|
||||||
|
|
@ -53,7 +53,7 @@ class Command(BaseCommand):
|
||||||
count += 1
|
count += 1
|
||||||
|
|
||||||
# Если нет en-GB в поле
|
# Если нет en-GB в поле
|
||||||
for establishment in Establishment.objects.filter(old_id__isnull=False):
|
for establishment in tqdm(Establishment.objects.filter(old_id__isnull=False)):
|
||||||
description = establishment.description
|
description = establishment.description
|
||||||
if len(description) and 'en-GB' not in description:
|
if len(description) and 'en-GB' not in description:
|
||||||
description.update({
|
description.update({
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
from establishment.models import Establishment
|
||||||
|
from transfer.models import Descriptions
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = """Add description to establishment from old db."""
|
||||||
|
|
||||||
|
def handle(self, *args, **kwarg):
|
||||||
|
establishments = Establishment.objects.exclude(old_id__isnull=True)
|
||||||
|
|
||||||
|
self.stdout.write(self.style.WARNING(f'Clear old descriptions'))
|
||||||
|
for item in tqdm(establishments):
|
||||||
|
item.description = None
|
||||||
|
item.save()
|
||||||
|
|
||||||
|
queryset = Descriptions.objects.filter(
|
||||||
|
establishment_id__in=list(establishments.values_list('old_id', flat=True)),
|
||||||
|
).values_list('establishment_id', 'locale', 'text')
|
||||||
|
|
||||||
|
self.stdout.write(self.style.WARNING(f'Update new description'))
|
||||||
|
for establishment_id, locale, text in tqdm(queryset):
|
||||||
|
establishment = Establishment.objects.filter(old_id=establishment_id).first()
|
||||||
|
if establishment:
|
||||||
|
if establishment.description:
|
||||||
|
establishment.description.update({
|
||||||
|
locale: text
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
establishment.description = {locale: text}
|
||||||
|
establishment.save()
|
||||||
|
|
||||||
|
self.stdout.write(self.style.WARNING(f'Update en-GB description'))
|
||||||
|
for establishment in tqdm(establishments.filter(description__isnull=False)):
|
||||||
|
description = establishment.description
|
||||||
|
if len(description) and 'en-GB' not in description:
|
||||||
|
description.update({
|
||||||
|
'en-GB': next(iter(description.values()))
|
||||||
|
})
|
||||||
|
establishment.description = description
|
||||||
|
establishment.save()
|
||||||
|
|
||||||
|
self.stdout.write(self.style.WARNING(f'Done'))
|
||||||
|
|
@ -212,6 +212,10 @@ class EstablishmentQuerySet(models.QuerySet):
|
||||||
output_field=models.FloatField(default=0)
|
output_field=models.FloatField(default=0)
|
||||||
))
|
))
|
||||||
|
|
||||||
|
def has_location(self):
|
||||||
|
"""Return objects with geo location."""
|
||||||
|
return self.filter(address__coordinates__isnull=False)
|
||||||
|
|
||||||
def similar_base(self, establishment):
|
def similar_base(self, establishment):
|
||||||
"""
|
"""
|
||||||
Return filtered QuerySet by base filters.
|
Return filtered QuerySet by base filters.
|
||||||
|
|
@ -267,25 +271,30 @@ class EstablishmentQuerySet(models.QuerySet):
|
||||||
else:
|
else:
|
||||||
return self.none()
|
return self.none()
|
||||||
|
|
||||||
def similar_artisans(self, slug):
|
def same_subtype(self, establishment):
|
||||||
|
"""Annotate flag same subtype."""
|
||||||
|
return self.annotate(same_subtype=Case(
|
||||||
|
models.When(
|
||||||
|
establishment_subtypes__in=establishment.establishment_subtypes.all(),
|
||||||
|
then=True
|
||||||
|
),
|
||||||
|
default=False,
|
||||||
|
output_field=models.BooleanField(default=False)
|
||||||
|
))
|
||||||
|
|
||||||
|
def similar_artisans_producers(self, slug):
|
||||||
"""
|
"""
|
||||||
Return QuerySet with objects that similar to Artisan.
|
Return QuerySet with objects that similar to Artisan/Producer(s).
|
||||||
:param slug: str artisan slug
|
:param slug: str artisan/producer slug
|
||||||
"""
|
"""
|
||||||
artisan_qs = self.filter(slug=slug)
|
establishment_qs = self.filter(slug=slug)
|
||||||
if artisan_qs.exists():
|
if establishment_qs.exists():
|
||||||
artisan = artisan_qs.first()
|
establishment = establishment_qs.first()
|
||||||
ids_by_subquery = self.similar_base_subquery(
|
return self.similar_base(establishment) \
|
||||||
establishment=artisan,
|
.same_subtype(establishment) \
|
||||||
filters={
|
.order_by(F('same_subtype').desc(),
|
||||||
'public_mark__gte': 10,
|
F('distance').asc()) \
|
||||||
}
|
.distinct('same_subtype', 'distance', 'id')
|
||||||
)
|
|
||||||
return self.filter(id__in=ids_by_subquery) \
|
|
||||||
.annotate_intermediate_public_mark() \
|
|
||||||
.annotate_mark_similarity(mark=artisan.public_mark) \
|
|
||||||
.order_by('mark_similarity') \
|
|
||||||
.distinct('mark_similarity', 'id')
|
|
||||||
else:
|
else:
|
||||||
return self.none()
|
return self.none()
|
||||||
|
|
||||||
|
|
@ -541,9 +550,15 @@ class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin,
|
||||||
def visible_tags(self):
|
def visible_tags(self):
|
||||||
return super().visible_tags \
|
return super().visible_tags \
|
||||||
.exclude(category__index_name__in=['guide', 'collection', 'purchased_item',
|
.exclude(category__index_name__in=['guide', 'collection', 'purchased_item',
|
||||||
'business_tag', 'business_tags_de', 'tag'])
|
'business_tag', 'business_tags_de']) \
|
||||||
|
.exclude(value__in=['rss', 'rss_selection'])
|
||||||
# todo: recalculate toque_number
|
# todo: recalculate toque_number
|
||||||
|
|
||||||
|
@property
|
||||||
|
def visible_tags_detail(self):
|
||||||
|
"""Removes some tags from detail Establishment representation"""
|
||||||
|
return self.visible_tags.exclude(category__index_name__in=['tag'])
|
||||||
|
|
||||||
def recalculate_toque_number(self):
|
def recalculate_toque_number(self):
|
||||||
toque_number = 0
|
toque_number = 0
|
||||||
if self.address and self.public_mark:
|
if self.address and self.public_mark:
|
||||||
|
|
|
||||||
|
|
@ -225,18 +225,19 @@ class EstablishmentBackOfficeGallerySerializer(serializers.ModelSerializer):
|
||||||
'is_main',
|
'is_main',
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_request_kwargs(self):
|
@property
|
||||||
|
def request_kwargs(self):
|
||||||
"""Get url kwargs from request."""
|
"""Get url kwargs from request."""
|
||||||
return self.context.get('request').parser_context.get('kwargs')
|
return self.context.get('request').parser_context.get('kwargs')
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
"""Override validate method."""
|
"""Override validate method."""
|
||||||
establishment_pk = self.get_request_kwargs().get('pk')
|
establishment_pk = self.request_kwargs.get('pk')
|
||||||
establishment_slug = self.get_request_kwargs().get('slug')
|
establishment_slug = self.request_kwargs.get('slug')
|
||||||
|
|
||||||
search_kwargs = {'pk': establishment_pk} if establishment_pk else {'slug': establishment_slug}
|
search_kwargs = {'pk': establishment_pk} if establishment_pk else {'slug': establishment_slug}
|
||||||
|
|
||||||
image_id = self.get_request_kwargs().get('image_id')
|
image_id = self.request_kwargs.get('image_id')
|
||||||
|
|
||||||
establishment_qs = models.Establishment.objects.filter(**search_kwargs)
|
establishment_qs = models.Establishment.objects.filter(**search_kwargs)
|
||||||
image_qs = Image.objects.filter(id=image_id)
|
image_qs = Image.objects.filter(id=image_id)
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,14 @@ urlpatterns = [
|
||||||
path('slug/<slug:slug>/favorites/', views.EstablishmentFavoritesCreateDestroyView.as_view(),
|
path('slug/<slug:slug>/favorites/', views.EstablishmentFavoritesCreateDestroyView.as_view(),
|
||||||
name='create-destroy-favorites'),
|
name='create-destroy-favorites'),
|
||||||
|
|
||||||
# similar establishments
|
# similar establishments by type/subtype
|
||||||
path('slug/<slug:slug>/similar/', views.RestaurantSimilarListView.as_view(),
|
path('slug/<slug:slug>/similar/', views.RestaurantSimilarListView.as_view(),
|
||||||
name='similar-restaurants'),
|
name='similar-restaurants'),
|
||||||
path('slug/<slug:slug>/similar/wineries/', views.WinerySimilarListView.as_view(),
|
path('slug/<slug:slug>/similar/wineries/', views.WinerySimilarListView.as_view(),
|
||||||
name='similar-wineries'),
|
name='similar-wineries'),
|
||||||
path('slug/<slug:slug>/similar/artisans/', views.ArtisanSimilarListView.as_view(),
|
# temporary uses single mechanism, bec. description in process
|
||||||
|
path('slug/<slug:slug>/similar/artisans/', views.ArtisanProducerSimilarListView.as_view(),
|
||||||
name='similar-artisans'),
|
name='similar-artisans'),
|
||||||
|
path('slug/<slug:slug>/similar/producers/', views.ArtisanProducerSimilarListView.as_view(),
|
||||||
|
name='similar-producers'),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ from comment import models as comment_models
|
||||||
from comment.serializers import CommentRUDSerializer
|
from comment.serializers import CommentRUDSerializer
|
||||||
from establishment import filters, models, serializers
|
from establishment import filters, models, serializers
|
||||||
from main import methods
|
from main import methods
|
||||||
from utils.pagination import EstablishmentPortionPagination
|
from utils.pagination import PortionPagination
|
||||||
from utils.views import FavoritesCreateDestroyMixinView, CarouselCreateDestroyMixinView
|
from utils.views import FavoritesCreateDestroyMixinView, CarouselCreateDestroyMixinView
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -41,6 +41,12 @@ class EstablishmentListView(EstablishmentMixinView, generics.ListAPIView):
|
||||||
.with_certain_tag_category_related('shop_category', 'artisan_category')
|
.with_certain_tag_category_related('shop_category', 'artisan_category')
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentSimilarView(EstablishmentListView):
|
||||||
|
"""Resource for getting a list of similar establishments."""
|
||||||
|
serializer_class = serializers.EstablishmentSimilarSerializer
|
||||||
|
pagination_class = PortionPagination
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentRetrieveView(EstablishmentMixinView, generics.RetrieveAPIView):
|
class EstablishmentRetrieveView(EstablishmentMixinView, generics.RetrieveAPIView):
|
||||||
"""Resource for getting a establishment."""
|
"""Resource for getting a establishment."""
|
||||||
|
|
||||||
|
|
@ -61,7 +67,7 @@ class EstablishmentMobileRetrieveView(EstablishmentRetrieveView):
|
||||||
class EstablishmentRecentReviewListView(EstablishmentListView):
|
class EstablishmentRecentReviewListView(EstablishmentListView):
|
||||||
"""List view for last reviewed establishments."""
|
"""List view for last reviewed establishments."""
|
||||||
|
|
||||||
pagination_class = EstablishmentPortionPagination
|
pagination_class = PortionPagination
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Overridden method 'get_queryset'."""
|
"""Overridden method 'get_queryset'."""
|
||||||
|
|
@ -77,37 +83,34 @@ class EstablishmentRecentReviewListView(EstablishmentListView):
|
||||||
return qs.last_reviewed(point=point)
|
return qs.last_reviewed(point=point)
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentSimilarList(EstablishmentListView):
|
class RestaurantSimilarListView(EstablishmentSimilarView):
|
||||||
"""Resource for getting a list of similar establishments."""
|
|
||||||
serializer_class = serializers.EstablishmentSimilarSerializer
|
|
||||||
pagination_class = EstablishmentPortionPagination
|
|
||||||
|
|
||||||
|
|
||||||
class RestaurantSimilarListView(EstablishmentSimilarList):
|
|
||||||
"""Resource for getting a list of similar restaurants."""
|
"""Resource for getting a list of similar restaurants."""
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Overridden get_queryset method"""
|
"""Overridden get_queryset method"""
|
||||||
return EstablishmentMixinView.get_queryset(self) \
|
return EstablishmentMixinView.get_queryset(self) \
|
||||||
|
.has_location() \
|
||||||
.similar_restaurants(slug=self.kwargs.get('slug'))
|
.similar_restaurants(slug=self.kwargs.get('slug'))
|
||||||
|
|
||||||
|
|
||||||
class WinerySimilarListView(EstablishmentSimilarList):
|
class WinerySimilarListView(EstablishmentSimilarView):
|
||||||
"""Resource for getting a list of similar wineries."""
|
"""Resource for getting a list of similar wineries."""
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Overridden get_queryset method"""
|
"""Overridden get_queryset method"""
|
||||||
return EstablishmentMixinView.get_queryset(self) \
|
return EstablishmentMixinView.get_queryset(self) \
|
||||||
|
.has_location() \
|
||||||
.similar_wineries(slug=self.kwargs.get('slug'))
|
.similar_wineries(slug=self.kwargs.get('slug'))
|
||||||
|
|
||||||
|
|
||||||
class ArtisanSimilarListView(EstablishmentSimilarList):
|
class ArtisanProducerSimilarListView(EstablishmentSimilarView):
|
||||||
"""Resource for getting a list of similar artisans."""
|
"""Resource for getting a list of similar artisan/producer(s)."""
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Overridden get_queryset method"""
|
"""Overridden get_queryset method"""
|
||||||
return EstablishmentMixinView.get_queryset(self) \
|
return EstablishmentMixinView.get_queryset(self) \
|
||||||
.similar_artisans(slug=self.kwargs.get('slug'))
|
.has_location() \
|
||||||
|
.similar_artisans_producers(slug=self.kwargs.get('slug'))
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentTypeListView(generics.ListAPIView):
|
class EstablishmentTypeListView(generics.ListAPIView):
|
||||||
|
|
|
||||||
|
|
@ -35,14 +35,15 @@ class CityGallerySerializer(serializers.ModelSerializer):
|
||||||
'is_main',
|
'is_main',
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_request_kwargs(self):
|
@property
|
||||||
|
def request_kwargs(self):
|
||||||
"""Get url kwargs from request."""
|
"""Get url kwargs from request."""
|
||||||
return self.context.get('request').parser_context.get('kwargs')
|
return self.context.get('request').parser_context.get('kwargs')
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
"""Override validate method."""
|
"""Override validate method."""
|
||||||
city_pk = self.get_request_kwargs().get('pk')
|
city_pk = self.request_kwargs.get('pk')
|
||||||
image_id = self.get_request_kwargs().get('image_id')
|
image_id = self.request_kwargs.get('image_id')
|
||||||
|
|
||||||
city_qs = models.City.objects.filter(pk=city_pk)
|
city_qs = models.City.objects.filter(pk=city_pk)
|
||||||
image_qs = Image.objects.filter(id=image_id)
|
image_qs = Image.objects.filter(id=image_id)
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ class RegionSerializer(serializers.ModelSerializer):
|
||||||
'country_id'
|
'country_id'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class CityShortSerializer(serializers.ModelSerializer):
|
class CityShortSerializer(serializers.ModelSerializer):
|
||||||
"""Short city serializer"""
|
"""Short city serializer"""
|
||||||
country = CountrySerializer(read_only=True)
|
country = CountrySerializer(read_only=True)
|
||||||
|
|
@ -89,7 +90,6 @@ class CitySerializer(serializers.ModelSerializer):
|
||||||
fields = [
|
fields = [
|
||||||
'id',
|
'id',
|
||||||
'name',
|
'name',
|
||||||
'code',
|
|
||||||
'region',
|
'region',
|
||||||
'region_id',
|
'region_id',
|
||||||
'country_id',
|
'country_id',
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
"""Location app views."""
|
"""Location app views."""
|
||||||
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
from rest_framework import generics
|
from rest_framework import generics
|
||||||
|
|
||||||
from location import models, serializers
|
from location import models, serializers
|
||||||
|
|
@ -11,6 +12,7 @@ from utils.serializers import ImageBaseSerializer
|
||||||
|
|
||||||
from location import filters
|
from location import filters
|
||||||
|
|
||||||
|
|
||||||
# Address
|
# Address
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -18,29 +20,36 @@ class AddressListCreateView(common.AddressViewMixin, generics.ListCreateAPIView)
|
||||||
"""Create view for model Address."""
|
"""Create view for model Address."""
|
||||||
serializer_class = serializers.AddressDetailSerializer
|
serializer_class = serializers.AddressDetailSerializer
|
||||||
queryset = models.Address.objects.all()
|
queryset = models.Address.objects.all()
|
||||||
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
|
permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin]
|
||||||
|
|
||||||
|
|
||||||
class AddressRUDView(common.AddressViewMixin, generics.RetrieveUpdateDestroyAPIView):
|
class AddressRUDView(common.AddressViewMixin, generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""RUD view for model Address."""
|
"""RUD view for model Address."""
|
||||||
serializer_class = serializers.AddressDetailSerializer
|
serializer_class = serializers.AddressDetailSerializer
|
||||||
queryset = models.Address.objects.all()
|
queryset = models.Address.objects.all()
|
||||||
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
|
permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin]
|
||||||
|
|
||||||
|
|
||||||
# City
|
# City
|
||||||
class CityListCreateView(common.CityViewMixin, generics.ListCreateAPIView):
|
class CityListCreateView(common.CityViewMixin, generics.ListCreateAPIView):
|
||||||
"""Create view for model City."""
|
"""Create view for model City."""
|
||||||
serializer_class = serializers.CitySerializer
|
serializer_class = serializers.CitySerializer
|
||||||
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
|
permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin]
|
||||||
queryset = models.City.objects.all()
|
# queryset = models.City.objects.all()
|
||||||
filter_class = filters.CityBackFilter
|
filter_class = filters.CityBackFilter
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
"""Overridden method 'get_queryset'."""
|
||||||
|
qs = models.City.objects.all()
|
||||||
|
if self.request.country_code:
|
||||||
|
qs = qs.by_country_code(self.request.country_code)
|
||||||
|
return qs
|
||||||
|
|
||||||
|
|
||||||
class CityListSearchView(common.CityViewMixin, generics.ListCreateAPIView):
|
class CityListSearchView(common.CityViewMixin, generics.ListCreateAPIView):
|
||||||
"""Create view for model City."""
|
"""Create view for model City."""
|
||||||
serializer_class = serializers.CitySerializer
|
serializer_class = serializers.CitySerializer
|
||||||
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
|
permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin]
|
||||||
queryset = models.City.objects.all()
|
queryset = models.City.objects.all()
|
||||||
filter_class = filters.CityBackFilter
|
filter_class = filters.CityBackFilter
|
||||||
pagination_class = None
|
pagination_class = None
|
||||||
|
|
@ -49,14 +58,14 @@ class CityListSearchView(common.CityViewMixin, generics.ListCreateAPIView):
|
||||||
class CityRUDView(common.CityViewMixin, generics.RetrieveUpdateDestroyAPIView):
|
class CityRUDView(common.CityViewMixin, generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""RUD view for model City."""
|
"""RUD view for model City."""
|
||||||
serializer_class = serializers.CitySerializer
|
serializer_class = serializers.CitySerializer
|
||||||
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
|
permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin]
|
||||||
|
|
||||||
|
|
||||||
class CityGalleryCreateDestroyView(common.CityViewMixin,
|
class CityGalleryCreateDestroyView(common.CityViewMixin,
|
||||||
CreateDestroyGalleryViewMixin):
|
CreateDestroyGalleryViewMixin):
|
||||||
"""Resource for a create gallery for product for back-office users."""
|
"""Resource for a create gallery for product for back-office users."""
|
||||||
serializer_class = serializers.CityGallerySerializer
|
serializer_class = serializers.CityGallerySerializer
|
||||||
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
|
permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin]
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -77,7 +86,7 @@ class CityGalleryListView(common.CityViewMixin,
|
||||||
generics.ListAPIView):
|
generics.ListAPIView):
|
||||||
"""Resource for returning gallery for product for back-office users."""
|
"""Resource for returning gallery for product for back-office users."""
|
||||||
serializer_class = ImageBaseSerializer
|
serializer_class = ImageBaseSerializer
|
||||||
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
|
permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin]
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
"""Override get_object method."""
|
"""Override get_object method."""
|
||||||
|
|
@ -99,13 +108,18 @@ class RegionListCreateView(common.RegionViewMixin, generics.ListCreateAPIView):
|
||||||
"""Create view for model Region"""
|
"""Create view for model Region"""
|
||||||
pagination_class = None
|
pagination_class = None
|
||||||
serializer_class = serializers.RegionSerializer
|
serializer_class = serializers.RegionSerializer
|
||||||
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
|
permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin]
|
||||||
|
filter_backends = (DjangoFilterBackend,)
|
||||||
|
ordering_fields = '__all__'
|
||||||
|
filterset_fields = (
|
||||||
|
'country',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class RegionRUDView(common.RegionViewMixin, generics.RetrieveUpdateDestroyAPIView):
|
class RegionRUDView(common.RegionViewMixin, generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Retrieve view for model Region"""
|
"""Retrieve view for model Region"""
|
||||||
serializer_class = serializers.RegionSerializer
|
serializer_class = serializers.RegionSerializer
|
||||||
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
|
permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin]
|
||||||
|
|
||||||
|
|
||||||
# Country
|
# Country
|
||||||
|
|
@ -114,11 +128,11 @@ class CountryListCreateView(generics.ListCreateAPIView):
|
||||||
queryset = models.Country.objects.all()
|
queryset = models.Country.objects.all()
|
||||||
serializer_class = serializers.CountryBackSerializer
|
serializer_class = serializers.CountryBackSerializer
|
||||||
pagination_class = None
|
pagination_class = None
|
||||||
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
|
permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin]
|
||||||
|
|
||||||
|
|
||||||
class CountryRUDView(generics.RetrieveUpdateDestroyAPIView):
|
class CountryRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""RUD view for model Country."""
|
"""RUD view for model Country."""
|
||||||
serializer_class = serializers.CountryBackSerializer
|
serializer_class = serializers.CountryBackSerializer
|
||||||
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
|
permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin]
|
||||||
queryset = models.Country.objects.all()
|
queryset = models.Country.objects.all()
|
||||||
|
|
@ -11,7 +11,7 @@ class SiteSettingsInline(admin.TabularInline):
|
||||||
@admin.register(models.SiteSettings)
|
@admin.register(models.SiteSettings)
|
||||||
class SiteSettingsAdmin(admin.ModelAdmin):
|
class SiteSettingsAdmin(admin.ModelAdmin):
|
||||||
"""Site settings admin conf."""
|
"""Site settings admin conf."""
|
||||||
inlines = [SiteSettingsInline,]
|
inlines = [SiteSettingsInline, ]
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.Feature)
|
@admin.register(models.Feature)
|
||||||
|
|
@ -62,6 +62,11 @@ class FooterAdmin(admin.ModelAdmin):
|
||||||
list_display = ('id', 'site', )
|
list_display = ('id', 'site', )
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.FooterLink)
|
||||||
|
class FooterLinkAdmin(admin.ModelAdmin):
|
||||||
|
"""FooterLink admin."""
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.Panel)
|
@admin.register(models.Panel)
|
||||||
class PanelAdmin(admin.ModelAdmin):
|
class PanelAdmin(admin.ModelAdmin):
|
||||||
"""Panel admin."""
|
"""Panel admin."""
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,13 @@ def determine_country_code(request):
|
||||||
return country_code.lower()
|
return country_code.lower()
|
||||||
|
|
||||||
|
|
||||||
|
def determine_country_name(request):
|
||||||
|
"""Determine country name."""
|
||||||
|
META = request.META
|
||||||
|
return META.get('X-GeoIP-Country-Name',
|
||||||
|
META.get('HTTP_X_GEOIP_COUNTRY_NAME'))
|
||||||
|
|
||||||
|
|
||||||
def determine_coordinates(request):
|
def determine_coordinates(request):
|
||||||
META = request.META
|
META = request.META
|
||||||
longitude = META.get('X-GeoIP-Longitude',
|
longitude = META.get('X-GeoIP-Longitude',
|
||||||
|
|
|
||||||
32
apps/main/migrations/0043_auto_20191217_1120.py
Normal file
32
apps/main/migrations/0043_auto_20191217_1120.py
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-12-17 11:20
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('main', '0042_panel'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='FooterLinks',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')),
|
||||||
|
('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||||
|
('link', models.URLField(verbose_name='link')),
|
||||||
|
('title', models.CharField(max_length=255, verbose_name='title')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='footer',
|
||||||
|
name='links',
|
||||||
|
field=models.ManyToManyField(related_name='link_footer', to='main.FooterLinks', verbose_name='links'),
|
||||||
|
),
|
||||||
|
]
|
||||||
17
apps/main/migrations/0044_auto_20191217_1125.py
Normal file
17
apps/main/migrations/0044_auto_20191217_1125.py
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-12-17 11:25
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('main', '0043_auto_20191217_1120'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameModel(
|
||||||
|
old_name='FooterLinks',
|
||||||
|
new_name='FooterLink',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -283,6 +283,11 @@ class Carousel(models.Model):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def slug(self):
|
def slug(self):
|
||||||
|
if hasattr(self.content_object, 'slugs'):
|
||||||
|
try:
|
||||||
|
return next(iter(self.content_object.slugs.values()))
|
||||||
|
except StopIteration:
|
||||||
|
return None
|
||||||
if hasattr(self.content_object, 'slug'):
|
if hasattr(self.content_object, 'slug'):
|
||||||
return self.content_object.slug
|
return self.content_object.slug
|
||||||
|
|
||||||
|
|
@ -358,6 +363,11 @@ class PageType(ProjectBaseMixin):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class FooterLink(ProjectBaseMixin):
|
||||||
|
link = models.URLField(_('link'))
|
||||||
|
title = models.CharField(_('title'), max_length=255)
|
||||||
|
|
||||||
|
|
||||||
class Footer(ProjectBaseMixin):
|
class Footer(ProjectBaseMixin):
|
||||||
site = models.ForeignKey(
|
site = models.ForeignKey(
|
||||||
'main.SiteSettings', related_name='footers', verbose_name=_('footer'),
|
'main.SiteSettings', related_name='footers', verbose_name=_('footer'),
|
||||||
|
|
@ -365,6 +375,7 @@ class Footer(ProjectBaseMixin):
|
||||||
)
|
)
|
||||||
about_us = models.TextField(_('about_us'))
|
about_us = models.TextField(_('about_us'))
|
||||||
copyright = models.TextField(_('copyright'))
|
copyright = models.TextField(_('copyright'))
|
||||||
|
links = models.ManyToManyField(FooterLink, verbose_name=_('links'), related_name='link_footer')
|
||||||
|
|
||||||
|
|
||||||
class PanelQuerySet(models.QuerySet):
|
class PanelQuerySet(models.QuerySet):
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ from rest_framework.response import Response
|
||||||
|
|
||||||
from main import methods, models, serializers
|
from main import methods, models, serializers
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# class FeatureViewMixin:
|
# class FeatureViewMixin:
|
||||||
# """Feature view mixin."""
|
# """Feature view mixin."""
|
||||||
|
|
@ -85,8 +84,14 @@ class DetermineLocation(generics.GenericAPIView):
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
longitude, latitude = methods.determine_coordinates(request)
|
longitude, latitude = methods.determine_coordinates(request)
|
||||||
city = methods.determine_user_city(request)
|
city = methods.determine_user_city(request)
|
||||||
if longitude and latitude and city:
|
country_name = methods.determine_country_name(request)
|
||||||
return Response(data={'latitude': latitude, 'longitude': longitude, 'city': city})
|
country_code = methods.determine_country_code(request)
|
||||||
else:
|
if longitude and latitude and city and country_name:
|
||||||
|
return Response(data={
|
||||||
|
'latitude': latitude,
|
||||||
|
'longitude': longitude,
|
||||||
|
'city': city,
|
||||||
|
'country_name': country_name,
|
||||||
|
'country_code': country_code,
|
||||||
|
})
|
||||||
raise Http404
|
raise Http404
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,4 +72,6 @@ class NewsListFilterSet(filters.FilterSet):
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
def sort_by_field(self, queryset, name, value):
|
def sort_by_field(self, queryset, name, value):
|
||||||
|
if value == self.SORT_BY_START_CHOICE:
|
||||||
|
return queryset.order_by('-publication_date', '-publication_time')
|
||||||
return queryset.order_by(f'-{value}')
|
return queryset.order_by(f'-{value}')
|
||||||
|
|
|
||||||
37
apps/news/migrations/0043_auto_20191216_1920.py
Normal file
37
apps/news/migrations/0043_auto_20191216_1920.py
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-12-16 19:20
|
||||||
|
|
||||||
|
import django.contrib.postgres.fields.hstore
|
||||||
|
from django.db import migrations, models
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
def fill_uuid(apps, schemaeditor):
|
||||||
|
News = apps.get_model('news', 'News')
|
||||||
|
for news in News.objects.all():
|
||||||
|
news.duplication_uuid = uuid.uuid4()
|
||||||
|
news.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('news', '0042_news_duplication_date'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='news',
|
||||||
|
name='description_to_locale_is_active',
|
||||||
|
field=django.contrib.postgres.fields.hstore.HStoreField(blank=True, default=dict, help_text='{"en-GB": true, "fr-FR": false}', null=True, verbose_name='Is description for certain locale active'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='news',
|
||||||
|
name='duplication_uuid',
|
||||||
|
field=models.UUIDField(default=uuid.uuid4, verbose_name='Field to detect doubles'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='news',
|
||||||
|
name='slugs',
|
||||||
|
field=django.contrib.postgres.fields.hstore.HStoreField(blank=True, default=dict, help_text='{"en-GB":"some slug"}', null=True, verbose_name='Slugs for current news obj'),
|
||||||
|
),
|
||||||
|
migrations.RunPython(fill_uuid, migrations.RunPython.noop),
|
||||||
|
]
|
||||||
18
apps/news/migrations/0044_auto_20191216_2044.py
Normal file
18
apps/news/migrations/0044_auto_20191216_2044.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-12-16 20:44
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('news', '0043_auto_20191216_1920'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='news',
|
||||||
|
old_name='description_to_locale_is_active',
|
||||||
|
new_name='locale_to_description_is_active',
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/news/migrations/0045_news_must_of_the_week.py
Normal file
18
apps/news/migrations/0045_news_must_of_the_week.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-12-17 17:59
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('news', '0044_auto_20191216_2044'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='news',
|
||||||
|
name='must_of_the_week',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
||||||
38
apps/news/migrations/0046_auto_20191218_1437.py
Normal file
38
apps/news/migrations/0046_auto_20191218_1437.py
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-12-18 14:37
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def fill_publication_date_and_time(apps, schema_editor):
|
||||||
|
News = apps.get_model('news', 'News')
|
||||||
|
for news in News.objects.all():
|
||||||
|
if news.start is not None:
|
||||||
|
news.publication_date = news.start.date()
|
||||||
|
news.publication_time = news.start.time()
|
||||||
|
news.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('news', '0045_news_must_of_the_week'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='news',
|
||||||
|
name='publication_date',
|
||||||
|
field=models.DateField(blank=True, help_text='date since when news item is published', null=True, verbose_name='News publication date'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='news',
|
||||||
|
name='publication_time',
|
||||||
|
field=models.TimeField(blank=True, help_text='time since when news item is published', null=True, verbose_name='News publication time'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='news',
|
||||||
|
name='must_of_the_week',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='Show in the carousel'),
|
||||||
|
),
|
||||||
|
migrations.RunPython(fill_publication_date_and_time, migrations.RunPython.noop),
|
||||||
|
]
|
||||||
17
apps/news/migrations/0047_remove_news_start.py
Normal file
17
apps/news/migrations/0047_remove_news_start.py
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-12-18 16:20
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('news', '0046_auto_20191218_1437'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='news',
|
||||||
|
name='start',
|
||||||
|
),
|
||||||
|
]
|
||||||
17
apps/news/migrations/0048_remove_news_must_of_the_week.py
Normal file
17
apps/news/migrations/0048_remove_news_must_of_the_week.py
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-12-19 13:47
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('news', '0047_remove_news_start'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='news',
|
||||||
|
name='must_of_the_week',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -1,18 +1,23 @@
|
||||||
"""News app models."""
|
"""News app models."""
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib.contenttypes import fields as generic
|
from django.contrib.contenttypes import fields as generic
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.contrib.postgres.fields import HStoreField
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Case, When
|
from django.db.models import Case, When
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework.reverse import reverse
|
from rest_framework.reverse import reverse
|
||||||
|
|
||||||
|
from main.models import Carousel
|
||||||
from rating.models import Rating, ViewCount
|
from rating.models import Rating, ViewCount
|
||||||
from utils.models import (BaseAttributes, TJSONField, TranslatedFieldsMixin, HasTagsMixin,
|
from utils.models import (BaseAttributes, TJSONField, TranslatedFieldsMixin, HasTagsMixin,
|
||||||
ProjectBaseMixin, GalleryModelMixin, IntermediateGalleryModelMixin,
|
ProjectBaseMixin, GalleryModelMixin, IntermediateGalleryModelMixin,
|
||||||
FavoritesMixin)
|
FavoritesMixin)
|
||||||
from utils.querysets import TranslationQuerysetMixin
|
from utils.querysets import TranslationQuerysetMixin
|
||||||
from django.conf import settings
|
from datetime import datetime
|
||||||
from django.contrib.postgres.fields import HStoreField
|
|
||||||
|
|
||||||
|
|
||||||
class Agenda(ProjectBaseMixin, TranslatedFieldsMixin):
|
class Agenda(ProjectBaseMixin, TranslatedFieldsMixin):
|
||||||
|
|
@ -62,7 +67,7 @@ class NewsQuerySet(TranslationQuerysetMixin):
|
||||||
|
|
||||||
def sort_by_start(self):
|
def sort_by_start(self):
|
||||||
"""Return qs sorted by start DESC"""
|
"""Return qs sorted by start DESC"""
|
||||||
return self.order_by('-start')
|
return self.order_by('-publication_date', '-publication_time')
|
||||||
|
|
||||||
def rating_value(self):
|
def rating_value(self):
|
||||||
return self.annotate(rating=models.Count('ratings__ip', distinct=True))
|
return self.annotate(rating=models.Count('ratings__ip', distinct=True))
|
||||||
|
|
@ -97,9 +102,13 @@ class NewsQuerySet(TranslationQuerysetMixin):
|
||||||
def published(self):
|
def published(self):
|
||||||
"""Return only published news"""
|
"""Return only published news"""
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
return self.filter(models.Q(models.Q(end__gte=now) |
|
date_now = now.date()
|
||||||
|
time_now = now.time()
|
||||||
|
return self.exclude(models.Q(publication_date__isnull=True) | models.Q(publication_time__isnull=True)). \
|
||||||
|
filter(models.Q(models.Q(end__gte=now) |
|
||||||
models.Q(end__isnull=True)),
|
models.Q(end__isnull=True)),
|
||||||
state__in=self.model.PUBLISHED_STATES, start__lte=now)
|
state__in=self.model.PUBLISHED_STATES, publication_date__lte=date_now,
|
||||||
|
publication_time__lte=time_now)
|
||||||
|
|
||||||
# todo: filter by best score
|
# todo: filter by best score
|
||||||
# todo: filter by country?
|
# todo: filter by country?
|
||||||
|
|
@ -112,7 +121,7 @@ class NewsQuerySet(TranslationQuerysetMixin):
|
||||||
return self.model.objects.exclude(pk=news.pk).published(). \
|
return self.model.objects.exclude(pk=news.pk).published(). \
|
||||||
annotate_in_favorites(user). \
|
annotate_in_favorites(user). \
|
||||||
with_base_related().by_type(news.news_type). \
|
with_base_related().by_type(news.news_type). \
|
||||||
by_tags(news.tags.all()).distinct().order_by('-start')
|
by_tags(news.tags.all()).distinct().sort_by_start()
|
||||||
|
|
||||||
def annotate_in_favorites(self, user):
|
def annotate_in_favorites(self, user):
|
||||||
"""Annotate flag in_favorites"""
|
"""Annotate flag in_favorites"""
|
||||||
|
|
@ -127,6 +136,9 @@ class NewsQuerySet(TranslationQuerysetMixin):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def by_locale(self, locale):
|
||||||
|
return self.filter(title__icontains=locale)
|
||||||
|
|
||||||
|
|
||||||
class News(GalleryModelMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixin,
|
class News(GalleryModelMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixin,
|
||||||
FavoritesMixin):
|
FavoritesMixin):
|
||||||
|
|
@ -177,11 +189,16 @@ class News(GalleryModelMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixi
|
||||||
description = TJSONField(blank=True, null=True, default=None,
|
description = TJSONField(blank=True, null=True, default=None,
|
||||||
verbose_name=_('description'),
|
verbose_name=_('description'),
|
||||||
help_text='{"en-GB":"some text"}')
|
help_text='{"en-GB":"some text"}')
|
||||||
start = models.DateTimeField(blank=True, null=True, default=None,
|
locale_to_description_is_active = HStoreField(null=True, default=dict, blank=True,
|
||||||
verbose_name=_('Start'))
|
verbose_name=_('Is description for certain locale active'),
|
||||||
|
help_text='{"en-GB": true, "fr-FR": false}')
|
||||||
|
publication_date = models.DateField(blank=True, null=True, verbose_name=_('News publication date'),
|
||||||
|
help_text=_('date since when news item is published'))
|
||||||
|
publication_time = models.TimeField(blank=True, null=True, verbose_name=_('News publication time'),
|
||||||
|
help_text=_('time since when news item is published'))
|
||||||
end = models.DateTimeField(blank=True, null=True, default=None,
|
end = models.DateTimeField(blank=True, null=True, default=None,
|
||||||
verbose_name=_('End'))
|
verbose_name=_('End'))
|
||||||
slugs = HStoreField(null=True, blank=True, default=None,
|
slugs = HStoreField(null=True, blank=True, default=dict,
|
||||||
verbose_name=_('Slugs for current news obj'),
|
verbose_name=_('Slugs for current news obj'),
|
||||||
help_text='{"en-GB":"some slug"}')
|
help_text='{"en-GB":"some slug"}')
|
||||||
state = models.PositiveSmallIntegerField(default=WAITING, choices=STATE_CHOICES,
|
state = models.PositiveSmallIntegerField(default=WAITING, choices=STATE_CHOICES,
|
||||||
|
|
@ -213,6 +230,8 @@ class News(GalleryModelMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixi
|
||||||
on_delete=models.SET_NULL, verbose_name=_('site settings'))
|
on_delete=models.SET_NULL, verbose_name=_('site settings'))
|
||||||
duplication_date = models.DateTimeField(blank=True, null=True, default=None,
|
duplication_date = models.DateTimeField(blank=True, null=True, default=None,
|
||||||
verbose_name=_('Duplication datetime'))
|
verbose_name=_('Duplication datetime'))
|
||||||
|
duplication_uuid = models.UUIDField(default=uuid.uuid4, editable=True, unique=False,
|
||||||
|
verbose_name=_('Field to detect doubles'))
|
||||||
objects = NewsQuerySet.as_manager()
|
objects = NewsQuerySet.as_manager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -233,6 +252,34 @@ class News(GalleryModelMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixi
|
||||||
self.duplication_date = timezone.now()
|
self.duplication_date = timezone.now()
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def must_of_the_week(self) -> bool:
|
||||||
|
"""Detects whether current item in carousel"""
|
||||||
|
kwargs = {
|
||||||
|
'content_type': ContentType.objects.get_for_model(self),
|
||||||
|
'object_id': self.pk,
|
||||||
|
'country': self.country,
|
||||||
|
}
|
||||||
|
return Carousel.objects.filter(**kwargs).exists()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def publication_datetime(self):
|
||||||
|
"""Represents datetime object combined from `publication_date` & `publication_time` fields"""
|
||||||
|
try:
|
||||||
|
return datetime.combine(date=self.publication_date, time=self.publication_time)
|
||||||
|
except TypeError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def duplicates(self):
|
||||||
|
"""Duplicates for this news item excluding same country code labeled"""
|
||||||
|
return News.objects.filter(duplication_uuid=self.duplication_uuid).exclude(country=self.country)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def has_any_desc_active(self):
|
||||||
|
"""Detects whether news item has any active description"""
|
||||||
|
return any(list(map(lambda v: v.lower() == 'true', self.locale_to_description_is_active.values())))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_publish(self):
|
def is_publish(self):
|
||||||
return self.state in self.PUBLISHED_STATES
|
return self.state in self.PUBLISHED_STATES
|
||||||
|
|
@ -317,7 +364,6 @@ class News(GalleryModelMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixi
|
||||||
|
|
||||||
|
|
||||||
class NewsGallery(IntermediateGalleryModelMixin):
|
class NewsGallery(IntermediateGalleryModelMixin):
|
||||||
|
|
||||||
news = models.ForeignKey(News, null=True,
|
news = models.ForeignKey(News, null=True,
|
||||||
related_name='news_gallery',
|
related_name='news_gallery',
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
|
|
@ -331,4 +377,4 @@ class NewsGallery(IntermediateGalleryModelMixin):
|
||||||
"""NewsGallery meta class."""
|
"""NewsGallery meta class."""
|
||||||
verbose_name = _('news gallery')
|
verbose_name = _('news gallery')
|
||||||
verbose_name_plural = _('news galleries')
|
verbose_name_plural = _('news galleries')
|
||||||
unique_together = [['news', 'image'],]
|
unique_together = [['news', 'image'], ]
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ from rest_framework.fields import SerializerMethodField
|
||||||
|
|
||||||
from account.serializers.common import UserBaseSerializer
|
from account.serializers.common import UserBaseSerializer
|
||||||
from gallery.models import Image
|
from gallery.models import Image
|
||||||
from main.models import SiteSettings
|
from main.models import SiteSettings, Carousel
|
||||||
from location import models as location_models
|
from location import models as location_models
|
||||||
from location.serializers import CountrySimpleSerializer, AddressBaseSerializer
|
from location.serializers import CountrySimpleSerializer, AddressBaseSerializer
|
||||||
from news import models
|
from news import models
|
||||||
|
|
@ -128,6 +128,7 @@ class NewsDetailSerializer(NewsBaseSerializer):
|
||||||
state_display = serializers.CharField(source='get_state_display',
|
state_display = serializers.CharField(source='get_state_display',
|
||||||
read_only=True)
|
read_only=True)
|
||||||
gallery = ImageBaseSerializer(read_only=True, source='crop_gallery', many=True)
|
gallery = ImageBaseSerializer(read_only=True, source='crop_gallery', many=True)
|
||||||
|
start = serializers.DateTimeField(source='publication_datetime', read_only=True)
|
||||||
|
|
||||||
class Meta(NewsBaseSerializer.Meta):
|
class Meta(NewsBaseSerializer.Meta):
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -182,12 +183,17 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer):
|
||||||
'backoffice_title',
|
'backoffice_title',
|
||||||
'subtitle',
|
'subtitle',
|
||||||
'slugs',
|
'slugs',
|
||||||
|
'locale_to_description_is_active',
|
||||||
'is_published',
|
'is_published',
|
||||||
'duplication_date',
|
'duplication_date',
|
||||||
|
'must_of_the_week',
|
||||||
|
'publication_date',
|
||||||
|
'publication_time',
|
||||||
)
|
)
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'backoffice_title': {'allow_null': False},
|
|
||||||
'duplication_date': {'read_only': True},
|
'duplication_date': {'read_only': True},
|
||||||
|
'locale_to_description_is_active': {'allow_null': False},
|
||||||
|
'must_of_the_week': {'read_only': True},
|
||||||
}
|
}
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
|
|
@ -209,6 +215,20 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer):
|
||||||
return super().update(instance, validated_data)
|
return super().update(instance, validated_data)
|
||||||
|
|
||||||
|
|
||||||
|
class NewsBackOfficeDuplicationInfoSerializer(serializers.ModelSerializer):
|
||||||
|
"""Duplication info for news detail."""
|
||||||
|
|
||||||
|
country = CountrySimpleSerializer(read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.News
|
||||||
|
fields = (
|
||||||
|
'id',
|
||||||
|
'duplication_date',
|
||||||
|
'country',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer,
|
class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer,
|
||||||
NewsDetailSerializer):
|
NewsDetailSerializer):
|
||||||
"""News detail serializer for back-office users."""
|
"""News detail serializer for back-office users."""
|
||||||
|
|
@ -224,6 +244,7 @@ class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer,
|
||||||
queryset=SiteSettings.objects.all())
|
queryset=SiteSettings.objects.all())
|
||||||
template_display = serializers.CharField(source='get_template_display',
|
template_display = serializers.CharField(source='get_template_display',
|
||||||
read_only=True)
|
read_only=True)
|
||||||
|
duplicates = NewsBackOfficeDuplicationInfoSerializer(many=True, allow_null=True, read_only=True)
|
||||||
|
|
||||||
class Meta(NewsBackOfficeBaseSerializer.Meta, NewsDetailSerializer.Meta):
|
class Meta(NewsBackOfficeBaseSerializer.Meta, NewsDetailSerializer.Meta):
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -237,6 +258,7 @@ class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer,
|
||||||
'template',
|
'template',
|
||||||
'template_display',
|
'template_display',
|
||||||
'is_international',
|
'is_international',
|
||||||
|
'duplicates',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -252,13 +274,14 @@ class NewsBackOfficeGallerySerializer(serializers.ModelSerializer):
|
||||||
'is_main',
|
'is_main',
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_request_kwargs(self):
|
@property
|
||||||
|
def request_kwargs(self):
|
||||||
"""Get url kwargs from request."""
|
"""Get url kwargs from request."""
|
||||||
return self.context.get('request').parser_context.get('kwargs')
|
return self.context.get('request').parser_context.get('kwargs')
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
news_pk = self.get_request_kwargs().get('pk')
|
news_pk = self.request_kwargs.get('pk')
|
||||||
image_id = self.get_request_kwargs().get('image_id')
|
image_id = self.request_kwargs.get('image_id')
|
||||||
qs = models.NewsGallery.objects.filter(image_id=image_id, news_id=news_pk)
|
qs = models.NewsGallery.objects.filter(image_id=image_id, news_id=news_pk)
|
||||||
instance = qs.first()
|
instance = qs.first()
|
||||||
if instance:
|
if instance:
|
||||||
|
|
@ -268,8 +291,8 @@ class NewsBackOfficeGallerySerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
"""Override validate method."""
|
"""Override validate method."""
|
||||||
news_pk = self.get_request_kwargs().get('pk')
|
news_pk = self.request_kwargs.get('pk')
|
||||||
image_id = self.get_request_kwargs().get('image_id')
|
image_id = self.request_kwargs.get('image_id')
|
||||||
|
|
||||||
news_qs = models.News.objects.filter(pk=news_pk)
|
news_qs = models.News.objects.filter(pk=news_pk)
|
||||||
image_qs = Image.objects.filter(id=image_id)
|
image_qs = Image.objects.filter(id=image_id)
|
||||||
|
|
@ -337,7 +360,12 @@ class NewsCarouselCreateSerializer(CarouselCreateSerializer):
|
||||||
|
|
||||||
def create(self, validated_data, *args, **kwargs):
|
def create(self, validated_data, *args, **kwargs):
|
||||||
validated_data.update({
|
validated_data.update({
|
||||||
'content_object': validated_data.pop('news')
|
'country': validated_data['news'].country
|
||||||
|
})
|
||||||
|
validated_data.update({
|
||||||
|
'content_object': validated_data.pop('news'),
|
||||||
|
'is_parse': True,
|
||||||
|
'active': True,
|
||||||
})
|
})
|
||||||
return super().create(validated_data)
|
return super().create(validated_data)
|
||||||
|
|
||||||
|
|
@ -347,9 +375,11 @@ class NewsCloneCreateSerializer(NewsBackOfficeBaseSerializer,
|
||||||
"""Serializer for creating news clone."""
|
"""Serializer for creating news clone."""
|
||||||
template_display = serializers.CharField(source='get_template_display',
|
template_display = serializers.CharField(source='get_template_display',
|
||||||
read_only=True)
|
read_only=True)
|
||||||
|
duplicates = NewsBackOfficeDuplicationInfoSerializer(many=True, allow_null=True, read_only=True)
|
||||||
class Meta(NewsBackOfficeBaseSerializer.Meta, NewsDetailSerializer.Meta):
|
class Meta(NewsBackOfficeBaseSerializer.Meta, NewsDetailSerializer.Meta):
|
||||||
fields = NewsBackOfficeBaseSerializer.Meta.fields + NewsDetailSerializer.Meta.fields + (
|
fields = NewsBackOfficeBaseSerializer.Meta.fields + NewsDetailSerializer.Meta.fields + (
|
||||||
'template_display',
|
'template_display',
|
||||||
|
'duplicates',
|
||||||
)
|
)
|
||||||
read_only_fields = fields
|
read_only_fields = fields
|
||||||
|
|
||||||
|
|
@ -359,5 +389,5 @@ class NewsCloneCreateSerializer(NewsBackOfficeBaseSerializer,
|
||||||
new_country = get_object_or_404(location_models.Country, code=kwargs['country_code'])
|
new_country = get_object_or_404(location_models.Country, code=kwargs['country_code'])
|
||||||
view_count_model = rating_models.ViewCount.objects.create(count=0)
|
view_count_model = rating_models.ViewCount.objects.create(count=0)
|
||||||
instance.create_duplicate(new_country, view_count_model)
|
instance.create_duplicate(new_country, view_count_model)
|
||||||
return instance
|
return get_object_or_404(models.News, pk=kwargs['pk'])
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,5 +14,5 @@ urlpatterns = [
|
||||||
path('<int:pk>/gallery/<int:image_id>/', views.NewsBackOfficeGalleryCreateDestroyView.as_view(),
|
path('<int:pk>/gallery/<int:image_id>/', views.NewsBackOfficeGalleryCreateDestroyView.as_view(),
|
||||||
name='gallery-create-destroy'),
|
name='gallery-create-destroy'),
|
||||||
path('<int:pk>/carousels/', views.NewsCarouselCreateDestroyView.as_view(), name='create-destroy-carousels'),
|
path('<int:pk>/carousels/', views.NewsCarouselCreateDestroyView.as_view(), name='create-destroy-carousels'),
|
||||||
path('<int:pk>/clone/<str:country_code>', views.NewsCloneView.as_view(), name='create-destroy-carousels'),
|
path('<int:pk>/clone/<str:country_code>', views.NewsCloneView.as_view(), name='clone-news-item'),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
"""News app views."""
|
"""News app views."""
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from rest_framework import generics, permissions
|
from django.utils import translation
|
||||||
|
from rest_framework import generics, permissions, response
|
||||||
|
|
||||||
from news import filters, models, serializers
|
from news import filters, models, serializers
|
||||||
from rating.tasks import add_rating
|
from rating.tasks import add_rating
|
||||||
|
|
@ -21,7 +22,7 @@ class NewsMixinView:
|
||||||
qs = models.News.objects.published() \
|
qs = models.News.objects.published() \
|
||||||
.with_base_related() \
|
.with_base_related() \
|
||||||
.annotate_in_favorites(self.request.user) \
|
.annotate_in_favorites(self.request.user) \
|
||||||
.order_by('-is_highlighted', '-start')
|
.order_by('-is_highlighted', '-publication_date', '-publication_time')
|
||||||
|
|
||||||
country_code = self.request.country_code
|
country_code = self.request.country_code
|
||||||
if country_code:
|
if country_code:
|
||||||
|
|
@ -29,6 +30,11 @@ class NewsMixinView:
|
||||||
qs = qs.international_news()
|
qs = qs.international_news()
|
||||||
else:
|
else:
|
||||||
qs = qs.by_country_code(country_code)
|
qs = qs.by_country_code(country_code)
|
||||||
|
|
||||||
|
# locale = kwargs.get('locale')
|
||||||
|
# if locale:
|
||||||
|
# qs = qs.by_locale(locale)
|
||||||
|
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
|
|
@ -43,8 +49,13 @@ class NewsListView(NewsMixinView, generics.ListAPIView):
|
||||||
filter_class = filters.NewsListFilterSet
|
filter_class = filters.NewsListFilterSet
|
||||||
|
|
||||||
def get_queryset(self, *args, **kwargs):
|
def get_queryset(self, *args, **kwargs):
|
||||||
kwargs.update({'international_preferred': True})
|
locale = translation.get_language()
|
||||||
return super().get_queryset(*args, **kwargs)
|
kwargs.update({
|
||||||
|
'international_preferred': True,
|
||||||
|
'locale': locale,
|
||||||
|
})
|
||||||
|
return super().get_queryset(*args, **kwargs)\
|
||||||
|
.filter(locale_to_description_is_active__values__contains=['True'])
|
||||||
|
|
||||||
|
|
||||||
class NewsDetailView(NewsMixinView, generics.RetrieveAPIView):
|
class NewsDetailView(NewsMixinView, generics.RetrieveAPIView):
|
||||||
|
|
@ -99,7 +110,10 @@ class NewsBackOfficeLCView(NewsBackOfficeMixinView,
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Override get_queryset method."""
|
"""Override get_queryset method."""
|
||||||
return super().get_queryset().with_extended_related()
|
qs = super().get_queryset().with_extended_related()
|
||||||
|
if self.request.country_code:
|
||||||
|
qs = qs.by_country_code(self.request.country_code)
|
||||||
|
return qs
|
||||||
|
|
||||||
|
|
||||||
class NewsBackOfficeGalleryCreateDestroyView(NewsBackOfficeMixinView,
|
class NewsBackOfficeGalleryCreateDestroyView(NewsBackOfficeMixinView,
|
||||||
|
|
@ -107,6 +121,13 @@ class NewsBackOfficeGalleryCreateDestroyView(NewsBackOfficeMixinView,
|
||||||
"""Resource for a create gallery for news for back-office users."""
|
"""Resource for a create gallery for news for back-office users."""
|
||||||
serializer_class = serializers.NewsBackOfficeGallerySerializer
|
serializer_class = serializers.NewsBackOfficeGallerySerializer
|
||||||
|
|
||||||
|
def create(self, request, *args, **kwargs):
|
||||||
|
_ = super().create(request, *args, **kwargs)
|
||||||
|
news_qs = self.filter_queryset(self.get_queryset())
|
||||||
|
return response.Response(
|
||||||
|
data=serializers.NewsDetailSerializer(get_object_or_404(news_qs, pk=kwargs.get('pk'))).data
|
||||||
|
)
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
"""
|
"""
|
||||||
Returns the object the view is displaying.
|
Returns the object the view is displaying.
|
||||||
|
|
@ -171,6 +192,6 @@ class NewsCarouselCreateDestroyView(CarouselCreateDestroyMixinView):
|
||||||
|
|
||||||
class NewsCloneView(generics.CreateAPIView):
|
class NewsCloneView(generics.CreateAPIView):
|
||||||
"""View for creating clone News"""
|
"""View for creating clone News"""
|
||||||
permission_classes = (permissions.AllowAny, )
|
permission_classes = (permissions.AllowAny,)
|
||||||
serializer_class = serializers.NewsCloneCreateSerializer
|
serializer_class = serializers.NewsCloneCreateSerializer
|
||||||
queryset = models.News.objects.all()
|
queryset = models.News.objects.all()
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,17 @@
|
||||||
"""Product app models."""
|
"""Product app models."""
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib.contenttypes import fields as generic
|
from django.contrib.contenttypes import fields as generic
|
||||||
from django.contrib.gis.db import models as gis_models
|
from django.contrib.gis.db import models as gis_models
|
||||||
|
from django.contrib.gis.db.models.functions import Distance
|
||||||
|
from django.contrib.gis.geos import Point
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Case, When
|
from django.db.models import Case, When, F
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from location.models import WineOriginAddressMixin
|
from location.models import WineOriginAddressMixin
|
||||||
|
from review.models import Review
|
||||||
from utils.models import (BaseAttributes, ProjectBaseMixin, HasTagsMixin,
|
from utils.models import (BaseAttributes, ProjectBaseMixin, HasTagsMixin,
|
||||||
TranslatedFieldsMixin, TJSONField, FavoritesMixin,
|
TranslatedFieldsMixin, TJSONField, FavoritesMixin,
|
||||||
GalleryModelMixin, IntermediateGalleryModelMixin)
|
GalleryModelMixin, IntermediateGalleryModelMixin)
|
||||||
|
|
@ -136,6 +140,60 @@ class ProductQuerySet(models.QuerySet):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def annotate_distance(self, point: Point = None):
|
||||||
|
"""
|
||||||
|
Return QuerySet with annotated field - distance
|
||||||
|
Description:
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self.annotate(distance=Distance('establishment__address__coordinates',
|
||||||
|
point,
|
||||||
|
srid=settings.GEO_DEFAULT_SRID))
|
||||||
|
|
||||||
|
def has_location(self):
|
||||||
|
"""Return objects with geo location."""
|
||||||
|
return self.filter(establishment__address__coordinates__isnull=False)
|
||||||
|
|
||||||
|
def same_subtype(self, product):
|
||||||
|
"""Annotate flag same subtype."""
|
||||||
|
return self.annotate(same_subtype=Case(
|
||||||
|
models.When(
|
||||||
|
subtypes__in=product.subtypes.all(),
|
||||||
|
then=True
|
||||||
|
),
|
||||||
|
default=False,
|
||||||
|
output_field=models.BooleanField(default=False)
|
||||||
|
))
|
||||||
|
|
||||||
|
def similar_base(self, product):
|
||||||
|
"""Return QuerySet filtered by base filters for Product model."""
|
||||||
|
filters = {
|
||||||
|
'reviews__status': Review.READY,
|
||||||
|
'product_type': product.product_type,
|
||||||
|
}
|
||||||
|
if product.subtypes.exists():
|
||||||
|
filters.update(
|
||||||
|
{'subtypes__in': product.subtypes.all()})
|
||||||
|
return self.exclude(id=product.id) \
|
||||||
|
.filter(**filters) \
|
||||||
|
.annotate_distance(point=product.establishment.location)
|
||||||
|
|
||||||
|
def similar(self, slug):
|
||||||
|
"""
|
||||||
|
Return QuerySet with objects that similar to Product.
|
||||||
|
:param slug: str product slug
|
||||||
|
"""
|
||||||
|
product_qs = self.filter(slug=slug)
|
||||||
|
if product_qs.exists():
|
||||||
|
product = product_qs.first()
|
||||||
|
return self.similar_base(product) \
|
||||||
|
.same_subtype(product) \
|
||||||
|
.order_by(F('same_subtype').desc(),
|
||||||
|
F('distance').asc()) \
|
||||||
|
.distinct('same_subtype', 'distance', 'id')
|
||||||
|
else:
|
||||||
|
return self.none()
|
||||||
|
|
||||||
|
|
||||||
class Product(GalleryModelMixin, TranslatedFieldsMixin, BaseAttributes,
|
class Product(GalleryModelMixin, TranslatedFieldsMixin, BaseAttributes,
|
||||||
HasTagsMixin, FavoritesMixin):
|
HasTagsMixin, FavoritesMixin):
|
||||||
|
|
|
||||||
|
|
@ -22,14 +22,15 @@ class ProductBackOfficeGallerySerializer(serializers.ModelSerializer):
|
||||||
'is_main',
|
'is_main',
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_request_kwargs(self):
|
@property
|
||||||
|
def request_kwargs(self):
|
||||||
"""Get url kwargs from request."""
|
"""Get url kwargs from request."""
|
||||||
return self.context.get('request').parser_context.get('kwargs')
|
return self.context.get('request').parser_context.get('kwargs')
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
"""Override validate method."""
|
"""Override validate method."""
|
||||||
product_pk = self.get_request_kwargs().get('pk')
|
product_pk = self.request_kwargs.get('pk')
|
||||||
image_id = self.get_request_kwargs().get('image_id')
|
image_id = self.request_kwargs.get('image_id')
|
||||||
|
|
||||||
product_qs = models.Product.objects.filter(pk=product_pk)
|
product_qs = models.Product.objects.filter(pk=product_pk)
|
||||||
image_qs = Image.objects.filter(id=image_id)
|
image_qs = Image.objects.filter(id=image_id)
|
||||||
|
|
|
||||||
|
|
@ -16,4 +16,13 @@ urlpatterns = [
|
||||||
name='create-comment'),
|
name='create-comment'),
|
||||||
path('slug/<slug:slug>/comments/<int:comment_id>/', views.ProductCommentRUDView.as_view(),
|
path('slug/<slug:slug>/comments/<int:comment_id>/', views.ProductCommentRUDView.as_view(),
|
||||||
name='rud-comment'),
|
name='rud-comment'),
|
||||||
|
|
||||||
|
# similar products by type/subtype
|
||||||
|
# temporary uses single mechanism, bec. description in process
|
||||||
|
path('slug/<slug:slug>/similar/wines/', views.SimilarListView.as_view(),
|
||||||
|
name='similar-wine'),
|
||||||
|
path('slug/<slug:slug>/similar/liquors/', views.SimilarListView.as_view(),
|
||||||
|
name='similar-liquor'),
|
||||||
|
path('slug/<slug:slug>/similar/food/', views.SimilarListView.as_view(),
|
||||||
|
name='similar-food'),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ from comment.models import Comment
|
||||||
from product import filters, serializers
|
from product import filters, serializers
|
||||||
from comment.serializers import CommentRUDSerializer
|
from comment.serializers import CommentRUDSerializer
|
||||||
from utils.views import FavoritesCreateDestroyMixinView
|
from utils.views import FavoritesCreateDestroyMixinView
|
||||||
|
from utils.pagination import PortionPagination
|
||||||
|
|
||||||
|
|
||||||
class ProductBaseView(generics.GenericAPIView):
|
class ProductBaseView(generics.GenericAPIView):
|
||||||
|
|
@ -31,6 +32,12 @@ class ProductListView(ProductBaseView, generics.ListAPIView):
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|
||||||
|
class ProductSimilarView(ProductListView):
|
||||||
|
"""Resource for getting a list of similar product."""
|
||||||
|
serializer_class = serializers.ProductBaseSerializer
|
||||||
|
pagination_class = PortionPagination
|
||||||
|
|
||||||
|
|
||||||
class ProductDetailView(ProductBaseView, generics.RetrieveAPIView):
|
class ProductDetailView(ProductBaseView, generics.RetrieveAPIView):
|
||||||
"""Detail view fro model Product."""
|
"""Detail view fro model Product."""
|
||||||
lookup_field = 'slug'
|
lookup_field = 'slug'
|
||||||
|
|
@ -81,3 +88,14 @@ class ProductCommentRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
self.check_object_permissions(self.request, comment_obj)
|
self.check_object_permissions(self.request, comment_obj)
|
||||||
|
|
||||||
return comment_obj
|
return comment_obj
|
||||||
|
|
||||||
|
|
||||||
|
class SimilarListView(ProductSimilarView):
|
||||||
|
"""Return similar products."""
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
"""Overridden get_queryset method."""
|
||||||
|
return super().get_queryset() \
|
||||||
|
.has_location() \
|
||||||
|
.similar(slug=self.kwargs.get('slug'))
|
||||||
|
|
||||||
|
|
|
||||||
18
apps/recipe/migrations/0002_recipe_old_id.py
Normal file
18
apps/recipe/migrations/0002_recipe_old_id.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-12-16 06:34
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('recipe', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='recipe',
|
||||||
|
name='old_id',
|
||||||
|
field=models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='old id'),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/recipe/migrations/0003_recipe_slug.py
Normal file
18
apps/recipe/migrations/0003_recipe_slug.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-12-16 13:51
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('recipe', '0002_recipe_old_id'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='recipe',
|
||||||
|
name='slug',
|
||||||
|
field=models.SlugField(max_length=255, null=True, unique=True, verbose_name='Slug'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -25,6 +25,9 @@ class RecipeQuerySet(models.QuerySet):
|
||||||
default=False,
|
default=False,
|
||||||
output_field=models.BooleanField(default=False)))
|
output_field=models.BooleanField(default=False)))
|
||||||
|
|
||||||
|
def by_locale(self, locale):
|
||||||
|
return self.filter(title__icontains=locale)
|
||||||
|
|
||||||
|
|
||||||
class Recipe(TranslatedFieldsMixin, ImageMixin, BaseAttributes):
|
class Recipe(TranslatedFieldsMixin, ImageMixin, BaseAttributes):
|
||||||
"""Recipe model."""
|
"""Recipe model."""
|
||||||
|
|
@ -43,22 +46,19 @@ class Recipe(TranslatedFieldsMixin, ImageMixin, BaseAttributes):
|
||||||
|
|
||||||
STR_FIELD_NAME = 'title'
|
STR_FIELD_NAME = 'title'
|
||||||
|
|
||||||
title = TJSONField(blank=True, null=True, default=None, verbose_name=_('Title'),
|
title = TJSONField(blank=True, null=True, default=None, verbose_name=_('Title'), help_text='{"en-GB": "some text"}')
|
||||||
help_text='{"en-GB": "some text"}')
|
|
||||||
subtitle = TJSONField(blank=True, null=True, default=None, verbose_name=_('Subtitle'),
|
subtitle = TJSONField(blank=True, null=True, default=None, verbose_name=_('Subtitle'),
|
||||||
help_text='{"en-GB": "some text"}')
|
help_text='{"en-GB": "some text"}')
|
||||||
description = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'),
|
description = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'),
|
||||||
help_text='{"en-GB": "some text"}')
|
help_text='{"en-GB": "some text"}')
|
||||||
state = models.PositiveSmallIntegerField(default=WAITING, choices=STATE_CHOICES,
|
state = models.PositiveSmallIntegerField(default=WAITING, choices=STATE_CHOICES, verbose_name=_('State'))
|
||||||
verbose_name=_('State'))
|
author = models.CharField(max_length=255, blank=True, null=True, default=None, verbose_name=_('Author'))
|
||||||
author = models.CharField(max_length=255, blank=True, null=True, default=None,
|
published_at = models.DateTimeField(verbose_name=_('Published at'), blank=True, default=None, null=True,
|
||||||
verbose_name=_('Author'))
|
|
||||||
published_at = models.DateTimeField(verbose_name=_('Published at'),
|
|
||||||
blank=True, default=None, null=True,
|
|
||||||
help_text=_('Published at'))
|
help_text=_('Published at'))
|
||||||
published_scheduled_at = models.DateTimeField(verbose_name=_('Published scheduled at'),
|
published_scheduled_at = models.DateTimeField(verbose_name=_('Published scheduled at'), blank=True, default=None,
|
||||||
blank=True, default=None, null=True,
|
null=True, help_text=_('Published scheduled at'))
|
||||||
help_text=_('Published scheduled at'))
|
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
|
||||||
|
slug = models.SlugField(unique=True, max_length=255, null=True, verbose_name=_('Slug'))
|
||||||
|
|
||||||
objects = RecipeQuerySet.as_manager()
|
objects = RecipeQuerySet.as_manager()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,15 @@ class RecipeListSerializer(serializers.ModelSerializer):
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
||||||
model = models.Recipe
|
model = models.Recipe
|
||||||
fields = ('id', 'title_translated', 'subtitle_translated', 'author',
|
fields = (
|
||||||
'published_at', 'in_favorites')
|
'id',
|
||||||
|
'title_translated',
|
||||||
|
'subtitle_translated',
|
||||||
|
'author',
|
||||||
|
'created_by',
|
||||||
|
'published_at',
|
||||||
|
'in_favorites',
|
||||||
|
)
|
||||||
read_only_fields = fields
|
read_only_fields = fields
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,49 @@
|
||||||
from django.db.models import Value, IntegerField, F
|
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
|
|
||||||
|
from django.db.models import Count
|
||||||
|
|
||||||
|
from recipe.models import Recipe
|
||||||
from transfer.models import PageTexts
|
from transfer.models import PageTexts
|
||||||
from transfer.serializers.recipe import RecipeSerializer
|
from transfer.serializers.recipe import RecipeSerializer
|
||||||
|
|
||||||
|
|
||||||
def transfer_recipe():
|
def transfer_recipe():
|
||||||
queryset = PageTexts.objects.filter(page__type="Recipe")
|
queryset = PageTexts.objects.filter(
|
||||||
|
page__type='Recipe',
|
||||||
|
).values(
|
||||||
|
'id',
|
||||||
|
'title',
|
||||||
|
'summary',
|
||||||
|
'body',
|
||||||
|
'locale',
|
||||||
|
'state',
|
||||||
|
'slug',
|
||||||
|
'created_at',
|
||||||
|
'page__attachment_suffix_url',
|
||||||
|
'page__account_id',
|
||||||
|
)
|
||||||
|
|
||||||
serialized_data = RecipeSerializer(data=list(queryset.values()), many=True)
|
serialized_data = RecipeSerializer(data=list(queryset), many=True)
|
||||||
if serialized_data.is_valid():
|
if serialized_data.is_valid():
|
||||||
serialized_data.save()
|
serialized_data.save()
|
||||||
else:
|
else:
|
||||||
pprint(f"News serializer errors: {serialized_data.errors}")
|
pprint(f'Recipe serializer errors: {serialized_data.errors}')
|
||||||
|
return
|
||||||
|
|
||||||
|
# Удаление дубликатов рецептов по одинаковым description
|
||||||
|
duplicate_descriptions = Recipe.objects.values(
|
||||||
|
'description'
|
||||||
|
).annotate(
|
||||||
|
description_count=Count('description')
|
||||||
|
).filter(
|
||||||
|
description_count__gt=1
|
||||||
|
)
|
||||||
|
for data in duplicate_descriptions:
|
||||||
|
description = data['description']
|
||||||
|
_list = list(Recipe.objects.filter(description=description).values_list('pk', flat=True)[1:])
|
||||||
|
Recipe.objects.filter(id__in=_list).delete()
|
||||||
|
|
||||||
|
|
||||||
data_types = {
|
data_types = {
|
||||||
"recipe": [transfer_recipe]
|
'recipe': [transfer_recipe]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
"""Recipe app common views."""
|
"""Recipe app common views."""
|
||||||
|
from django.utils import translation
|
||||||
from rest_framework import generics, permissions
|
from rest_framework import generics, permissions
|
||||||
|
|
||||||
from recipe import models
|
from recipe import models
|
||||||
from recipe.serializers import common as serializers
|
from recipe.serializers import common as serializers
|
||||||
|
|
||||||
|
|
@ -10,9 +12,14 @@ class RecipeViewMixin(generics.GenericAPIView):
|
||||||
pagination_class = None
|
pagination_class = None
|
||||||
permission_classes = (permissions.AllowAny,)
|
permission_classes = (permissions.AllowAny,)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self, *args, **kwargs):
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
qs = models.Recipe.objects.published().annotate_in_favorites(user)
|
qs = models.Recipe.objects.published().annotate_in_favorites(user)
|
||||||
|
|
||||||
|
locale = kwargs.get('locale')
|
||||||
|
if locale:
|
||||||
|
qs = qs.by_locale(locale)
|
||||||
|
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -21,6 +28,11 @@ class RecipeListView(RecipeViewMixin, generics.ListAPIView):
|
||||||
|
|
||||||
serializer_class = serializers.RecipeListSerializer
|
serializer_class = serializers.RecipeListSerializer
|
||||||
|
|
||||||
|
def get_queryset(self, *args, **kwargs):
|
||||||
|
locale = translation.get_language()
|
||||||
|
kwargs.update({'locale': locale})
|
||||||
|
return super().get_queryset(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class RecipeDetailView(RecipeViewMixin, generics.RetrieveAPIView):
|
class RecipeDetailView(RecipeViewMixin, generics.RetrieveAPIView):
|
||||||
"""Resource for detailed recipe information."""
|
"""Resource for detailed recipe information."""
|
||||||
|
|
|
||||||
|
|
@ -127,8 +127,10 @@ def transfer_product_reviews():
|
||||||
|
|
||||||
|
|
||||||
data_types = {
|
data_types = {
|
||||||
|
"languages": [
|
||||||
|
transfer_languages,
|
||||||
|
],
|
||||||
"overlook": [
|
"overlook": [
|
||||||
# transfer_languages,
|
|
||||||
transfer_reviews,
|
transfer_reviews,
|
||||||
transfer_text_review,
|
transfer_text_review,
|
||||||
make_en_text_review,
|
make_en_text_review,
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ from django.conf import settings
|
||||||
from django_elasticsearch_dsl import Document, Index, fields
|
from django_elasticsearch_dsl import Document, Index, fields
|
||||||
from search_indexes.utils import OBJECT_FIELD_PROPERTIES
|
from search_indexes.utils import OBJECT_FIELD_PROPERTIES
|
||||||
from news import models
|
from news import models
|
||||||
|
from json import dumps
|
||||||
|
|
||||||
|
|
||||||
NewsIndex = Index(settings.ELASTICSEARCH_INDEX_NAMES.get(__name__, 'news'))
|
NewsIndex = Index(settings.ELASTICSEARCH_INDEX_NAMES.get(__name__, 'news'))
|
||||||
|
|
@ -17,7 +18,7 @@ class NewsDocument(Document):
|
||||||
'name': fields.KeywordField()})
|
'name': fields.KeywordField()})
|
||||||
title = fields.ObjectField(attr='title_indexing',
|
title = fields.ObjectField(attr='title_indexing',
|
||||||
properties=OBJECT_FIELD_PROPERTIES)
|
properties=OBJECT_FIELD_PROPERTIES)
|
||||||
slugs = fields.ObjectField(properties=OBJECT_FIELD_PROPERTIES)
|
slugs = fields.KeywordField()
|
||||||
backoffice_title = fields.TextField(analyzer='english')
|
backoffice_title = fields.TextField(analyzer='english')
|
||||||
subtitle = fields.ObjectField(attr='subtitle_indexing',
|
subtitle = fields.ObjectField(attr='subtitle_indexing',
|
||||||
properties=OBJECT_FIELD_PROPERTIES)
|
properties=OBJECT_FIELD_PROPERTIES)
|
||||||
|
|
@ -44,10 +45,11 @@ class NewsDocument(Document):
|
||||||
},
|
},
|
||||||
multi=True)
|
multi=True)
|
||||||
favorites_for_users = fields.ListField(field=fields.IntegerField())
|
favorites_for_users = fields.ListField(field=fields.IntegerField())
|
||||||
start = fields.DateField(attr='start')
|
start = fields.DateField(attr='publication_datetime')
|
||||||
|
has_any_desc_active = fields.BooleanField()
|
||||||
|
|
||||||
def prepare_slugs(self, instance):
|
def prepare_slugs(self, instance):
|
||||||
return {locale: instance.slugs.get(locale) for locale in OBJECT_FIELD_PROPERTIES}
|
return dumps(instance.slugs or {})
|
||||||
|
|
||||||
class Django:
|
class Django:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ from news.serializers import NewsTypeSerializer
|
||||||
from search_indexes.documents import EstablishmentDocument, NewsDocument
|
from search_indexes.documents import EstablishmentDocument, NewsDocument
|
||||||
from search_indexes.documents.product import ProductDocument
|
from search_indexes.documents.product import ProductDocument
|
||||||
from search_indexes.utils import get_translated_value
|
from search_indexes.utils import get_translated_value
|
||||||
|
from json import loads
|
||||||
|
|
||||||
|
|
||||||
class TagsDocumentSerializer(serializers.Serializer):
|
class TagsDocumentSerializer(serializers.Serializer):
|
||||||
|
|
@ -243,7 +244,7 @@ class NewsDocumentSerializer(InFavoritesMixin, DocumentSerializer):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_slug(obj):
|
def get_slug(obj):
|
||||||
return get_translated_value(obj.slugs)
|
return get_translated_value(loads(obj.slugs))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_title_translated(obj):
|
def get_title_translated(obj):
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
"""Search indexes app views."""
|
"""Search indexes app views."""
|
||||||
from rest_framework import permissions
|
|
||||||
from django_elasticsearch_dsl_drf import constants
|
from django_elasticsearch_dsl_drf import constants
|
||||||
from django_elasticsearch_dsl_drf.filter_backends import (
|
from django_elasticsearch_dsl_drf.filter_backends import (
|
||||||
FilteringFilterBackend,
|
FilteringFilterBackend,
|
||||||
GeoSpatialOrderingFilterBackend,
|
GeoSpatialOrderingFilterBackend,
|
||||||
OrderingFilterBackend,
|
OrderingFilterBackend,
|
||||||
)
|
)
|
||||||
from elasticsearch_dsl import TermsFacet
|
|
||||||
from django_elasticsearch_dsl_drf.viewsets import BaseDocumentViewSet
|
from django_elasticsearch_dsl_drf.viewsets import BaseDocumentViewSet
|
||||||
|
from elasticsearch_dsl import TermsFacet
|
||||||
|
from rest_framework import permissions
|
||||||
|
|
||||||
|
from product.models import Product
|
||||||
from search_indexes import serializers, filters, utils
|
from search_indexes import serializers, filters, utils
|
||||||
from search_indexes.documents import EstablishmentDocument, NewsDocument
|
from search_indexes.documents import EstablishmentDocument, NewsDocument
|
||||||
from search_indexes.documents.product import ProductDocument
|
from search_indexes.documents.product import ProductDocument
|
||||||
|
|
@ -17,6 +19,11 @@ from utils.pagination import ESDocumentPagination
|
||||||
class NewsDocumentViewSet(BaseDocumentViewSet):
|
class NewsDocumentViewSet(BaseDocumentViewSet):
|
||||||
"""News document ViewSet."""
|
"""News document ViewSet."""
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
qs = super(NewsDocumentViewSet, self).get_queryset()
|
||||||
|
qs = qs.filter('match', has_any_desc_active=True)
|
||||||
|
return qs
|
||||||
|
|
||||||
document = NewsDocument
|
document = NewsDocument
|
||||||
lookup_field = 'slug'
|
lookup_field = 'slug'
|
||||||
pagination_class = ESDocumentPagination
|
pagination_class = ESDocumentPagination
|
||||||
|
|
@ -61,11 +68,18 @@ class NewsDocumentViewSet(BaseDocumentViewSet):
|
||||||
)
|
)
|
||||||
|
|
||||||
filter_fields = {
|
filter_fields = {
|
||||||
|
'tags_id': {
|
||||||
|
'field': 'tags.id',
|
||||||
|
'lookups': [
|
||||||
|
constants.LOOKUP_QUERY_IN,
|
||||||
|
constants.LOOKUP_QUERY_EXCLUDE,
|
||||||
|
],
|
||||||
|
},
|
||||||
'tag': {
|
'tag': {
|
||||||
'field': 'tags.id',
|
'field': 'tags.id',
|
||||||
'lookups': [
|
'lookups': [
|
||||||
constants.LOOKUP_QUERY_IN,
|
constants.LOOKUP_QUERY_IN,
|
||||||
constants.LOOKUP_QUERY_EXCLUDE
|
constants.LOOKUP_QUERY_EXCLUDE,
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
'tag_value': {
|
'tag_value': {
|
||||||
|
|
@ -334,6 +348,12 @@ class ProductDocumentViewSet(BaseDocumentViewSet):
|
||||||
# GeoSpatialOrderingFilterBackend,
|
# GeoSpatialOrderingFilterBackend,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
qs = super(ProductDocumentViewSet, self).get_queryset()
|
||||||
|
qs = qs.filter('match', state=Product.PUBLISHED)
|
||||||
|
return qs
|
||||||
|
|
||||||
ordering_fields = {
|
ordering_fields = {
|
||||||
'created': {
|
'created': {
|
||||||
'field': 'created',
|
'field': 'created',
|
||||||
|
|
@ -390,7 +410,7 @@ class ProductDocumentViewSet(BaseDocumentViewSet):
|
||||||
'lookups': [constants.LOOKUP_QUERY_IN],
|
'lookups': [constants.LOOKUP_QUERY_IN],
|
||||||
},
|
},
|
||||||
'country': {
|
'country': {
|
||||||
'field': 'establishment.address.city.country.code',
|
'field': 'establishment.city.country.code',
|
||||||
},
|
},
|
||||||
'wine_colors_id': {
|
'wine_colors_id': {
|
||||||
'field': 'wine_colors.id',
|
'field': 'wine_colors.id',
|
||||||
|
|
|
||||||
|
|
@ -63,9 +63,20 @@ class FiltersTagCategoryViewSet(TagCategoryViewSet):
|
||||||
"""ViewSet for TagCategory model."""
|
"""ViewSet for TagCategory model."""
|
||||||
|
|
||||||
serializer_class = serializers.FiltersTagCategoryBaseSerializer
|
serializer_class = serializers.FiltersTagCategoryBaseSerializer
|
||||||
|
index_name_to_order = {
|
||||||
|
'open_now': 9,
|
||||||
|
'works_noon': 8,
|
||||||
|
'works_evening': 7,
|
||||||
|
'pop': 6,
|
||||||
|
'category': 5,
|
||||||
|
'toque_number': 4,
|
||||||
|
'cuisine': 3,
|
||||||
|
'moment': 2,
|
||||||
|
'service': 1,
|
||||||
|
}
|
||||||
|
|
||||||
def list(self, request, *args, **kwargs):
|
def list(self, request, *args, **kwargs):
|
||||||
queryset = self.filter_queryset(self.get_queryset())
|
queryset = self.filter_queryset(self.get_queryset().exclude(public=False))
|
||||||
serializer = self.get_serializer(queryset, many=True)
|
serializer = self.get_serializer(queryset, many=True)
|
||||||
|
|
||||||
result_list = serializer.data
|
result_list = serializer.data
|
||||||
|
|
@ -77,7 +88,7 @@ class FiltersTagCategoryViewSet(TagCategoryViewSet):
|
||||||
elif query_params.get('product_type'):
|
elif query_params.get('product_type'):
|
||||||
params_type = query_params.get('product_type')
|
params_type = query_params.get('product_type')
|
||||||
|
|
||||||
week_days = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")
|
week_days = tuple(map(_, ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")))
|
||||||
flags = ('toque_number', 'wine_region', 'works_noon', 'works_evening', 'works_now', 'works_at_weekday')
|
flags = ('toque_number', 'wine_region', 'works_noon', 'works_evening', 'works_now', 'works_at_weekday')
|
||||||
filter_flags = {flag_name: False for flag_name in flags}
|
filter_flags = {flag_name: False for flag_name in flags}
|
||||||
additional_flags = []
|
additional_flags = []
|
||||||
|
|
@ -94,19 +105,6 @@ class FiltersTagCategoryViewSet(TagCategoryViewSet):
|
||||||
for flag_name in additional_flags:
|
for flag_name in additional_flags:
|
||||||
filter_flags[flag_name] = True
|
filter_flags[flag_name] = True
|
||||||
|
|
||||||
if filter_flags['toque_number']:
|
|
||||||
toques = {
|
|
||||||
"index_name": "toque_number",
|
|
||||||
"label_translated": "Toques",
|
|
||||||
"param_name": "toque_number__in",
|
|
||||||
"filters": [{
|
|
||||||
"id": toque_id,
|
|
||||||
"index_name": "toque_%d" % toque_id,
|
|
||||||
"label_translated": "Toque %d" % toque_id
|
|
||||||
} for toque_id in range(6)]
|
|
||||||
}
|
|
||||||
result_list.append(toques)
|
|
||||||
|
|
||||||
if request.query_params.get('product_type') == ProductType.WINE:
|
if request.query_params.get('product_type') == ProductType.WINE:
|
||||||
wine_region_id = query_params.get('wine_region_id__in')
|
wine_region_id = query_params.get('wine_region_id__in')
|
||||||
|
|
||||||
|
|
@ -129,11 +127,30 @@ class FiltersTagCategoryViewSet(TagCategoryViewSet):
|
||||||
|
|
||||||
result_list.append(wine_regions)
|
result_list.append(wine_regions)
|
||||||
|
|
||||||
|
for item in result_list:
|
||||||
|
if 'filters' in item:
|
||||||
|
item['filters'].sort(key=lambda x: x.get('label_translated'))
|
||||||
|
|
||||||
|
if filter_flags['toque_number']:
|
||||||
|
toques = {
|
||||||
|
"index_name": "toque_number",
|
||||||
|
"label_translated": "Toques",
|
||||||
|
"param_name": "toque_number__in",
|
||||||
|
'type': 'toque',
|
||||||
|
"filters": [{
|
||||||
|
"id": toque_id,
|
||||||
|
"index_name": "toque_%d" % toque_id,
|
||||||
|
"label_translated": "Toque %d" % toque_id
|
||||||
|
} for toque_id in range(6)]
|
||||||
|
}
|
||||||
|
result_list.append(toques)
|
||||||
|
|
||||||
if filter_flags['works_noon']:
|
if filter_flags['works_noon']:
|
||||||
works_noon = {
|
works_noon = {
|
||||||
"index_name": "works_noon",
|
"index_name": "works_noon",
|
||||||
"label_translated": "Open noon",
|
"label_translated": "Open noon",
|
||||||
"param_name": "works_noon__in",
|
"param_name": "works_noon__in",
|
||||||
|
'type': 'weekday',
|
||||||
"filters": [{
|
"filters": [{
|
||||||
"id": weekday,
|
"id": weekday,
|
||||||
"index_name": week_days[weekday].lower(),
|
"index_name": week_days[weekday].lower(),
|
||||||
|
|
@ -148,6 +165,7 @@ class FiltersTagCategoryViewSet(TagCategoryViewSet):
|
||||||
"index_name": "works_evening",
|
"index_name": "works_evening",
|
||||||
"label_translated": "Open evening",
|
"label_translated": "Open evening",
|
||||||
"param_name": "works_evening__in",
|
"param_name": "works_evening__in",
|
||||||
|
'type': 'weekday',
|
||||||
"filters": [{
|
"filters": [{
|
||||||
"id": weekday,
|
"id": weekday,
|
||||||
"index_name": week_days[weekday].lower(),
|
"index_name": week_days[weekday].lower(),
|
||||||
|
|
@ -161,7 +179,7 @@ class FiltersTagCategoryViewSet(TagCategoryViewSet):
|
||||||
"index_name": "open_now",
|
"index_name": "open_now",
|
||||||
"label_translated": "Open now",
|
"label_translated": "Open now",
|
||||||
"param_name": "open_now",
|
"param_name": "open_now",
|
||||||
"type": True
|
"type": 'bool',
|
||||||
}
|
}
|
||||||
result_list.append(works_now)
|
result_list.append(works_now)
|
||||||
|
|
||||||
|
|
@ -170,6 +188,7 @@ class FiltersTagCategoryViewSet(TagCategoryViewSet):
|
||||||
"index_name": "works_at_weekday",
|
"index_name": "works_at_weekday",
|
||||||
"label_translated": "Works at weekday",
|
"label_translated": "Works at weekday",
|
||||||
"param_name": "works_at_weekday__in",
|
"param_name": "works_at_weekday__in",
|
||||||
|
'type': 'weekday',
|
||||||
"filters": [{
|
"filters": [{
|
||||||
"id": weekday,
|
"id": weekday,
|
||||||
"index_name": week_days[weekday].lower(),
|
"index_name": week_days[weekday].lower(),
|
||||||
|
|
@ -180,7 +199,17 @@ class FiltersTagCategoryViewSet(TagCategoryViewSet):
|
||||||
|
|
||||||
search_view_class = self.define_search_view_by_request(request)
|
search_view_class = self.define_search_view_by_request(request)
|
||||||
facets = search_view_class.as_view({'get': 'list'})(self.mutate_request(self.request)).data['facets']
|
facets = search_view_class.as_view({'get': 'list'})(self.mutate_request(self.request)).data['facets']
|
||||||
return Response(self.remove_empty_filters(result_list, facets))
|
result_list = self.remove_empty_filters(result_list, facets)
|
||||||
|
tag_category = list(filter(lambda x: x.get('index_name') == 'tag', result_list))
|
||||||
|
result_list = [category for category in result_list if category.get('index_name') != 'tag']
|
||||||
|
if len(tag_category):
|
||||||
|
tag_category = list(filter(lambda x: x.get('index_name') == 'pop', tag_category[0]['filters']))
|
||||||
|
if len(tag_category): # we have Pop tag in our results
|
||||||
|
tag_category = tag_category[0]
|
||||||
|
tag_category['param_name'] = 'tags_id__in'
|
||||||
|
result_list.append(tag_category)
|
||||||
|
result_list.sort(key=lambda x: self.index_name_to_order.get(x.get('index_name'), 0), reverse=True)
|
||||||
|
return Response(result_list)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def mutate_request(request):
|
def mutate_request(request):
|
||||||
|
|
@ -217,9 +246,11 @@ class FiltersTagCategoryViewSet(TagCategoryViewSet):
|
||||||
if facets.get('_filter_tag'):
|
if facets.get('_filter_tag'):
|
||||||
tags_to_preserve = list(map(lambda el: el['key'], facets['_filter_tag']['tag']['buckets']))
|
tags_to_preserve = list(map(lambda el: el['key'], facets['_filter_tag']['tag']['buckets']))
|
||||||
if facets.get('_filter_wine_colors'):
|
if facets.get('_filter_wine_colors'):
|
||||||
wine_colors_to_preserve = list(map(lambda el: el['key'], facets['_filter_wine_colors']['wine_colors']['buckets']))
|
wine_colors_to_preserve = list(
|
||||||
|
map(lambda el: el['key'], facets['_filter_wine_colors']['wine_colors']['buckets']))
|
||||||
if facets.get('_filter_wine_region_id'):
|
if facets.get('_filter_wine_region_id'):
|
||||||
wine_regions_to_preserve = list(map(lambda el: el['key'], facets['_filter_wine_region_id']['wine_region_id']['buckets']))
|
wine_regions_to_preserve = list(
|
||||||
|
map(lambda el: el['key'], facets['_filter_wine_region_id']['wine_region_id']['buckets']))
|
||||||
if facets.get('_filter_toque_number'):
|
if facets.get('_filter_toque_number'):
|
||||||
toque_numbers = list(map(lambda el: el['key'], facets['_filter_toque_number']['toque_number']['buckets']))
|
toque_numbers = list(map(lambda el: el['key'], facets['_filter_toque_number']['toque_number']['buckets']))
|
||||||
if facets.get('_filter_works_noon'):
|
if facets.get('_filter_works_noon'):
|
||||||
|
|
@ -227,7 +258,8 @@ class FiltersTagCategoryViewSet(TagCategoryViewSet):
|
||||||
if facets.get('_filter_works_evening'):
|
if facets.get('_filter_works_evening'):
|
||||||
works_evening = list(map(lambda el: el['key'], facets['_filter_works_evening']['works_evening']['buckets']))
|
works_evening = list(map(lambda el: el['key'], facets['_filter_works_evening']['works_evening']['buckets']))
|
||||||
if facets.get('_filter_works_at_weekday'):
|
if facets.get('_filter_works_at_weekday'):
|
||||||
works_at_weekday = list(map(lambda el: el['key'], facets['_filter_works_at_weekday']['works_at_weekday']['buckets']))
|
works_at_weekday = list(
|
||||||
|
map(lambda el: el['key'], facets['_filter_works_at_weekday']['works_at_weekday']['buckets']))
|
||||||
if facets.get('_filter_works_now'):
|
if facets.get('_filter_works_now'):
|
||||||
works_now = list(map(lambda el: el['key'], facets['_filter_works_now']['works_now']['buckets']))
|
works_now = list(map(lambda el: el['key'], facets['_filter_works_now']['works_now']['buckets']))
|
||||||
|
|
||||||
|
|
@ -237,9 +269,11 @@ class FiltersTagCategoryViewSet(TagCategoryViewSet):
|
||||||
if param_name == 'tags_id__in':
|
if param_name == 'tags_id__in':
|
||||||
category['filters'] = list(filter(lambda tag: tag['id'] in tags_to_preserve, category['filters']))
|
category['filters'] = list(filter(lambda tag: tag['id'] in tags_to_preserve, category['filters']))
|
||||||
elif param_name == 'wine_colors_id__in':
|
elif param_name == 'wine_colors_id__in':
|
||||||
category['filters'] = list(filter(lambda tag: tag['id'] in wine_colors_to_preserve, category['filters']))
|
category['filters'] = list(
|
||||||
|
filter(lambda tag: tag['id'] in wine_colors_to_preserve, category['filters']))
|
||||||
elif param_name == 'wine_region_id__in':
|
elif param_name == 'wine_region_id__in':
|
||||||
category['filters'] = list(filter(lambda tag: tag['id'] in wine_regions_to_preserve, category['filters']))
|
category['filters'] = list(
|
||||||
|
filter(lambda tag: tag['id'] in wine_regions_to_preserve, category['filters']))
|
||||||
elif param_name == 'toque_number__in':
|
elif param_name == 'toque_number__in':
|
||||||
category['filters'] = list(filter(lambda tag: tag['id'] in toque_numbers, category['filters']))
|
category['filters'] = list(filter(lambda tag: tag['id'] in toque_numbers, category['filters']))
|
||||||
elif param_name == 'works_noon__in':
|
elif param_name == 'works_noon__in':
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ class Command(BaseCommand):
|
||||||
'news', # перенос новостей (после №2)
|
'news', # перенос новостей (после №2)
|
||||||
'account', # №1 - перенос пользователей
|
'account', # №1 - перенос пользователей
|
||||||
'subscriber',
|
'subscriber',
|
||||||
'recipe',
|
'recipe', # №2 - рецепты
|
||||||
'partner',
|
'partner',
|
||||||
'establishment', # №3 - перенос заведений
|
'establishment', # №3 - перенос заведений
|
||||||
'gallery',
|
'gallery',
|
||||||
|
|
@ -48,7 +48,9 @@ class Command(BaseCommand):
|
||||||
'guide_element_types',
|
'guide_element_types',
|
||||||
'guide_elements_bulk',
|
'guide_elements_bulk',
|
||||||
'guide_element_advertorials',
|
'guide_element_advertorials',
|
||||||
|
'guide_element_label_photo',
|
||||||
'guide_complete',
|
'guide_complete',
|
||||||
|
'languages', # №4 - перенос языков
|
||||||
]
|
]
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
|
|
|
||||||
|
|
@ -343,7 +343,7 @@ class GuideAds(MigrateMixin):
|
||||||
nb_right_pages = models.IntegerField(blank=True, null=True)
|
nb_right_pages = models.IntegerField(blank=True, null=True)
|
||||||
created_at = models.DateTimeField()
|
created_at = models.DateTimeField()
|
||||||
updated_at = models.DateTimeField()
|
updated_at = models.DateTimeField()
|
||||||
guide_ad_node_id = models.IntegerField(blank=True, null=True)
|
guide_ad_node = models.ForeignKey('GuideElements', on_delete=models.DO_NOTHING, blank=True, null=True)
|
||||||
type = models.CharField(max_length=255, blank=True, null=True)
|
type = models.CharField(max_length=255, blank=True, null=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -1224,6 +1224,22 @@ class Footers(MigrateMixin):
|
||||||
db_table = 'footers'
|
db_table = 'footers'
|
||||||
|
|
||||||
|
|
||||||
|
class LabelPhotos(MigrateMixin):
|
||||||
|
using = 'legacy'
|
||||||
|
|
||||||
|
guide_ad = models.ForeignKey(GuideAds, models.DO_NOTHING, blank=True, null=True)
|
||||||
|
attachment_file_name = models.CharField(max_length=255)
|
||||||
|
attachment_content_type = models.CharField(max_length=255)
|
||||||
|
attachment_file_size = models.IntegerField()
|
||||||
|
attachment_updated_at = models.DateTimeField()
|
||||||
|
attachment_suffix_url = models.CharField(max_length=255)
|
||||||
|
geometries = models.CharField(max_length=1024)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
managed = False
|
||||||
|
db_table = 'label_photos'
|
||||||
|
|
||||||
|
|
||||||
class OwnershipAffs(MigrateMixin):
|
class OwnershipAffs(MigrateMixin):
|
||||||
using = 'legacy'
|
using = 'legacy'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,11 @@ class EstablishmentSerializer(serializers.ModelSerializer):
|
||||||
schedules = validated_data.pop('schedules')
|
schedules = validated_data.pop('schedules')
|
||||||
subtypes = [validated_data.pop('subtype', None)]
|
subtypes = [validated_data.pop('subtype', None)]
|
||||||
|
|
||||||
establishment = Establishment.objects.create(**validated_data)
|
# establishment = Establishment.objects.create(**validated_data)
|
||||||
|
establishment, _ = Establishment.objects.update_or_create(
|
||||||
|
old_id=validated_data['old_id'],
|
||||||
|
defaults=validated_data,
|
||||||
|
)
|
||||||
if email:
|
if email:
|
||||||
ContactEmail.objects.get_or_create(
|
ContactEmail.objects.get_or_create(
|
||||||
email=email,
|
email=email,
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ class GuideSerializer(TransferSerializerMixin):
|
||||||
class GuideFilterSerializer(TransferSerializerMixin):
|
class GuideFilterSerializer(TransferSerializerMixin):
|
||||||
id = serializers.IntegerField()
|
id = serializers.IntegerField()
|
||||||
year = serializers.CharField(allow_null=True)
|
year = serializers.CharField(allow_null=True)
|
||||||
type = serializers.CharField(allow_null=True, source='establishment_type')
|
establishment_type = serializers.CharField(allow_null=True)
|
||||||
countries = serializers.CharField(allow_null=True)
|
countries = serializers.CharField(allow_null=True)
|
||||||
regions = serializers.CharField(allow_null=True)
|
regions = serializers.CharField(allow_null=True)
|
||||||
subregions = serializers.CharField(allow_null=True)
|
subregions = serializers.CharField(allow_null=True)
|
||||||
|
|
@ -86,7 +86,7 @@ class GuideFilterSerializer(TransferSerializerMixin):
|
||||||
fields = (
|
fields = (
|
||||||
'id',
|
'id',
|
||||||
'year',
|
'year',
|
||||||
'type',
|
'establishment_type',
|
||||||
'countries',
|
'countries',
|
||||||
'regions',
|
'regions',
|
||||||
'subregions',
|
'subregions',
|
||||||
|
|
|
||||||
|
|
@ -1,55 +1,87 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from account.models import User
|
||||||
from recipe.models import Recipe
|
from recipe.models import Recipe
|
||||||
from utils.legacy_parser import parse_legacy_news_content
|
from utils.legacy_parser import parse_legacy_news_content
|
||||||
|
|
||||||
|
|
||||||
class RecipeSerializer(serializers.ModelSerializer):
|
class RecipeSerializer(serializers.Serializer):
|
||||||
locale = serializers.CharField()
|
id = serializers.IntegerField()
|
||||||
|
title = serializers.CharField(allow_null=True)
|
||||||
|
summary = serializers.CharField(allow_null=True, allow_blank=True)
|
||||||
body = serializers.CharField(allow_null=True)
|
body = serializers.CharField(allow_null=True)
|
||||||
title = serializers.CharField()
|
locale = serializers.CharField(allow_null=True)
|
||||||
state = serializers.CharField()
|
state = serializers.CharField(allow_null=True)
|
||||||
created_at = serializers.DateTimeField(source="published_at", format='%m-%d-%Y %H:%M:%S')
|
slug = serializers.CharField(allow_null=True)
|
||||||
|
created_at = serializers.DateTimeField(format='%m-%d-%Y %H:%M:%S')
|
||||||
class Meta:
|
page__attachment_suffix_url = serializers.CharField(allow_null=True)
|
||||||
model = Recipe
|
page__account_id = serializers.IntegerField(allow_null=True)
|
||||||
fields = (
|
|
||||||
"body",
|
|
||||||
"title",
|
|
||||||
"state",
|
|
||||||
"created_at",
|
|
||||||
'locale',
|
|
||||||
)
|
|
||||||
|
|
||||||
def validate(self, data):
|
def validate(self, data):
|
||||||
data["state"] = self.get_state(data)
|
data.update({
|
||||||
data["title"] = self.get_title(data)
|
'old_id': data.pop('id'),
|
||||||
data["description"] = self.get_description(data)
|
'title': self.get_title(data),
|
||||||
data.pop("body")
|
'subtitle': self.get_subtitle(data),
|
||||||
data.pop("locale")
|
'description': self.get_description(data),
|
||||||
|
'state': self.get_state(data),
|
||||||
|
'created': data.pop('created_at'),
|
||||||
|
'image': self.get_image(data),
|
||||||
|
'created_by': self.get_account(data),
|
||||||
|
'modified_by': self.get_account(data),
|
||||||
|
})
|
||||||
|
|
||||||
|
data.pop('page__account_id')
|
||||||
|
data.pop('page__attachment_suffix_url')
|
||||||
|
data.pop('summary')
|
||||||
|
data.pop('body')
|
||||||
|
data.pop('locale')
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
return Recipe.objects.create(**validated_data)
|
obj, _ = Recipe.objects.update_or_create(
|
||||||
|
old_id=validated_data['old_id'],
|
||||||
|
defaults=validated_data,
|
||||||
|
)
|
||||||
|
return obj
|
||||||
|
|
||||||
def get_state(self, obj):
|
@staticmethod
|
||||||
if obj["state"] == "published":
|
def get_title(data):
|
||||||
return Recipe.PUBLISHED
|
if data.get('title') and data.get('locale'):
|
||||||
elif obj["state"] == "hidden":
|
return {data['locale']: data['title']}
|
||||||
return Recipe.HIDDEN
|
return None
|
||||||
elif obj["state"] == "published_exclusive":
|
|
||||||
return Recipe.PUBLISHED_EXCLUSIVE
|
|
||||||
else:
|
|
||||||
return Recipe.WAITING
|
|
||||||
|
|
||||||
def get_title(self, obj):
|
@staticmethod
|
||||||
# tit = obj.get("title")
|
def get_subtitle(data):
|
||||||
# return {"en-GB": tit}
|
if data.get('summary') and data.get('locale'):
|
||||||
return {obj['locale']: obj['title']}
|
return {data['locale']: data['summary']}
|
||||||
|
return None
|
||||||
|
|
||||||
def get_description(self, obj):
|
@staticmethod
|
||||||
# desc = obj.get("body")
|
def get_description(data):
|
||||||
# return {"en-GB": desc}
|
if data.get('body') and data.get('locale'):
|
||||||
content = None
|
content = parse_legacy_news_content(data['body'])
|
||||||
if obj['body']:
|
return {data['locale']: content}
|
||||||
content = parse_legacy_news_content(obj['body'])
|
return None
|
||||||
return {obj['locale']: content}
|
|
||||||
|
@staticmethod
|
||||||
|
def get_state(data):
|
||||||
|
value = data.get('state')
|
||||||
|
states = {
|
||||||
|
'published': Recipe.PUBLISHED,
|
||||||
|
'hidden': Recipe.HIDDEN,
|
||||||
|
'published_exclusive': Recipe.PUBLISHED_EXCLUSIVE
|
||||||
|
}
|
||||||
|
return states.get(value, Recipe.WAITING)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_image(data):
|
||||||
|
values = (None, 'default/missing.png')
|
||||||
|
if data.get('page__attachment_suffix_url') not in values:
|
||||||
|
return data['page__attachment_suffix_url']
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_account(data):
|
||||||
|
if data.get('page__account_id'):
|
||||||
|
return User.objects.filter(old_id=data['page__account_id']).first()
|
||||||
|
return None
|
||||||
|
|
|
||||||
|
|
@ -67,16 +67,23 @@ def get_default_locale():
|
||||||
settings.FALLBACK_LOCALE
|
settings.FALLBACK_LOCALE
|
||||||
|
|
||||||
|
|
||||||
def translate_field(self, field_name):
|
def translate_field(self, field_name, toggle_field_name=None):
|
||||||
def translate(self):
|
def translate(self):
|
||||||
field = getattr(self, field_name)
|
field = getattr(self, field_name)
|
||||||
|
toggler = getattr(self, toggle_field_name, None)
|
||||||
if isinstance(field, dict):
|
if isinstance(field, dict):
|
||||||
|
if toggler:
|
||||||
|
field = {locale: v for locale, v in field.items() if toggler.get(locale) in [True, 'True', 'true']}
|
||||||
value = field.get(to_locale(get_language()))
|
value = field.get(to_locale(get_language()))
|
||||||
# fallback
|
# fallback
|
||||||
if value is None:
|
if value is None:
|
||||||
value = field.get(get_default_locale())
|
value = field.get(get_default_locale())
|
||||||
if value is None:
|
if value is None:
|
||||||
value = field.get(next(iter(field.keys()), None))
|
try:
|
||||||
|
value = next(iter(field.values()))
|
||||||
|
except StopIteration:
|
||||||
|
# field values are absent
|
||||||
|
return None
|
||||||
return value
|
return value
|
||||||
return None
|
return None
|
||||||
return translate
|
return translate
|
||||||
|
|
@ -114,7 +121,7 @@ class TranslatedFieldsMixin:
|
||||||
field_name = field.name
|
field_name = field.name
|
||||||
if isinstance(field, TJSONField):
|
if isinstance(field, TJSONField):
|
||||||
setattr(cls, f'{field.name}_translated',
|
setattr(cls, f'{field.name}_translated',
|
||||||
property(translate_field(self, field_name)))
|
property(translate_field(self, field_name, f'locale_to_{field_name}_is_active')))
|
||||||
setattr(cls, f'{field_name}_indexing',
|
setattr(cls, f'{field_name}_indexing',
|
||||||
property(index_field(self, field_name)))
|
property(index_field(self, field_name)))
|
||||||
|
|
||||||
|
|
@ -361,6 +368,10 @@ class GMTokenGenerator(PasswordResetTokenGenerator):
|
||||||
class GalleryModelMixin(models.Model):
|
class GalleryModelMixin(models.Model):
|
||||||
"""Mixin for models that has gallery."""
|
"""Mixin for models that has gallery."""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
abstract = True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def crop_gallery(self):
|
def crop_gallery(self):
|
||||||
if hasattr(self, 'gallery'):
|
if hasattr(self, 'gallery'):
|
||||||
|
|
@ -400,10 +411,6 @@ class GalleryModelMixin(models.Model):
|
||||||
)
|
)
|
||||||
return image_property
|
return image_property
|
||||||
|
|
||||||
class Meta:
|
|
||||||
"""Meta class."""
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
|
|
||||||
class IntermediateGalleryModelQuerySet(models.QuerySet):
|
class IntermediateGalleryModelQuerySet(models.QuerySet):
|
||||||
"""Extended QuerySet."""
|
"""Extended QuerySet."""
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ from django.conf import settings
|
||||||
from rest_framework.pagination import CursorPagination, PageNumberPagination
|
from rest_framework.pagination import CursorPagination, PageNumberPagination
|
||||||
from django_elasticsearch_dsl_drf.pagination import PageNumberPagination as ESPagination
|
from django_elasticsearch_dsl_drf.pagination import PageNumberPagination as ESPagination
|
||||||
|
|
||||||
|
|
||||||
class ProjectPageNumberPagination(PageNumberPagination):
|
class ProjectPageNumberPagination(PageNumberPagination):
|
||||||
"""Customized pagination class."""
|
"""Customized pagination class."""
|
||||||
|
|
||||||
|
|
@ -82,7 +83,7 @@ class ESDocumentPagination(ESPagination):
|
||||||
return page.facets._d_
|
return page.facets._d_
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentPortionPagination(ProjectMobilePagination):
|
class PortionPagination(ProjectMobilePagination):
|
||||||
"""
|
"""
|
||||||
Pagination for app establishments with limit page size equal to 12
|
Pagination for app establishments with limit page size equal to 12
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ urlpatterns = [
|
||||||
path('timetables/', include('timetable.urls.mobile')),
|
path('timetables/', include('timetable.urls.mobile')),
|
||||||
# path('account/', include('account.urls.web')),
|
# path('account/', include('account.urls.web')),
|
||||||
path('re_blocks/', include('advertisement.urls.mobile')),
|
path('re_blocks/', include('advertisement.urls.mobile')),
|
||||||
# path('collection/', include('collection.urls.web')),
|
# path('collection/', include('collection.urls.mobile')),
|
||||||
# path('establishments/', include('establishment.urls.web')),
|
# path('establishments/', include('establishment.urls.web')),
|
||||||
path('news/', include('news.urls.mobile')),
|
path('news/', include('news.urls.mobile')),
|
||||||
# path('partner/', include('partner.urls.web')),
|
# path('partner/', include('partner.urls.web')),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user