Merge branch 'develop' into feature/fix-country-region-city-transfer

# Conflicts:
#	apps/transfer/management/commands/transfer.py
#	apps/transfer/serializers/location.py
This commit is contained in:
littlewolf 2019-11-15 17:08:51 +03:00
commit cf7cc9bd0c
123 changed files with 2708 additions and 926 deletions

View File

@ -100,7 +100,6 @@ class User(AbstractUser):
newsletter = models.NullBooleanField(default=True) newsletter = models.NullBooleanField(default=True)
old_id = models.IntegerField(null=True, blank=True, default=None) old_id = models.IntegerField(null=True, blank=True, default=None)
EMAIL_FIELD = 'email' EMAIL_FIELD = 'email'
USERNAME_FIELD = 'username' USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email'] REQUIRED_FIELDS = ['email']
@ -261,15 +260,31 @@ class User(AbstractUser):
def favorite_establishment_ids(self): def favorite_establishment_ids(self):
"""Return establishment IDs that in favorites for current user.""" """Return establishment IDs that in favorites for current user."""
return self.favorites.by_content_type(app_label='establishment', return self.favorites.by_content_type(app_label='establishment',
model='establishment')\ model='establishment') \
.values_list('object_id', flat=True) .values_list('object_id', flat=True)
@property @property
def favorite_recipe_ids(self): def favorite_recipe_ids(self):
"""Return recipe IDs that in favorites for current user.""" """Return recipe IDs that in favorites for current user."""
return self.favorites.by_content_type(app_label='recipe', return self.favorites.by_content_type(app_label='recipe',
model='recipe')\ model='recipe') \
.values_list('object_id', flat=True) .values_list('object_id', flat=True)
@property
def favorite_news_ids(self):
"""Return news IDs that in favorites for current user."""
return self.favorites.by_content_type(
app_label='news',
model='news',
).values_list('object_id', flat=True)
@property
def favorite_product_ids(self):
"""Return news IDs that in favorites for current user."""
return self.favorites.by_content_type(
app_label='product',
model='product',
).values_list('object_id', flat=True)
class UserRole(ProjectBaseMixin): class UserRole(ProjectBaseMixin):

View File

@ -2,8 +2,15 @@
from django.contrib import admin from django.contrib import admin
from advertisement import models from advertisement import models
from main.models import Page
class PageInline(admin.TabularInline):
model = Page
extra = 0
@admin.register(models.Advertisement) @admin.register(models.Advertisement)
class AdvertisementModelAdmin(admin.ModelAdmin): class AdvertisementModelAdmin(admin.ModelAdmin):
"""Admin model for model Advertisement""" """Admin model for model Advertisement"""
inlines = (PageInline, )

View File

@ -0,0 +1,34 @@
# Generated by Django 2.2.7 on 2019-11-15 07:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('advertisement', '0005_auto_20191108_0923'),
]
operations = [
migrations.RemoveField(
model_name='advertisement',
name='height',
),
migrations.RemoveField(
model_name='advertisement',
name='image_url',
),
migrations.RemoveField(
model_name='advertisement',
name='source',
),
migrations.RemoveField(
model_name='advertisement',
name='width',
),
migrations.AddField(
model_name='advertisement',
name='end',
field=models.DateTimeField(blank=True, default=None, null=True, verbose_name='end'),
),
]

View File

@ -0,0 +1,30 @@
# Generated by Django 2.2.7 on 2019-11-15 07:50
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('advertisement', '0006_auto_20191115_0750'),
('main', '0036_auto_20191115_0750'),
]
operations = [
migrations.AddField(
model_name='advertisement',
name='page_type',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='advertisements', to='main.PageType', verbose_name='page type'),
),
migrations.AddField(
model_name='advertisement',
name='sites',
field=models.ManyToManyField(related_name='advertisements', to='main.SiteSettings', verbose_name='site'),
),
migrations.AddField(
model_name='advertisement',
name='start',
field=models.DateTimeField(null=True, verbose_name='start'),
),
]

View File

@ -6,18 +6,47 @@ from django.utils.translation import gettext_lazy as _
from translation.models import Language from translation.models import Language
from utils.models import ProjectBaseMixin, ImageMixin, PlatformMixin, URLImageMixin from utils.models import ProjectBaseMixin, ImageMixin, PlatformMixin, URLImageMixin
from main.models import Page
class Advertisement(URLImageMixin, ProjectBaseMixin, PlatformMixin): class AdvertisementQuerySet(models.QuerySet):
"""QuerySet for model Advertisement."""
def with_base_related(self):
"""Return QuerySet with base related"""
return self.select_related('page_type') \
.prefetch_related('target_languages', 'sites', 'pages')
def by_page_type(self, page_type: str):
"""Filter Advertisement by page type."""
return self.filter(page_type__name=page_type)
def by_locale(self, locale):
"""Filter by locale."""
return self.filter(target_languages__locale=locale)
class Advertisement(ProjectBaseMixin):
"""Advertisement model.""" """Advertisement model."""
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None) old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True) uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
url = models.URLField(verbose_name=_('Ad URL')) url = models.URLField(verbose_name=_('Ad URL'))
width = models.PositiveIntegerField(verbose_name=_('Block width')) # 300
height = models.PositiveIntegerField(verbose_name=_('Block height')) # 250
block_level = models.CharField(verbose_name=_('Block level'), max_length=10, blank=True, null=True) block_level = models.CharField(verbose_name=_('Block level'), max_length=10, blank=True, null=True)
target_languages = models.ManyToManyField(Language) target_languages = models.ManyToManyField(Language)
start = models.DateTimeField(null=True,
verbose_name=_('start'))
end = models.DateTimeField(blank=True, null=True, default=None,
verbose_name=_('end'))
sites = models.ManyToManyField('main.SiteSettings',
related_name='advertisements',
verbose_name=_('site'))
page_type = models.ForeignKey('main.PageType', on_delete=models.PROTECT,
null=True,
related_name='advertisements',
verbose_name=_('page type'))
objects = AdvertisementQuerySet.as_manager()
class Meta: class Meta:
verbose_name = _('Advertisement') verbose_name = _('Advertisement')
@ -25,3 +54,13 @@ class Advertisement(URLImageMixin, ProjectBaseMixin, PlatformMixin):
def __str__(self): def __str__(self):
return str(self.url) return str(self.url)
@property
def mobile_page(self):
"""Return mobile page"""
return self.pages.by_platform(Page.MOBILE).first()
@property
def web_page(self):
"""Return web page"""
return self.pages.by_platform(Page.WEB).first()

View File

@ -0,0 +1,3 @@
from .common import *
from .mobile import *
from .web import *

View File

@ -0,0 +1,39 @@
"""Serializers for app advertisements"""
from rest_framework import serializers
from advertisement import models
from translation.serializers import LanguageSerializer
from main.serializers import SiteShortSerializer
from main.serializers import PageBaseSerializer
class AdvertisementBaseSerializer(serializers.ModelSerializer):
"""Base serializer for model Advertisement."""
languages = LanguageSerializer(many=True, read_only=True)
sites = SiteShortSerializer(many=True, read_only=True)
class Meta:
model = models.Advertisement
fields = [
'id',
'uuid',
'url',
'block_level',
'languages',
'sites',
'start',
'end',
]
class AdvertisementPageTypeCommonListSerializer(AdvertisementBaseSerializer):
"""Serializer for AdvertisementPageTypeCommonView."""
page = PageBaseSerializer(source='common_page', read_only=True)
class Meta(AdvertisementBaseSerializer.Meta):
"""Meta class."""
fields = AdvertisementBaseSerializer.Meta.fields + [
'page',
]

View File

@ -0,0 +1,15 @@
"""Serializers for mobile app advertisements"""
from advertisement.serializers import AdvertisementBaseSerializer
from main.serializers import PageBaseSerializer
class AdvertisementPageTypeMobileListSerializer(AdvertisementBaseSerializer):
"""Serializer for AdvertisementPageTypeMobileView."""
page = PageBaseSerializer(source='mobile_page', read_only=True)
class Meta(AdvertisementBaseSerializer.Meta):
"""Meta class."""
fields = AdvertisementBaseSerializer.Meta.fields + [
'page',
]

View File

@ -1,22 +1,15 @@
"""Serializers for app advertisements""" """Serializers for web app advertisements"""
from rest_framework import serializers from advertisement.serializers import AdvertisementBaseSerializer
from main.serializers import PageBaseSerializer
from advertisement import models
from translation.serializers import LanguageSerializer
class AdvertisementSerializer(serializers.ModelSerializer): class AdvertisementPageTypeWebListSerializer(AdvertisementBaseSerializer):
"""Serializer for model Advertisement.""" """Serializer for AdvertisementPageTypeWebView."""
class Meta: page = PageBaseSerializer(source='web_page', read_only=True)
model = models.Advertisement
fields = ( class Meta(AdvertisementBaseSerializer.Meta):
'id', """Meta class."""
'uuid', fields = AdvertisementBaseSerializer.Meta.fields + [
'url', 'page',
'image_url', ]
'width',
'height',
'block_level',
'source'
)

View File

@ -0,0 +1,8 @@
"""Advertisement common urlpaths."""
from django.urls import path
app_name = 'advertisements'
common_urlpatterns = [
]

View File

@ -0,0 +1,14 @@
"""Advertisement common urlpaths."""
from django.urls import path
from advertisement.views import mobile as views
from .common import common_urlpatterns
app_name = 'advertisements'
urlpatterns = [
path('<page_type>/', views.AdvertisementPageTypeMobileListView.as_view(), name='list'),
]
urlpatterns += common_urlpatterns

View File

@ -2,9 +2,12 @@
from django.urls import path from django.urls import path
from advertisement.views import web as views from advertisement.views import web as views
from .common import common_urlpatterns
app_name = 'advertisements' app_name = 'advertisements'
urlpatterns = [ urlpatterns = [
path('<str:page>/', views.AdvertisementListView.as_view(), name='list') path('<page_type>/', views.AdvertisementPageTypeWebListView.as_view(), name='list'),
] ]
urlpatterns += common_urlpatterns

View File

@ -0,0 +1,3 @@
from .common import *
from .mobile import *
from .web import *

View File

@ -0,0 +1,32 @@
"""Views for app advertisement"""
from rest_framework import generics
from rest_framework import permissions
from advertisement.models import Advertisement
from advertisement.serializers import AdvertisementBaseSerializer, \
AdvertisementPageTypeCommonListSerializer
class AdvertisementBaseView(generics.GenericAPIView):
"""Advertisement list view."""
pagination_class = None
permission_classes = (permissions.AllowAny, )
serializer_class = AdvertisementBaseSerializer
def get_queryset(self):
"""Overridden get queryset method."""
return Advertisement.objects.with_base_related() \
.by_locale(self.request.locale)
class AdvertisementPageTypeListView(AdvertisementBaseView, generics.ListAPIView):
"""Advertisement list view by page type."""
def get_queryset(self):
"""Overridden get queryset method."""
product_type = self.kwargs.get('page_type')
qs = super(AdvertisementPageTypeListView, self).get_queryset()
if product_type:
return qs.by_page_type(product_type)
return qs.none()

View File

@ -0,0 +1,9 @@
"""Mobile views for app advertisement"""
from advertisement.serializers import AdvertisementPageTypeMobileListSerializer
from .common import AdvertisementPageTypeListView
class AdvertisementPageTypeMobileListView(AdvertisementPageTypeListView):
"""Advertisement mobile list view."""
serializer_class = AdvertisementPageTypeMobileListSerializer

View File

@ -1,19 +1,9 @@
"""Views for app advertisement""" """Web views for app advertisement"""
from rest_framework import generics from advertisement.serializers import AdvertisementPageTypeWebListSerializer
from rest_framework import permissions from .common import AdvertisementPageTypeListView
from advertisement import models
from advertisement.serializers import web as serializers
class AdvertisementListView(generics.ListAPIView): class AdvertisementPageTypeWebListView(AdvertisementPageTypeListView):
"""List view for model Advertisement""" """Advertisement mobile list view."""
pagination_class = None
model = models.Advertisement
permission_classes = (permissions.AllowAny,)
serializer_class = serializers.AdvertisementSerializer
def get_queryset(self): serializer_class = AdvertisementPageTypeWebListSerializer
return models.Advertisement.objects\
.filter(page__page_name__contains=self.kwargs['page'])\
.filter(target_languages__locale=self.request.locale)

View File

@ -1,9 +1,33 @@
from rest_framework import serializers from rest_framework import serializers
from location.models import Country
from location.serializers import CountrySimpleSerializer
from collection.serializers.common import CollectionBaseSerializer
from collection import models from collection import models
class CollectionBackOfficeSerializer(serializers.ModelSerializer): class CollectionBackOfficeSerializer(CollectionBaseSerializer):
"""Collection serializer.""" """Collection back serializer."""
country_id = serializers.PrimaryKeyRelatedField(
source='country', write_only=True,
queryset=Country.objects.all())
collection_type_display = serializers.CharField(
source='get_collection_type_display', read_only=True)
country = CountrySimpleSerializer(read_only=True)
class Meta: class Meta:
model = models.Collection model = models.Collection
fields = '__all__' fields = CollectionBaseSerializer.Meta.fields + [
'id',
'name',
'collection_type',
'collection_type_display',
'is_publish',
'on_top',
'country',
'country_id',
'block_size',
'description',
'slug',
'start',
'end',
]

View File

@ -7,4 +7,5 @@ app_name = 'collection'
urlpatterns = [ urlpatterns = [
path('', views.CollectionListCreateView.as_view(), name='list-create'), path('', views.CollectionListCreateView.as_view(), name='list-create'),
path('<int:pk>/', views.CollectionRUDView.as_view(), name='rud-collection'),
] ]

View File

@ -1,6 +1,6 @@
from rest_framework import generics, permissions from rest_framework import generics, permissions
from collection import models from collection import models
from collection.serializers import common, back from collection.serializers import back
class CollectionListCreateView(generics.ListCreateAPIView): class CollectionListCreateView(generics.ListCreateAPIView):

View File

@ -57,6 +57,8 @@ class ProductInline(admin.TabularInline):
class EstablishmentAdmin(BaseModelAdminMixin, admin.ModelAdmin): class EstablishmentAdmin(BaseModelAdminMixin, admin.ModelAdmin):
"""Establishment admin.""" """Establishment admin."""
list_display = ['id', '__str__', 'image_tag', ] list_display = ['id', '__str__', 'image_tag', ]
search_fields = ['id', 'name', 'index_name', 'slug']
list_filter = ['public_mark', 'toque_number']
# inlines = [ # inlines = [
# AwardInline, ContactPhoneInline, ContactEmailInline, # AwardInline, ContactPhoneInline, ContactEmailInline,
@ -94,3 +96,14 @@ class MenuAdmin(BaseModelAdminMixin, admin.ModelAdmin):
@admin.register(models.RatingStrategy) @admin.register(models.RatingStrategy)
class RatingStrategyAdmin(BaseModelAdminMixin, admin.ModelAdmin): class RatingStrategyAdmin(BaseModelAdminMixin, admin.ModelAdmin):
"""Admin conf for Rating Strategy model.""" """Admin conf for Rating Strategy model."""
@admin.register(models.SocialChoice)
class SocialChoiceAdmin(BaseModelAdminMixin, admin.ModelAdmin):
"""Admin conf for SocialChoice model."""
@admin.register(models.SocialNetwork)
class SocialNetworkAdmin(BaseModelAdminMixin, admin.ModelAdmin):
"""Admin conf for SocialNetwork model."""
raw_id_fields = ('establishment',)

View File

@ -11,10 +11,8 @@ class EstablishmentFilter(filters.FilterSet):
tag_id = filters.NumberFilter(field_name='tags__metadata__id',) tag_id = filters.NumberFilter(field_name='tags__metadata__id',)
award_id = filters.NumberFilter(field_name='awards__id',) award_id = filters.NumberFilter(field_name='awards__id',)
search = filters.CharFilter(method='search_text') search = filters.CharFilter(method='search_text')
type = filters.ChoiceFilter(choices=models.EstablishmentType.INDEX_NAME_TYPES, type = filters.CharFilter(method='by_type')
method='by_type') subtype = filters.CharFilter(method='by_subtype')
subtype = filters.ChoiceFilter(choices=models.EstablishmentSubType.INDEX_NAME_TYPES,
method='by_subtype')
class Meta: class Meta:
"""Meta class.""" """Meta class."""

View File

@ -1,6 +1,6 @@
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from establishment.models import Establishment, SocialNetwork from establishment.models import Establishment, SocialChoice, SocialNetwork
from transfer.models import EstablishmentInfos from transfer.models import EstablishmentInfos
@ -10,47 +10,48 @@ class Command(BaseCommand):
def handle(self, *args, **kwargs): def handle(self, *args, **kwargs):
count = 0 count = 0
facebook, _ = SocialChoice.objects.get_or_create(title='facebook')
twitter, _ = SocialChoice.objects.get_or_create(title='twitter')
instagram, _ = SocialChoice.objects.get_or_create(title='instagram')
queryset = EstablishmentInfos.objects.exclude( queryset = EstablishmentInfos.objects.exclude(
establishment_id__isnull=True establishment_id__isnull=True
).values_list('id', 'establishment_id', 'facebook', 'twitter', 'instagram') ).values_list('id', 'establishment_id', 'facebook', 'twitter', 'instagram')
for id, es_id, facebook, twitter, instagram in queryset: for id, es_id, facebook_url, twitter_url, instagram_url in queryset:
try: establishment = Establishment.objects.filter(old_id=es_id).first()
establishment = Establishment.objects.get(old_id=es_id) if not establishment:
except Establishment.DoesNotExist:
continue continue
except Establishment.MultipleObjectsReturned:
establishment = Establishment.objects.filter(old_id=es_id).first() if facebook_url:
else: if 'facebook.com/' not in facebook_url:
if facebook: facebook_url = 'https://www.facebook.com/' + facebook_url
if 'facebook.com/' not in facebook: obj, _ = SocialNetwork.objects.get_or_create(
facebook = 'https://www.facebook.com/' + facebook old_id=id,
obj, _ = SocialNetwork.objects.get_or_create( establishment=establishment,
old_id=id, network=facebook,
establishment=establishment, url=facebook_url,
title='facebook', )
url=facebook, count += 1
) if twitter_url:
count += 1 if 'twitter.com/' not in twitter_url:
if twitter: twitter_url = 'https://www.twitter.com/' + twitter_url
if 'twitter.com/' not in twitter: obj, _ = SocialNetwork.objects.get_or_create(
twitter = 'https://www.twitter.com/' + twitter old_id=id,
obj, _ = SocialNetwork.objects.get_or_create( establishment=establishment,
old_id=id, network=twitter,
establishment=establishment, url=twitter_url,
title='twitter', )
url=twitter, count += 1
) if instagram_url:
count += 1 if 'instagram.com/' not in instagram_url:
if instagram: instagram = 'https://www.instagram.com/' + instagram_url
if 'instagram.com/' not in instagram: obj, _ = SocialNetwork.objects.get_or_create(
instagram = 'https://www.instagram.com/' + instagram old_id=id,
obj, _ = SocialNetwork.objects.get_or_create( establishment=establishment,
old_id=id, network=instagram,
establishment=establishment, url=instagram_url,
title='instagram', )
url=instagram, count += 1
)
count += 1
self.stdout.write(self.style.WARNING(f'Created/updated {count} objects.')) self.stdout.write(self.style.WARNING(f'Created/updated {count} objects.'))

View File

@ -1,9 +1,12 @@
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.db import connections from django.db import connections
from establishment.management.commands.add_position import namedtuplefetchall from establishment.management.commands.add_position import namedtuplefetchall
from establishment.models import Employee from establishment.models import Employee, EstablishmentEmployee
from django.utils import timezone
from django.db.models import Q
from tqdm import tqdm from tqdm import tqdm
class Command(BaseCommand): class Command(BaseCommand):
help = 'Add employee from old db to new db.' help = 'Add employee from old db to new db.'
@ -30,4 +33,7 @@ class Command(BaseCommand):
for e in tqdm(self.employees_sql()): for e in tqdm(self.employees_sql()):
empl = Employee.objects.filter(old_id=e.profile_id).update(name=e.name) empl = Employee.objects.filter(old_id=e.profile_id).update(name=e.name)
EstablishmentEmployee.objects.filter(from_date__isnull=True, old_id__isnull=False)\
.update(from_date=timezone.now()-timezone.timedelta(days=1))
self.stdout.write(self.style.WARNING(f'Update employee name objects.')) self.stdout.write(self.style.WARNING(f'Update employee name objects.'))

View File

@ -0,0 +1,23 @@
# Generated by Django 2.2.7 on 2019-11-13 15:12
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('establishment', '0059_establishmentnote'),
]
operations = [
migrations.AlterField(
model_name='establishmentsubtype',
name='index_name',
field=models.CharField(db_index=True, max_length=50, unique=True, verbose_name='Index name'),
),
migrations.AlterField(
model_name='establishmenttype',
name='index_name',
field=models.CharField(db_index=True, max_length=50, unique=True, verbose_name='Index name'),
),
]

View File

@ -0,0 +1,35 @@
# Generated by Django 2.2.7 on 2019-11-14 05:50
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('establishment', '0060_auto_20191113_1512'),
]
operations = [
migrations.CreateModel(
name='SocialChoice',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=255, unique=True, verbose_name='title')),
],
options={
'verbose_name': 'social choice',
'verbose_name_plural': 'social choices',
},
),
migrations.RemoveField(
model_name='socialnetwork',
name='title',
),
migrations.AddField(
model_name='socialnetwork',
name='network',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='social_links', to='establishment.SocialChoice', verbose_name='social network'),
preserve_default=False,
),
]

View File

@ -31,21 +31,14 @@ class EstablishmentType(TranslatedFieldsMixin, ProjectBaseMixin):
STR_FIELD_NAME = 'name' STR_FIELD_NAME = 'name'
# INDEX NAME CHOICES # EXAMPLE OF INDEX NAME CHOICES
RESTAURANT = 'restaurant' RESTAURANT = 'restaurant'
ARTISAN = 'artisan' ARTISAN = 'artisan'
PRODUCER = 'producer' PRODUCER = 'producer'
INDEX_NAME_TYPES = (
(RESTAURANT, _('Restaurant')),
(ARTISAN, _('Artisan')),
(PRODUCER, _('Producer')),
)
name = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'), name = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'),
help_text='{"en-GB":"some text"}') help_text='{"en-GB":"some text"}')
index_name = models.CharField(max_length=50, choices=INDEX_NAME_TYPES, index_name = models.CharField(max_length=50, unique=True, db_index=True,
unique=True, db_index=True,
verbose_name=_('Index name')) verbose_name=_('Index name'))
use_subtypes = models.BooleanField(_('Use subtypes'), default=True) use_subtypes = models.BooleanField(_('Use subtypes'), default=True)
tag_categories = models.ManyToManyField('tag.TagCategory', tag_categories = models.ManyToManyField('tag.TagCategory',
@ -72,17 +65,12 @@ class EstablishmentSubTypeManager(models.Manager):
class EstablishmentSubType(ProjectBaseMixin, TranslatedFieldsMixin): class EstablishmentSubType(ProjectBaseMixin, TranslatedFieldsMixin):
"""Establishment type model.""" """Establishment type model."""
# INDEX NAME CHOICES # EXAMPLE OF INDEX NAME CHOICES
WINERY = 'winery' WINERY = 'winery'
INDEX_NAME_TYPES = (
(WINERY, _('Winery')),
)
name = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'), name = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'),
help_text='{"en-GB":"some text"}') help_text='{"en-GB":"some text"}')
index_name = models.CharField(max_length=50, choices=INDEX_NAME_TYPES, index_name = models.CharField(max_length=50, unique=True, db_index=True,
unique=True, db_index=True,
verbose_name=_('Index name')) verbose_name=_('Index name'))
establishment_type = models.ForeignKey(EstablishmentType, establishment_type = models.ForeignKey(EstablishmentType,
on_delete=models.CASCADE, on_delete=models.CASCADE,
@ -109,7 +97,7 @@ class EstablishmentQuerySet(models.QuerySet):
def with_base_related(self): def with_base_related(self):
"""Return qs with related objects.""" """Return qs with related objects."""
return self.select_related('address', 'establishment_type').\ return self.select_related('address', 'establishment_type'). \
prefetch_related('tags') prefetch_related('tags')
def with_schedule(self): def with_schedule(self):
@ -126,9 +114,9 @@ class EstablishmentQuerySet(models.QuerySet):
'address__city__country') 'address__city__country')
def with_extended_related(self): def with_extended_related(self):
return self.select_related('establishment_type').\ return self.select_related('establishment_type'). \
prefetch_related('establishment_subtypes', 'awards', 'schedule', prefetch_related('establishment_subtypes', 'awards', 'schedule',
'phones').\ 'phones'). \
prefetch_actual_employees() prefetch_actual_employees()
def with_type_related(self): def with_type_related(self):
@ -136,7 +124,7 @@ class EstablishmentQuerySet(models.QuerySet):
def with_es_related(self): def with_es_related(self):
"""Return qs with related for ES indexing objects.""" """Return qs with related for ES indexing objects."""
return self.select_related('address', 'establishment_type', 'address__city', 'address__city__country').\ return self.select_related('address', 'establishment_type', 'address__city', 'address__city__country'). \
prefetch_related('tags', 'schedule') prefetch_related('tags', 'schedule')
def search(self, value, locale=None): def search(self, value, locale=None):
@ -178,7 +166,7 @@ class EstablishmentQuerySet(models.QuerySet):
""" """
Return QuerySet establishments with published reviews. Return QuerySet establishments with published reviews.
""" """
return self.filter(reviews__status=Review.READY,) return self.filter(reviews__status=Review.READY, )
def annotate_distance(self, point: Point = None): def annotate_distance(self, point: Point = None):
""" """
@ -234,10 +222,10 @@ class EstablishmentQuerySet(models.QuerySet):
.values('id') .values('id')
) )
return self.filter(id__in=subquery_filter_by_distance) \ return self.filter(id__in=subquery_filter_by_distance) \
.annotate_intermediate_public_mark() \ .annotate_intermediate_public_mark() \
.annotate_mark_similarity(mark=establishment.public_mark) \ .annotate_mark_similarity(mark=establishment.public_mark) \
.order_by('mark_similarity') \ .order_by('mark_similarity') \
.distinct('mark_similarity', 'id') .distinct('mark_similarity', 'id')
else: else:
return self.none() return self.none()
@ -254,7 +242,7 @@ class EstablishmentQuerySet(models.QuerySet):
.values('id') .values('id')
) )
return self.filter(id__in=subquery_filter_by_distance) \ return self.filter(id__in=subquery_filter_by_distance) \
.order_by('-reviews__published_at') .order_by('-reviews__published_at')
def prefetch_actual_employees(self): def prefetch_actual_employees(self):
"""Prefetch actual employees.""" """Prefetch actual employees."""
@ -290,28 +278,28 @@ class EstablishmentQuerySet(models.QuerySet):
def artisans(self): def artisans(self):
"""Return artisans.""" """Return artisans."""
return self.filter(establishment_type__index_name=EstablishmentType.ARTISAN) return self.filter(establishment_type__index_name__icontains=EstablishmentType.ARTISAN)
def producers(self): def producers(self):
"""Return producers.""" """Return producers."""
return self.filter(establishment_type__index_name=EstablishmentType.PRODUCER) return self.filter(establishment_type__index_name__icontains=EstablishmentType.PRODUCER)
def restaurants(self): def restaurants(self):
"""Return restaurants.""" """Return restaurants."""
return self.filter(establishment_type__index_name=EstablishmentType.RESTAURANT) return self.filter(establishment_type__index_name__icontains=EstablishmentType.RESTAURANT)
def wineries(self): def wineries(self):
"""Return wineries.""" """Return wineries."""
return self.producers().filter( return self.producers().filter(
establishment_subtypes__index_name=EstablishmentSubType.WINERY) establishment_subtypes__index_name__icontains=EstablishmentSubType.WINERY)
def by_type(self, value): def by_type(self, value):
"""Return QuerySet with type by value.""" """Return QuerySet with type by value."""
return self.filter(establishment_type__index_name=value) return self.filter(establishment_type__index_name__icontains=value)
def by_subtype(self, value): def by_subtype(self, value):
"""Return QuerySet with subtype by value.""" """Return QuerySet with subtype by value."""
return self.filter(establishment_subtypes__index_name=value) return self.filter(establishment_subtypes__index_name__icontains=value)
def by_public_mark_range(self, min_value, max_value): def by_public_mark_range(self, min_value, max_value):
"""Filter by public mark range.""" """Filter by public mark range."""
@ -341,11 +329,11 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
help_text='{"en-GB":"some text"}') help_text='{"en-GB":"some text"}')
public_mark = models.PositiveIntegerField(blank=True, null=True, public_mark = models.PositiveIntegerField(blank=True, null=True,
default=None, default=None,
verbose_name=_('public mark'),) verbose_name=_('public mark'), )
# todo: set default 0 # todo: set default 0
toque_number = models.PositiveIntegerField(blank=True, null=True, toque_number = models.PositiveIntegerField(blank=True, null=True,
default=None, default=None,
verbose_name=_('toque number'),) verbose_name=_('toque number'), )
establishment_type = models.ForeignKey(EstablishmentType, establishment_type = models.ForeignKey(EstablishmentType,
related_name='establishment', related_name='establishment',
on_delete=models.PROTECT, on_delete=models.PROTECT,
@ -369,9 +357,9 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
lafourchette = models.URLField(blank=True, null=True, default=None, max_length=255, lafourchette = models.URLField(blank=True, null=True, default=None, max_length=255,
verbose_name=_('Lafourchette URL')) verbose_name=_('Lafourchette URL'))
guestonline_id = models.PositiveIntegerField(blank=True, verbose_name=_('guestonline id'), guestonline_id = models.PositiveIntegerField(blank=True, verbose_name=_('guestonline id'),
null=True, default=None,) null=True, default=None, )
lastable_id = models.TextField(blank=True, verbose_name=_('lastable id'), unique=True, lastable_id = models.TextField(blank=True, verbose_name=_('lastable id'), unique=True,
null=True, default=None,) null=True, default=None, )
booking = models.URLField(blank=True, null=True, default=None, max_length=255, booking = models.URLField(blank=True, null=True, default=None, max_length=255,
verbose_name=_('Booking URL')) verbose_name=_('Booking URL'))
is_publish = models.BooleanField(default=False, verbose_name=_('Publish status')) is_publish = models.BooleanField(default=False, verbose_name=_('Publish status'))
@ -517,13 +505,13 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
@property @property
def tags_indexing(self): def tags_indexing(self):
return [{'id': tag.metadata.id, return [{'id': tag.metadata.id,
'label': tag.metadata.label} for tag in self.tags.all()] 'label': tag.metadata.label} for tag in self.tags.all()]
@property @property
def last_published_review(self): def last_published_review(self):
"""Return last published review""" """Return last published review"""
return self.reviews.published()\ return self.reviews.published() \
.order_by('-published_at').first() .order_by('-published_at').first()
@property @property
def location(self): def location(self):
@ -535,7 +523,7 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
@property @property
def the_most_recent_award(self): def the_most_recent_award(self):
return Award.objects.filter(Q(establishment=self) | Q(employees__establishments=self)) \ return Award.objects.filter(Q(establishment=self) | Q(employees__establishments=self)) \
.latest(field_name='vintage_year') .latest(field_name='vintage_year')
@property @property
def country_id(self): def country_id(self):
@ -602,7 +590,7 @@ class Position(BaseAttributes, TranslatedFieldsMixin):
class EstablishmentEmployeeQuerySet(models.QuerySet): class EstablishmentEmployeeQuerySet(models.QuerySet):
"""Extended queryset for EstablishmEntemployee model.""" """Extended queryset for EstablishmentEmployee model."""
def actual(self): def actual(self):
"""Actual objects..""" """Actual objects.."""
@ -620,7 +608,7 @@ class EstablishmentEmployee(BaseAttributes):
employee = models.ForeignKey('establishment.Employee', on_delete=models.PROTECT, employee = models.ForeignKey('establishment.Employee', on_delete=models.PROTECT,
verbose_name=_('Employee')) verbose_name=_('Employee'))
from_date = models.DateTimeField(default=timezone.now, verbose_name=_('From date'), from_date = models.DateTimeField(default=timezone.now, verbose_name=_('From date'),
null=True, blank=True) null=True, blank=True)
to_date = models.DateTimeField(blank=True, null=True, default=None, to_date = models.DateTimeField(blank=True, null=True, default=None,
verbose_name=_('To date')) verbose_name=_('To date'))
position = models.ForeignKey(Position, on_delete=models.PROTECT, position = models.ForeignKey(Position, on_delete=models.PROTECT,
@ -639,7 +627,7 @@ class Employee(BaseAttributes):
verbose_name=_('User')) verbose_name=_('User'))
name = models.CharField(max_length=255, verbose_name=_('Last name')) name = models.CharField(max_length=255, verbose_name=_('Last name'))
establishments = models.ManyToManyField(Establishment, related_name='employees', establishments = models.ManyToManyField(Establishment, related_name='employees',
through=EstablishmentEmployee,) through=EstablishmentEmployee, )
awards = generic.GenericRelation(to='main.Award', related_query_name='employees') awards = generic.GenericRelation(to='main.Award', related_query_name='employees')
tags = models.ManyToManyField('tag.Tag', related_name='employees', tags = models.ManyToManyField('tag.Tag', related_name='employees',
verbose_name=_('Tags')) verbose_name=_('Tags'))
@ -752,12 +740,31 @@ class Menu(TranslatedFieldsMixin, BaseAttributes):
verbose_name_plural = _('menu') verbose_name_plural = _('menu')
class SocialChoice(models.Model):
title = models.CharField(_('title'), max_length=255, unique=True)
class Meta:
verbose_name = _('social choice')
verbose_name_plural = _('social choices')
def __str__(self):
return self.title
class SocialNetwork(models.Model): class SocialNetwork(models.Model):
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None) old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
establishment = models.ForeignKey( establishment = models.ForeignKey(
'Establishment', verbose_name=_('establishment'), 'Establishment',
related_name='socials', on_delete=models.CASCADE) verbose_name=_('establishment'),
title = models.CharField(_('title'), max_length=255) related_name='socials',
on_delete=models.CASCADE,
)
network = models.ForeignKey(
SocialChoice,
verbose_name=_('social network'),
related_name='social_links',
on_delete=models.CASCADE,
)
url = models.URLField(_('URL'), max_length=255) url = models.URLField(_('URL'), max_length=255)
class Meta: class Meta:
@ -765,7 +772,7 @@ class SocialNetwork(models.Model):
verbose_name_plural = _('social networks') verbose_name_plural = _('social networks')
def __str__(self): def __str__(self):
return self.title return f'{self.network.title}: {self.url}'
class RatingStrategyManager(models.Manager): class RatingStrategyManager(models.Manager):

View File

@ -84,20 +84,29 @@ class EstablishmentRUDSerializer(EstablishmentBaseSerializer):
] ]
class SocialChoiceSerializers(serializers.ModelSerializer):
"""SocialChoice serializers."""
class Meta:
model = models.SocialChoice
fields = '__all__'
class SocialNetworkSerializers(serializers.ModelSerializer): class SocialNetworkSerializers(serializers.ModelSerializer):
"""Social network serializers.""" """Social network serializers."""
class Meta: class Meta:
model = models.SocialNetwork model = models.SocialNetwork
fields = [ fields = [
'id', 'id',
'establishment', 'establishment',
'title', 'network',
'url', 'url',
] ]
class PlatesSerializers(PlateSerializer): class PlatesSerializers(PlateSerializer):
"""Social network serializers.""" """Plates serializers."""
currency_id = serializers.PrimaryKeyRelatedField( currency_id = serializers.PrimaryKeyRelatedField(
source='currency', source='currency',
@ -116,7 +125,8 @@ class PlatesSerializers(PlateSerializer):
class ContactPhoneBackSerializers(PlateSerializer): class ContactPhoneBackSerializers(PlateSerializer):
"""Social network serializers.""" """ContactPhone serializers."""
class Meta: class Meta:
model = models.ContactPhone model = models.ContactPhone
fields = [ fields = [
@ -127,7 +137,8 @@ class ContactPhoneBackSerializers(PlateSerializer):
class ContactEmailBackSerializers(PlateSerializer): class ContactEmailBackSerializers(PlateSerializer):
"""Social network serializers.""" """ContactEmail serializers."""
class Meta: class Meta:
model = models.ContactEmail model = models.ContactEmail
fields = [ fields = [
@ -140,8 +151,8 @@ class ContactEmailBackSerializers(PlateSerializer):
# TODO: test decorator # TODO: test decorator
@with_base_attributes @with_base_attributes
class EmployeeBackSerializers(serializers.ModelSerializer): class EmployeeBackSerializers(serializers.ModelSerializer):
"""Employee serializers."""
"""Social network serializers."""
class Meta: class Meta:
model = models.Employee model = models.Employee
fields = [ fields = [

View File

@ -39,7 +39,7 @@ class SocialNetworkRelatedSerializers(serializers.ModelSerializer):
model = models.SocialNetwork model = models.SocialNetwork
fields = [ fields = [
'id', 'id',
'title', 'network',
'url', 'url',
] ]
@ -98,7 +98,8 @@ class EstablishmentTypeBaseSerializer(serializers.ModelSerializer):
'id', 'id',
'name', 'name',
'name_translated', 'name_translated',
'use_subtypes' 'use_subtypes',
'index_name',
] ]
extra_kwargs = { extra_kwargs = {
'name': {'write_only': True}, 'name': {'write_only': True},
@ -131,7 +132,8 @@ class EstablishmentSubTypeBaseSerializer(serializers.ModelSerializer):
'id', 'id',
'name', 'name',
'name_translated', 'name_translated',
'establishment_type' 'establishment_type',
'index_name',
] ]
extra_kwargs = { extra_kwargs = {
'name': {'write_only': True}, 'name': {'write_only': True},
@ -172,6 +174,8 @@ class EstablishmentEmployeeSerializer(serializers.ModelSerializer):
class EstablishmentShortSerializer(serializers.ModelSerializer): class EstablishmentShortSerializer(serializers.ModelSerializer):
"""Short serializer for establishment.""" """Short serializer for establishment."""
city = CitySerializer(source='address.city', allow_null=True) city = CitySerializer(source='address.city', allow_null=True)
establishment_type = EstablishmentTypeGeoSerializer()
establishment_subtypes = EstablishmentSubTypeBaseSerializer(many=True)
class Meta: class Meta:
"""Meta class.""" """Meta class."""
@ -179,8 +183,11 @@ class EstablishmentShortSerializer(serializers.ModelSerializer):
fields = [ fields = [
'id', 'id',
'name', 'name',
'index_name',
'slug', 'slug',
'city', 'city',
'establishment_type',
'establishment_subtypes',
] ]
@ -204,6 +211,8 @@ class EstablishmentBaseSerializer(ProjectModelSerializer):
in_favorites = serializers.BooleanField(allow_null=True) in_favorites = serializers.BooleanField(allow_null=True)
tags = TagBaseSerializer(read_only=True, many=True) tags = TagBaseSerializer(read_only=True, many=True)
currency = CurrencySerializer() currency = CurrencySerializer()
type = EstablishmentTypeBaseSerializer(source='establishment_type', read_only=True)
subtypes = EstablishmentSubTypeBaseSerializer(many=True, source='establishment_subtypes')
class Meta: class Meta:
"""Meta class.""" """Meta class."""
@ -222,9 +231,12 @@ class EstablishmentBaseSerializer(ProjectModelSerializer):
'in_favorites', 'in_favorites',
'address', 'address',
'tags', 'tags',
'currency' 'currency',
'type',
'subtypes',
] ]
class EstablishmentListRetrieveSerializer(EstablishmentBaseSerializer): class EstablishmentListRetrieveSerializer(EstablishmentBaseSerializer):
"""Establishment with city serializer.""" """Establishment with city serializer."""
@ -248,10 +260,7 @@ class EstablishmentGeoSerializer(EstablishmentBaseSerializer):
class Meta(EstablishmentBaseSerializer.Meta): class Meta(EstablishmentBaseSerializer.Meta):
"""Meta class.""" """Meta class."""
fields = EstablishmentBaseSerializer.Meta.fields + [ fields = EstablishmentBaseSerializer.Meta.fields
'type',
'subtypes',
]
class RangePriceSerializer(serializers.Serializer): class RangePriceSerializer(serializers.Serializer):
@ -264,8 +273,6 @@ class EstablishmentDetailSerializer(EstablishmentBaseSerializer):
description_translated = TranslatedField() description_translated = TranslatedField()
image = serializers.URLField(source='image_url') image = serializers.URLField(source='image_url')
type = EstablishmentTypeBaseSerializer(source='establishment_type', read_only=True)
subtypes = EstablishmentSubTypeBaseSerializer(many=True, source='establishment_subtypes')
awards = AwardSerializer(many=True) awards = AwardSerializer(many=True)
schedule = ScheduleRUDSerializer(many=True, allow_null=True) schedule = ScheduleRUDSerializer(many=True, allow_null=True)
phones = ContactPhonesSerializer(read_only=True, many=True) phones = ContactPhonesSerializer(read_only=True, many=True)
@ -288,8 +295,6 @@ class EstablishmentDetailSerializer(EstablishmentBaseSerializer):
fields = EstablishmentBaseSerializer.Meta.fields + [ fields = EstablishmentBaseSerializer.Meta.fields + [
'description_translated', 'description_translated',
'image', 'image',
'subtypes',
'type',
'awards', 'awards',
'schedule', 'schedule',
'website', 'website',

View File

@ -4,11 +4,14 @@ import logging
from celery import shared_task from celery import shared_task
from celery.schedules import crontab from celery.schedules import crontab
from celery.task import periodic_task from celery.task import periodic_task
from django.core import management from django.core import management
from django_elasticsearch_dsl.management.commands import search_index from django_elasticsearch_dsl.management.commands import search_index
from django_elasticsearch_dsl.registries import registry
from establishment import models from establishment import models
from location.models import Country from location.models import Country
from search_indexes.documents.establishment import EstablishmentDocument
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -25,7 +28,18 @@ def recalculate_price_levels_by_country(country_id):
establishment.recalculate_price_level(low_price=country.low_price, establishment.recalculate_price_level(low_price=country.low_price,
high_price=country.high_price) high_price=country.high_price)
# @periodic_task(run_every=crontab(minute=59))
# def rebuild_establishment_indices():
# management.call_command(search_index.Command(), action='populate', models=[models.Establishment.__name__],
# force=True)
@periodic_task(run_every=crontab(minute=59)) @periodic_task(run_every=crontab(minute=59))
def rebuild_establishment_indices(): def update_establishment_indices():
management.call_command(search_index.Command(), action='rebuild', models=[models.Establishment.__name__], try:
force=True) doc = registry.get_documents([models.Establishment]).pop()
except KeyError:
pass
else:
qs = doc().get_indexing_queryset()
doc().update(qs)

View File

@ -4,7 +4,7 @@ from account.models import User
from rest_framework import status from rest_framework import status
from http.cookies import SimpleCookie from http.cookies import SimpleCookie
from main.models import Currency from main.models import Currency
from establishment.models import Establishment, EstablishmentType, Menu from establishment.models import Establishment, EstablishmentType, Menu, SocialChoice, SocialNetwork
# Create your tests here. # Create your tests here.
from translation.models import Language from translation.models import Language
from account.models import Role, UserRole from account.models import Role, UserRole
@ -19,7 +19,11 @@ class BaseTestCase(APITestCase):
self.email = 'sedragurda@desoz.com' self.email = 'sedragurda@desoz.com'
self.newsletter = True self.newsletter = True
self.user = User.objects.create_user( self.user = User.objects.create_user(
username=self.username, email=self.email, password=self.password) username=self.username,
email=self.email,
password=self.password,
is_staff=True,
)
# get tokens # get tokens
tokens = User.create_jwt_tokens(self.user) tokens = User.create_jwt_tokens(self.user)
self.client.cookies = SimpleCookie( self.client.cookies = SimpleCookie(
@ -30,13 +34,14 @@ class BaseTestCase(APITestCase):
name="Test establishment type") name="Test establishment type")
# Create lang object # Create lang object
self.lang = Language.objects.get( self.lang = Language.objects.create(
title='Russia', title='Russia',
locale='ru-RU' locale='ru-RU'
) )
self.country_ru = Country.objects.get( self.country_ru = Country.objects.create(
name={"en-GB": "Russian"} name={'en-GB': 'Russian'},
code='RU',
) )
self.region = Region.objects.create(name='Moscow area', code='01', self.region = Region.objects.create(name='Moscow area', code='01',
@ -72,10 +77,17 @@ class BaseTestCase(APITestCase):
establishment=self.establishment) establishment=self.establishment)
self.user_role.save() self.user_role.save()
self.social_choice = SocialChoice.objects.create(title='facebook')
self.social_network = SocialNetwork.objects.create(
network=self.social_choice,
url='https://testsocial.de',
establishment=self.establishment,
)
class EstablishmentBTests(BaseTestCase): class EstablishmentBTests(BaseTestCase):
def test_establishment_CRUD(self): def test_establishment_CRUD(self):
params = {'page': 1, 'page_size': 1,} params = {'page': 1, 'page_size': 1, }
response = self.client.get('/api/back/establishments/', params, format='json') response = self.client.get('/api/back/establishments/', params, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
@ -108,7 +120,7 @@ class EstablishmentBTests(BaseTestCase):
class EmployeeTests(BaseTestCase): class EmployeeTests(BaseTestCase):
def test_employee_CRUD(self): def test_employee_CRUD(self):
response = self.client.get('/api/back/establishments/employees/', format='json') response = self.client.get('/api/back/establishments/employees/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
data = { data = {
@ -205,13 +217,40 @@ class PhoneTests(ChildTestCase):
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
class SocialChoicesTests(ChildTestCase):
def test_social_choices_CRUD(self):
response = self.client.get('/api/back/establishments/social_choice/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
data = {
'title': 'twitter',
}
response = self.client.post('/api/back/establishments/social_choice/', data=data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
response = self.client.get(f'/api/back/establishments/social_choice/{self.social_choice.id}/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
update_data = {
'title': 'vk'
}
response = self.client.patch(f'/api/back/establishments/social_choice/{self.social_choice.id}/',
data=update_data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
response = self.client.delete(f'/api/back/establishments/social_choice/{self.social_choice.id}/')
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
class SocialTests(ChildTestCase): class SocialTests(ChildTestCase):
def test_social_CRUD(self): def test_social_CRUD(self):
response = self.client.get('/api/back/establishments/socials/', format='json') response = self.client.get('/api/back/establishments/socials/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
data = { data = {
'title': "Test social", 'network': self.social_choice.id,
'url': 'https://testsocial.com', 'url': 'https://testsocial.com',
'establishment': self.establishment.id 'establishment': self.establishment.id
} }
@ -219,17 +258,17 @@ class SocialTests(ChildTestCase):
response = self.client.post('/api/back/establishments/socials/', data=data) response = self.client.post('/api/back/establishments/socials/', data=data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
response = self.client.get('/api/back/establishments/socials/1/', format='json') response = self.client.get(f'/api/back/establishments/socials/{self.social_network.id}/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
update_data = { update_data = {
'title': 'Test new social' 'url': 'https://newtestsocial.com'
} }
response = self.client.patch('/api/back/establishments/socials/1/', data=update_data) response = self.client.patch(f'/api/back/establishments/socials/{self.social_network.id}/', data=update_data)
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
response = self.client.delete('/api/back/establishments/socials/1/') response = self.client.delete(f'/api/back/establishments/socials/{self.social_network.id}/')
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
@ -322,7 +361,7 @@ class EstablishmentShedulerTests(ChildTestCase):
class EstablishmentWebTests(BaseTestCase): class EstablishmentWebTests(BaseTestCase):
def test_establishment_Read(self): def test_establishment_Read(self):
params = {'page': 1, 'page_size': 1,} params = {'page': 1, 'page_size': 1, }
response = self.client.get('/api/web/establishments/', params, format='json') response = self.client.get('/api/web/establishments/', params, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
@ -351,7 +390,6 @@ class EstablishmentWebSimilarTests(ChildTestCase):
class EstablishmentWebCommentsTests(ChildTestCase): class EstablishmentWebCommentsTests(ChildTestCase):
def test_comments_CRUD(self): def test_comments_CRUD(self):
response = self.client.get(f'/api/web/establishments/slug/{self.establishment.slug}/comments/', format='json') response = self.client.get(f'/api/web/establishments/slug/{self.establishment.slug}/comments/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
@ -375,8 +413,9 @@ class EstablishmentWebCommentsTests(ChildTestCase):
'text': 'Test new establishment' 'text': 'Test new establishment'
} }
response = self.client.patch(f'/api/web/establishments/slug/{self.establishment.slug}/comments/{comment["id"]}/', response = self.client.patch(
data=update_data) f'/api/web/establishments/slug/{self.establishment.slug}/comments/{comment["id"]}/',
data=update_data)
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
response = self.client.delete( response = self.client.delete(

View File

@ -6,7 +6,6 @@ from establishment import views
app_name = 'establishment' app_name = 'establishment'
urlpatterns = [ urlpatterns = [
path('', views.EstablishmentListCreateView.as_view(), name='list'), path('', views.EstablishmentListCreateView.as_view(), name='list'),
path('<int:pk>/', views.EstablishmentRUDView.as_view(), name='detail'), path('<int:pk>/', views.EstablishmentRUDView.as_view(), name='detail'),
@ -18,6 +17,8 @@ urlpatterns = [
path('menus/<int:pk>/', views.MenuRUDView.as_view(), name='menu-rud'), path('menus/<int:pk>/', views.MenuRUDView.as_view(), name='menu-rud'),
path('plates/', views.PlateListCreateView.as_view(), name='plates'), path('plates/', views.PlateListCreateView.as_view(), name='plates'),
path('plates/<int:pk>/', views.PlateRUDView.as_view(), name='plate-rud'), path('plates/<int:pk>/', views.PlateRUDView.as_view(), name='plate-rud'),
path('social_choice/', views.SocialChoiceListCreateView.as_view(), name='socials_choice'),
path('social_choice/<int:pk>/', views.SocialChoiceRUDView.as_view(), name='socials_choice-rud'),
path('socials/', views.SocialListCreateView.as_view(), name='socials'), path('socials/', views.SocialListCreateView.as_view(), name='socials'),
path('socials/<int:pk>/', views.SocialRUDView.as_view(), name='social-rud'), path('socials/<int:pk>/', views.SocialRUDView.as_view(), name='social-rud'),
path('phones/', views.PhonesListCreateView.as_view(), name='phones'), path('phones/', views.PhonesListCreateView.as_view(), name='phones'),

View File

@ -1,6 +1,6 @@
"""Establishment app views.""" """Establishment app views."""
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from rest_framework import generics from rest_framework import generics, permissions
from utils.permissions import IsCountryAdmin, IsEstablishmentManager from utils.permissions import IsCountryAdmin, IsEstablishmentManager
from establishment import filters, models, serializers from establishment import filters, models, serializers
@ -27,7 +27,7 @@ class EstablishmentListCreateView(EstablishmentMixinViews, generics.ListCreateAP
class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView): class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView):
queryset = models.Establishment.objects.all() queryset = models.Establishment.objects.all()
serializer_class = serializers.EstablishmentRUDSerializer serializer_class = serializers.EstablishmentRUDSerializer
permission_classes = [IsCountryAdmin|IsEstablishmentManager] permission_classes = [IsCountryAdmin | IsEstablishmentManager]
class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView): class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView):
@ -72,12 +72,27 @@ class MenuRUDView(generics.RetrieveUpdateDestroyAPIView):
permission_classes = [IsEstablishmentManager] permission_classes = [IsEstablishmentManager]
class SocialChoiceListCreateView(generics.ListCreateAPIView):
"""SocialChoice list create view."""
serializer_class = serializers.SocialChoiceSerializers
queryset = models.SocialChoice.objects.all()
pagination_class = None
permission_classes = [permissions.IsAdminUser]
class SocialChoiceRUDView(generics.RetrieveUpdateDestroyAPIView):
"""SocialChoice RUD view."""
serializer_class = serializers.SocialChoiceSerializers
queryset = models.SocialChoice.objects.all()
permission_classes = [permissions.IsAdminUser]
class SocialListCreateView(generics.ListCreateAPIView): class SocialListCreateView(generics.ListCreateAPIView):
"""Social list create view.""" """Social list create view."""
serializer_class = serializers.SocialNetworkSerializers serializer_class = serializers.SocialNetworkSerializers
queryset = models.SocialNetwork.objects.all() queryset = models.SocialNetwork.objects.all()
pagination_class = None pagination_class = None
permission_classes = [IsEstablishmentManager] permission_classes = [permissions.IsAdminUser]
class SocialRUDView(generics.RetrieveUpdateDestroyAPIView): class SocialRUDView(generics.RetrieveUpdateDestroyAPIView):
@ -96,14 +111,14 @@ class PlateListCreateView(generics.ListCreateAPIView):
class PlateRUDView(generics.RetrieveUpdateDestroyAPIView): class PlateRUDView(generics.RetrieveUpdateDestroyAPIView):
"""Social RUD view.""" """Plate RUD view."""
serializer_class = serializers.PlatesSerializers serializer_class = serializers.PlatesSerializers
queryset = models.Plate.objects.all() queryset = models.Plate.objects.all()
permission_classes = [IsEstablishmentManager] permission_classes = [IsEstablishmentManager]
class PhonesListCreateView(generics.ListCreateAPIView): class PhonesListCreateView(generics.ListCreateAPIView):
"""Plate list create view.""" """Phones list create view."""
serializer_class = serializers.ContactPhoneBackSerializers serializer_class = serializers.ContactPhoneBackSerializers
queryset = models.ContactPhone.objects.all() queryset = models.ContactPhone.objects.all()
pagination_class = None pagination_class = None
@ -111,14 +126,14 @@ class PhonesListCreateView(generics.ListCreateAPIView):
class PhonesRUDView(generics.RetrieveUpdateDestroyAPIView): class PhonesRUDView(generics.RetrieveUpdateDestroyAPIView):
"""Social RUD view.""" """Phones RUD view."""
serializer_class = serializers.ContactPhoneBackSerializers serializer_class = serializers.ContactPhoneBackSerializers
queryset = models.ContactPhone.objects.all() queryset = models.ContactPhone.objects.all()
permission_classes = [IsEstablishmentManager] permission_classes = [IsEstablishmentManager]
class EmailListCreateView(generics.ListCreateAPIView): class EmailListCreateView(generics.ListCreateAPIView):
"""Plate list create view.""" """Email list create view."""
serializer_class = serializers.ContactEmailBackSerializers serializer_class = serializers.ContactEmailBackSerializers
queryset = models.ContactEmail.objects.all() queryset = models.ContactEmail.objects.all()
pagination_class = None pagination_class = None
@ -126,7 +141,7 @@ class EmailListCreateView(generics.ListCreateAPIView):
class EmailRUDView(generics.RetrieveUpdateDestroyAPIView): class EmailRUDView(generics.RetrieveUpdateDestroyAPIView):
"""Social RUD view.""" """Email RUD view."""
serializer_class = serializers.ContactEmailBackSerializers serializer_class = serializers.ContactEmailBackSerializers
queryset = models.ContactEmail.objects.all() queryset = models.ContactEmail.objects.all()
permission_classes = [IsEstablishmentManager] permission_classes = [IsEstablishmentManager]
@ -140,7 +155,7 @@ class EmployeeListCreateView(generics.ListCreateAPIView):
class EmployeeRUDView(generics.RetrieveUpdateDestroyAPIView): class EmployeeRUDView(generics.RetrieveUpdateDestroyAPIView):
"""Social RUD view.""" """Employee RUD view."""
serializer_class = serializers.EmployeeBackSerializers serializer_class = serializers.EmployeeBackSerializers
queryset = models.Employee.objects.all() queryset = models.Employee.objects.all()

View File

@ -5,7 +5,7 @@ from establishment.filters import EstablishmentFilter
from establishment.serializers import EstablishmentBaseSerializer from establishment.serializers import EstablishmentBaseSerializer
from news.filters import NewsListFilterSet from news.filters import NewsListFilterSet
from news.models import News from news.models import News
from news.serializers import NewsBaseSerializer from news.serializers import NewsBaseSerializer, NewsListSerializer
from product.models import Product from product.models import Product
from product.serializers import ProductBaseSerializer from product.serializers import ProductBaseSerializer
from product.filters import ProductFilterSet from product.filters import ProductFilterSet
@ -47,7 +47,7 @@ class FavoritesProductListView(generics.ListAPIView):
class FavoritesNewsListView(generics.ListAPIView): class FavoritesNewsListView(generics.ListAPIView):
"""List views for news in favorites.""" """List views for news in favorites."""
serializer_class = NewsBaseSerializer serializer_class = NewsListSerializer
filter_class = NewsListFilterSet filter_class = NewsListFilterSet
def get_queryset(self): def get_queryset(self):

View File

View File

@ -0,0 +1,19 @@
# Generated by Django 2.2.7 on 2019-11-13 12:40
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tag', '0014_tag_old_id_meta_product'),
('location', '0023_auto_20191112_0104'),
]
operations = [
migrations.AddField(
model_name='wineregion',
name='tag_categories',
field=models.ManyToManyField(blank=True, help_text='attribute from legacy db', related_name='wine_regions', to='tag.TagCategory'),
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 2.2.7 on 2019-11-14 08:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tag', '0014_tag_old_id_meta_product'),
('location', '0024_wineregion_tag_categories'),
]
operations = [
migrations.RemoveField(
model_name='wineregion',
name='tag_categories',
),
migrations.AddField(
model_name='wineregion',
name='tags',
field=models.ManyToManyField(blank=True, help_text='attribute from legacy db', related_name='wine_regions', to='tag.Tag'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-11-14 19:47
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('location', '0025_auto_20191114_0809'),
]
operations = [
migrations.AddField(
model_name='country',
name='is_active',
field=models.BooleanField(default=True, verbose_name='is active'),
),
]

View File

@ -11,6 +11,13 @@ from utils.models import (ProjectBaseMixin, SVGImageMixin, TJSONField,
TranslatedFieldsMixin, get_current_locale) TranslatedFieldsMixin, get_current_locale)
class CountryQuerySet(models.QuerySet):
"""Country queryset."""
def active(self, switcher=True):
"""Filter only active users."""
return self.filter(is_active=switcher)
class Country(TranslatedFieldsMixin, SVGImageMixin, ProjectBaseMixin): class Country(TranslatedFieldsMixin, SVGImageMixin, ProjectBaseMixin):
"""Country model.""" """Country model."""
@ -29,8 +36,11 @@ class Country(TranslatedFieldsMixin, SVGImageMixin, ProjectBaseMixin):
low_price = models.IntegerField(default=25, verbose_name=_('Low price')) low_price = models.IntegerField(default=25, verbose_name=_('Low price'))
high_price = models.IntegerField(default=50, verbose_name=_('High price')) high_price = models.IntegerField(default=50, verbose_name=_('High price'))
languages = models.ManyToManyField(Language, verbose_name=_('Languages')) languages = models.ManyToManyField(Language, verbose_name=_('Languages'))
is_active = models.BooleanField(_('is active'), default=True)
old_id = models.IntegerField(null=True, blank=True, default=None) old_id = models.IntegerField(null=True, blank=True, default=None)
objects = CountryQuerySet.as_manager()
@property @property
def time_format(self): def time_format(self):
if self.code.lower() not in self.TWELVE_HOURS_FORMAT_COUNTRIES: if self.code.lower() not in self.TWELVE_HOURS_FORMAT_COUNTRIES:
@ -200,7 +210,7 @@ class WineRegionQuerySet(models.QuerySet):
"""Wine region queryset.""" """Wine region queryset."""
class WineRegion(models.Model): class WineRegion(models.Model, TranslatedFieldsMixin):
"""Wine region model.""" """Wine region model."""
name = models.CharField(_('name'), max_length=255) name = models.CharField(_('name'), max_length=255)
country = models.ForeignKey(Country, on_delete=models.PROTECT, country = models.ForeignKey(Country, on_delete=models.PROTECT,
@ -213,6 +223,9 @@ class WineRegion(models.Model):
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"}')
tags = models.ManyToManyField('tag.Tag', blank=True,
related_name='wine_regions',
help_text='attribute from legacy db')
objects = WineRegionQuerySet.as_manager() objects = WineRegionQuerySet.as_manager()
@ -221,6 +234,10 @@ class WineRegion(models.Model):
verbose_name_plural = _('wine regions') verbose_name_plural = _('wine regions')
verbose_name = _('wine region') verbose_name = _('wine region')
def __str__(self):
"""Override dunder method."""
return self.name
class WineSubRegionQuerySet(models.QuerySet): class WineSubRegionQuerySet(models.QuerySet):
"""Wine sub region QuerySet.""" """Wine sub region QuerySet."""
@ -241,6 +258,10 @@ class WineSubRegion(models.Model):
verbose_name_plural = _('wine sub regions') verbose_name_plural = _('wine sub regions')
verbose_name = _('wine sub region') verbose_name = _('wine sub region')
def __str__(self):
"""Override dunder method."""
return self.name
class WineVillageQuerySet(models.QuerySet): class WineVillageQuerySet(models.QuerySet):
"""Wine village QuerySet.""" """Wine village QuerySet."""
@ -264,6 +285,10 @@ class WineVillage(models.Model):
verbose_name = _('wine village') verbose_name = _('wine village')
verbose_name_plural = _('wine villages') verbose_name_plural = _('wine villages')
def __str__(self):
"""Override str dunder."""
return self.name
# todo: Make recalculate price levels # todo: Make recalculate price levels
@receiver(post_save, sender=Country) @receiver(post_save, sender=Country)

View File

@ -357,10 +357,10 @@ def update_child_regions():
data_types = { data_types = {
"dictionaries": [ "dictionaries": [
# transfer_countries, transfer_countries,
# transfer_regions, transfer_regions,
# transfer_cities, transfer_cities,
# transfer_addresses, transfer_addresses,
transfer_wine_region, transfer_wine_region,
transfer_wine_sub_region, transfer_wine_sub_region,
transfer_wine_village, transfer_wine_village,

View File

@ -15,7 +15,7 @@ class CountryViewMixin(generics.GenericAPIView):
serializer_class = serializers.CountrySerializer serializer_class = serializers.CountrySerializer
permission_classes = (permissions.AllowAny,) permission_classes = (permissions.AllowAny,)
queryset = models.Country.objects.all() queryset = models.Country.objects.active()
class RegionViewMixin(generics.GenericAPIView): class RegionViewMixin(generics.GenericAPIView):

View File

@ -35,3 +35,8 @@ class CurrencyContentAdmin(admin.ModelAdmin):
@admin.register(models.Carousel) @admin.register(models.Carousel)
class CarouselAdmin(admin.ModelAdmin): class CarouselAdmin(admin.ModelAdmin):
"""Carousel admin.""" """Carousel admin."""
@admin.register(models.PageType)
class PageTypeAdmin(admin.ModelAdmin):
"""PageType admin."""

View File

@ -3,6 +3,7 @@ from django.db import connections
from establishment.management.commands.add_position import namedtuplefetchall from establishment.management.commands.add_position import namedtuplefetchall
from main.models import Award, AwardType from main.models import Award, AwardType
from establishment.models import Employee from establishment.models import Employee
from tqdm import tqdm
class Command(BaseCommand): class Command(BaseCommand):
@ -25,15 +26,18 @@ class Command(BaseCommand):
def handle(self, *args, **kwargs): def handle(self, *args, **kwargs):
objects =[] objects =[]
for a in self.award_sql(): Award.objects.all().delete()
profile = Employee.objects.filter(old_id=a.profile_id).first() for a in tqdm(self.award_sql(), desc='Add award to profile'):
type = AwardType.objects.filter(old_id=a.award_type).first() profiles = Employee.objects.filter(old_id=a.profile_id)
type = AwardType.objects.filter(old_id=a.award_type)
state = Award.PUBLISHED if a.state == 'published' else Award.WAITING state = Award.PUBLISHED if a.state == 'published' else Award.WAITING
if profile and type: if profiles.exists() and type.exists():
award = Award(award_type=type, vintage_year=a.vintage_year, for profile in profiles:
title={"en-GB": a.title}, state=state, award = Award(award_type=type.first(), vintage_year=a.vintage_year,
content_object=profile, old_id=a.id) title={"en-GB": a.title}, state=state,
objects.append(award) content_object=profile, old_id=a.id)
objects.append(award)
awards = Award.objects.bulk_create(objects) awards = Award.objects.bulk_create(objects)
self.stdout.write(self.style.WARNING(f'Created awards objects.')) self.stdout.write(self.style.WARNING(f'Created awards objects.'))

View File

@ -3,7 +3,7 @@ from django.db import connections
from establishment.management.commands.add_position import namedtuplefetchall from establishment.management.commands.add_position import namedtuplefetchall
from main.models import AwardType from main.models import AwardType
from location.models import Country from location.models import Country
from tqdm import tqdm
class Command(BaseCommand): class Command(BaseCommand):
help = '''Add award types from old db to new db. help = '''Add award types from old db to new db.
@ -24,7 +24,7 @@ class Command(BaseCommand):
def handle(self, *args, **kwargs): def handle(self, *args, **kwargs):
objects =[] objects =[]
for a in self.award_types_sql(): for a in tqdm(self.award_types_sql(), desc='Add award types: '):
country = Country.objects.filter(code=a.country_code).first() country = Country.objects.filter(code=a.country_code).first()
if country: if country:
type = AwardType(name=a.name, old_id=a.id) type = AwardType(name=a.name, old_id=a.id)

View File

@ -0,0 +1,85 @@
# Generated by Django 2.2.7 on 2019-11-15 07:50
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('advertisement', '0006_auto_20191115_0750'),
('main', '0035_merge_20191112_1218'),
]
operations = [
migrations.CreateModel(
name='PageType',
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')),
('name', models.CharField(max_length=255, unique=True, verbose_name='name')),
],
options={
'verbose_name': 'page type',
'verbose_name_plural': 'page types',
},
),
migrations.AlterModelOptions(
name='page',
options={'verbose_name': 'page', 'verbose_name_plural': 'pages'},
),
migrations.RemoveField(
model_name='page',
name='advertisements',
),
migrations.RemoveField(
model_name='page',
name='page_name',
),
migrations.AddField(
model_name='page',
name='advertisement',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='pages', to='advertisement.Advertisement', verbose_name='advertisement'),
),
migrations.AddField(
model_name='page',
name='created',
field=models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created'),
),
migrations.AddField(
model_name='page',
name='height',
field=models.PositiveIntegerField(null=True, verbose_name='Block height'),
),
migrations.AddField(
model_name='page',
name='image_url',
field=models.URLField(blank=True, default=None, null=True, verbose_name='Image URL path'),
),
migrations.AddField(
model_name='page',
name='modified',
field=models.DateTimeField(auto_now=True, verbose_name='Date updated'),
),
migrations.AddField(
model_name='page',
name='source',
field=models.PositiveSmallIntegerField(choices=[(0, 'Mobile'), (1, 'Web'), (2, 'All')], default=0, verbose_name='Source'),
),
migrations.AddField(
model_name='page',
name='width',
field=models.PositiveIntegerField(null=True, verbose_name='Block width'),
),
migrations.RemoveField(
model_name='feature',
name='route',
),
migrations.AddField(
model_name='feature',
name='route',
field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.PROTECT, to='main.PageType'),
),
]

View File

@ -10,7 +10,6 @@ from django.db import models
from django.db.models import Q from django.db.models import Q
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from advertisement.models import Advertisement
from configuration.models import TranslationSettings from configuration.models import TranslationSettings
from location.models import Country from location.models import Country
from main import methods from main import methods
@ -99,27 +98,12 @@ class SiteSettings(ProjectBaseMixin):
domain=settings.SITE_DOMAIN_URI) domain=settings.SITE_DOMAIN_URI)
class Page(models.Model):
"""Page model."""
page_name = models.CharField(max_length=255, unique=True)
advertisements = models.ManyToManyField(Advertisement)
class Meta:
"""Meta class."""
verbose_name = _('Page')
verbose_name_plural = _('Pages')
def __str__(self):
return f'{self.page_name}'
class Feature(ProjectBaseMixin, PlatformMixin): class Feature(ProjectBaseMixin, PlatformMixin):
"""Feature model.""" """Feature model."""
slug = models.SlugField(max_length=255, unique=True) slug = models.SlugField(max_length=255, unique=True)
priority = models.IntegerField(unique=True, null=True, default=None) priority = models.IntegerField(unique=True, null=True, default=None)
route = models.ForeignKey(Page, on_delete=models.PROTECT, null=True, default=None) route = models.ForeignKey('PageType', on_delete=models.PROTECT, null=True, default=None)
site_settings = models.ManyToManyField(SiteSettings, through='SiteFeature') site_settings = models.ManyToManyField(SiteSettings, through='SiteFeature')
class Meta: class Meta:
@ -310,3 +294,56 @@ class Carousel(models.Model):
elif self.link not in EMPTY_VALUES: elif self.link not in EMPTY_VALUES:
return 'external' return 'external'
return None return None
class PageQuerySet(models.QuerySet):
"""QuerySet for model Page."""
def by_platform(self, platform: int):
"""Filter by platform."""
return self.filter(source=platform)
class Page(URLImageMixin, PlatformMixin, ProjectBaseMixin):
"""Page model."""
advertisement = models.ForeignKey('advertisement.Advertisement',
on_delete=models.PROTECT, null=True,
related_name='pages',
verbose_name=_('advertisement'))
width = models.PositiveIntegerField(null=True,
verbose_name=_('Block width')) # 300
height = models.PositiveIntegerField(null=True,
verbose_name=_('Block height')) # 250
objects = PageQuerySet.as_manager()
class Meta:
"""Meta class."""
verbose_name = _('page')
verbose_name_plural = _('pages')
def __str__(self):
"""Overridden dunder method."""
return self.get_source_display()
class PageTypeQuerySet(models.QuerySet):
"""QuerySet for model PageType."""
class PageType(ProjectBaseMixin):
"""Page type model."""
name = models.CharField(max_length=255, unique=True,
verbose_name=_('name'))
objects = PageTypeQuerySet.as_manager()
class Meta:
"""Meta class."""
verbose_name = _('page type')
verbose_name_plural = _('page types')
def __str__(self):
"""Overridden dunder method."""
return self.name

View File

@ -1,7 +1,6 @@
"""Main app serializers.""" """Main app serializers."""
from rest_framework import serializers from rest_framework import serializers
from advertisement.serializers.web import AdvertisementSerializer
from location.serializers import CountrySerializer from location.serializers import CountrySerializer
from main import models from main import models
from utils.serializers import ProjectModelSerializer, TranslatedField, RecursiveFieldSerializer from utils.serializers import ProjectModelSerializer, TranslatedField, RecursiveFieldSerializer
@ -25,7 +24,7 @@ class SiteFeatureSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(source='feature.id') id = serializers.IntegerField(source='feature.id')
slug = serializers.CharField(source='feature.slug') slug = serializers.CharField(source='feature.slug')
priority = serializers.IntegerField(source='feature.priority') priority = serializers.IntegerField(source='feature.priority')
route = serializers.CharField(source='feature.route.page_name') route = serializers.CharField(source='feature.route.name')
source = serializers.IntegerField(source='feature.source') source = serializers.IntegerField(source='feature.source')
nested = RecursiveFieldSerializer(many=True, allow_null=True) nested = RecursiveFieldSerializer(many=True, allow_null=True)
@ -94,11 +93,20 @@ class SiteSerializer(serializers.ModelSerializer):
class Meta: class Meta:
"""Meta class.""" """Meta class."""
model = models.SiteSettings model = models.SiteSettings
fields = ('subdomain', 'site_url', 'country') fields = ('subdomain', 'site_url', 'country')
class SiteShortSerializer(serializers.ModelSerializer):
"""Short serializer for model SiteSettings."""
class Meta(SiteSerializer.Meta):
"""Meta class."""
fields = [
'subdomain',
]
# class SiteFeatureSerializer(serializers.ModelSerializer): # class SiteFeatureSerializer(serializers.ModelSerializer):
# """Site feature serializer.""" # """Site feature serializer."""
# #
@ -167,15 +175,27 @@ class CarouselListSerializer(serializers.ModelSerializer):
] ]
class PageSerializer(serializers.ModelSerializer): class PageBaseSerializer(serializers.ModelSerializer):
page_name = serializers.CharField() """Serializer for model Page"""
advertisements = AdvertisementSerializer(source='advertisements', many=True)
class Meta: class Meta:
"""Meta class.""" """Meta class."""
model = models.Carousel model = models.Page
fields = [ fields = [
'id', 'id',
'page_name', 'image_url',
'advertisements' 'width',
'height',
]
class PageTypeBaseSerializer(serializers.ModelSerializer):
"""Serializer fro model PageType."""
class Meta:
"""Meta class."""
model = models.PageType
fields = [
'id',
'name',
] ]

View File

@ -70,6 +70,9 @@ class CarouselListView(generics.ListAPIView):
def get_queryset(self): def get_queryset(self):
country_code = self.request.country_code country_code = self.request.country_code
if hasattr(settings, 'CAROUSEL_ITEMS') and country_code in ['www', 'main']:
qs = models.Carousel.objects.filter(id__in=settings.CAROUSEL_ITEMS)
return qs
qs = models.Carousel.objects.is_parsed().active() qs = models.Carousel.objects.is_parsed().active()
if country_code: if country_code:
qs = qs.by_country_code(country_code) qs = qs.by_country_code(country_code)

View File

@ -0,0 +1,20 @@
# Generated by Django 2.2.4 on 2019-11-14 20:43
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('rating', '0004_auto_20191114_2041'),
('news', '0034_merge_20191030_1714'),
]
operations = [
migrations.AddField(
model_name='news',
name='views_count',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='rating.ViewCount'),
),
]

View File

@ -1,15 +1,40 @@
"""News app models.""" """News app models."""
from django.contrib.contenttypes import fields as generic from django.contrib.contenttypes import fields as generic
from django.db import models from django.db import models
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 rating.models import Rating from rating.models import Rating, ViewCount
from utils.models import BaseAttributes, TJSONField, TranslatedFieldsMixin, ProjectBaseMixin from utils.models import BaseAttributes, TJSONField, TranslatedFieldsMixin, ProjectBaseMixin
from utils.querysets import TranslationQuerysetMixin from utils.querysets import TranslationQuerysetMixin
class Agenda(ProjectBaseMixin, TranslatedFieldsMixin):
"""News agenda model"""
event_datetime = models.DateTimeField(default=timezone.now, editable=False,
verbose_name=_('Event datetime'))
address = models.ForeignKey('location.Address', blank=True, null=True,
default=None, verbose_name=_('address'),
on_delete=models.SET_NULL)
content = TJSONField(blank=True, null=True, default=None,
verbose_name=_('content'),
help_text='{"en-GB":"some text"}')
class NewsBanner(ProjectBaseMixin, TranslatedFieldsMixin):
"""News banner model"""
title = TJSONField(blank=True, null=True, default=None,
verbose_name=_('title'),
help_text='{"en-GB":"some text"}')
image_url = models.URLField(verbose_name=_('Image URL path'),
blank=True, null=True, default=None)
content_url = models.URLField(verbose_name=_('Content URL path'),
blank=True, null=True, default=None)
class NewsType(models.Model): class NewsType(models.Model):
"""NewsType model.""" """NewsType model."""
@ -31,6 +56,10 @@ class NewsType(models.Model):
class NewsQuerySet(TranslationQuerysetMixin): class NewsQuerySet(TranslationQuerysetMixin):
"""QuerySet for model News""" """QuerySet for model News"""
def sort_by_start(self):
"""Return qs sorted by start DESC"""
return self.order_by('-start')
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))
@ -66,38 +95,29 @@ class NewsQuerySet(TranslationQuerysetMixin):
# todo: filter by best score # todo: filter by best score
# todo: filter by country? # todo: filter by country?
def should_read(self, news): def should_read(self, news, user):
return self.model.objects.exclude(pk=news.pk).published(). \ return self.model.objects.exclude(pk=news.pk).published(). \
annotate_in_favorites(user). \
with_base_related().by_type(news.news_type).distinct().order_by('?') with_base_related().by_type(news.news_type).distinct().order_by('?')
def same_theme(self, news): def same_theme(self, news, user):
return self.model.objects.exclude(pk=news.pk).published(). \ return self.model.objects.exclude(pk=news.pk).published(). \
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().order_by('-start')
def annotate_in_favorites(self, user):
class Agenda(ProjectBaseMixin, TranslatedFieldsMixin): """Annotate flag in_favorites"""
"""News agenda model""" favorite_news_ids = []
if user.is_authenticated:
event_datetime = models.DateTimeField(default=timezone.now, editable=False, favorite_news_ids = user.favorite_news_ids
verbose_name=_('Event datetime')) return self.annotate(
address = models.ForeignKey('location.Address', blank=True, null=True, in_favorites=Case(
default=None, verbose_name=_('address'), When(id__in=favorite_news_ids, then=True),
on_delete=models.SET_NULL) default=False,
content = TJSONField(blank=True, null=True, default=None, output_field=models.BooleanField(default=False)
verbose_name=_('content'), )
help_text='{"en-GB":"some text"}') )
class NewsBanner(ProjectBaseMixin, TranslatedFieldsMixin):
"""News banner model"""
title = TJSONField(blank=True, null=True, default=None,
verbose_name=_('title'),
help_text='{"en-GB":"some text"}')
image_url = models.URLField(verbose_name=_('Image URL path'),
blank=True, null=True, default=None)
content_url = models.URLField(verbose_name=_('Content URL path'),
blank=True, null=True, default=None)
class News(BaseAttributes, TranslatedFieldsMixin): class News(BaseAttributes, TranslatedFieldsMixin):
@ -164,6 +184,7 @@ class News(BaseAttributes, TranslatedFieldsMixin):
tags = models.ManyToManyField('tag.Tag', related_name='news', tags = models.ManyToManyField('tag.Tag', related_name='news',
verbose_name=_('Tags')) verbose_name=_('Tags'))
gallery = models.ManyToManyField('gallery.Image', through='news.NewsGallery') gallery = models.ManyToManyField('gallery.Image', through='news.NewsGallery')
views_count = models.OneToOneField('rating.ViewCount', blank=True, null=True, on_delete=models.SET_NULL)
ratings = generic.GenericRelation(Rating) ratings = generic.GenericRelation(Rating)
favorites = generic.GenericRelation(to='favorites.Favorites') favorites = generic.GenericRelation(to='favorites.Favorites')
agenda = models.ForeignKey('news.Agenda', blank=True, null=True, agenda = models.ForeignKey('news.Agenda', blank=True, null=True,
@ -193,13 +214,11 @@ class News(BaseAttributes, TranslatedFieldsMixin):
def web_url(self): def web_url(self):
return reverse('web:news:rud', kwargs={'slug': self.slug}) return reverse('web:news:rud', kwargs={'slug': self.slug})
@property def should_read(self, user):
def should_read(self): return self.__class__.objects.should_read(self, user)[:3]
return self.__class__.objects.should_read(self)[:3]
@property def same_theme(self, user):
def same_theme(self): return self.__class__.objects.same_theme(self, user)[:3]
return self.__class__.objects.same_theme(self)[:3]
@property @property
def main_image(self): def main_image(self):
@ -216,6 +235,13 @@ class News(BaseAttributes, TranslatedFieldsMixin):
if self.main_image: if self.main_image:
return self.main_image.get_image_url(thumbnail_key='news_preview') return self.main_image.get_image_url(thumbnail_key='news_preview')
@property
def view_counter(self):
count_value = 0
if self.views_count:
count_value = self.views_count.count
return count_value
class NewsGalleryQuerySet(models.QuerySet): class NewsGalleryQuerySet(models.QuerySet):
"""QuerySet for model News""" """QuerySet for model News"""

View File

@ -1,6 +1,7 @@
"""News app common serializers.""" """News app common serializers."""
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
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
@ -135,6 +136,8 @@ class NewsBaseSerializer(ProjectModelSerializer):
subtitle_translated = TranslatedField() subtitle_translated = TranslatedField()
news_type = NewsTypeSerializer(read_only=True) news_type = NewsTypeSerializer(read_only=True)
tags = TagBaseSerializer(read_only=True, many=True) tags = TagBaseSerializer(read_only=True, many=True)
in_favorites = serializers.BooleanField(allow_null=True)
view_counter = serializers.IntegerField(read_only=True)
class Meta: class Meta:
"""Meta class.""" """Meta class."""
@ -148,6 +151,8 @@ class NewsBaseSerializer(ProjectModelSerializer):
'news_type', 'news_type',
'tags', 'tags',
'slug', 'slug',
'in_favorites',
'view_counter',
) )
@ -204,8 +209,8 @@ class NewsDetailSerializer(NewsBaseSerializer):
class NewsDetailWebSerializer(NewsDetailSerializer): class NewsDetailWebSerializer(NewsDetailSerializer):
"""News detail serializer for web users..""" """News detail serializer for web users.."""
same_theme = NewsSimilarListSerializer(many=True, read_only=True) same_theme = SerializerMethodField()
should_read = NewsSimilarListSerializer(many=True, read_only=True) should_read = SerializerMethodField()
agenda = AgendaSerializer() agenda = AgendaSerializer()
banner = NewsBannerSerializer() banner = NewsBannerSerializer()
@ -219,6 +224,12 @@ class NewsDetailWebSerializer(NewsDetailSerializer):
'banner', 'banner',
) )
def get_same_theme(self, obj):
return NewsSimilarListSerializer(obj.same_theme(self.context['request'].user), many=True, read_only=True).data
def get_should_read(self, obj):
return NewsSimilarListSerializer(obj.should_read(self.context['request'].user), many=True, read_only=True).data
class NewsBackOfficeBaseSerializer(NewsBaseSerializer): class NewsBackOfficeBaseSerializer(NewsBaseSerializer):
"""News back office base serializer.""" """News back office base serializer."""
@ -290,6 +301,9 @@ class NewsBackOfficeGallerySerializer(serializers.ModelSerializer):
news = news_qs.first() news = news_qs.first()
image = image_qs.first() image = image_qs.first()
if image in news.gallery.all():
raise serializers.ValidationError({'detail': _('Image is already added.')})
attrs['news'] = news attrs['news'] = news
attrs['image'] = image attrs['image'] = image

View File

@ -9,6 +9,7 @@ from gallery.tasks import delete_image
from news import filters, models, serializers from news import filters, models, serializers
from rating.tasks import add_rating from rating.tasks import add_rating
from utils.permissions import IsCountryAdmin, IsContentPageManager from utils.permissions import IsCountryAdmin, IsContentPageManager
from utils.views import CreateDestroyGalleryViewMixin
class NewsMixinView: class NewsMixinView:
@ -17,11 +18,13 @@ class NewsMixinView:
permission_classes = (permissions.AllowAny,) permission_classes = (permissions.AllowAny,)
serializer_class = serializers.NewsBaseSerializer serializer_class = serializers.NewsBaseSerializer
def get_queryset(self, *args, **kwargs): def get_queryset(self):
"""Override get_queryset method.""" """Override get_queryset method."""
qs = models.News.objects.published() \ qs = models.News.objects.published() \
.with_base_related() \ .with_base_related() \
.annotate_in_favorites(self.request.user) \
.order_by('-is_highlighted', '-created') .order_by('-is_highlighted', '-created')
country_code = self.request.country_code country_code = self.request.country_code
if country_code: if country_code:
qs = qs.by_country_code(country_code) qs = qs.by_country_code(country_code)
@ -41,10 +44,10 @@ class NewsDetailView(NewsMixinView, generics.RetrieveAPIView):
lookup_field = 'slug' lookup_field = 'slug'
serializer_class = serializers.NewsDetailWebSerializer serializer_class = serializers.NewsDetailWebSerializer
queryset = models.News.objects.all()
def get_queryset(self): def get_queryset(self):
return self.queryset """Override get_queryset method."""
qs = models.News.objects.all().annotate_in_favorites(self.request.user)
return qs
class NewsTypeListView(generics.ListAPIView): class NewsTypeListView(generics.ListAPIView):
@ -60,8 +63,13 @@ class NewsBackOfficeMixinView:
"""News back office mixin view.""" """News back office mixin view."""
permission_classes = (permissions.IsAuthenticated,) permission_classes = (permissions.IsAuthenticated,)
queryset = models.News.objects.with_base_related() \
.order_by('-is_highlighted', '-created') def get_queryset(self):
"""Override get_queryset method."""
qs = models.News.objects.with_base_related() \
.annotate_in_favorites(self.request.user) \
.order_by('-is_highlighted', '-created')
return qs
class NewsBackOfficeLCView(NewsBackOfficeMixinView, class NewsBackOfficeLCView(NewsBackOfficeMixinView,
@ -84,8 +92,7 @@ class NewsBackOfficeLCView(NewsBackOfficeMixinView,
class NewsBackOfficeGalleryCreateDestroyView(NewsBackOfficeMixinView, class NewsBackOfficeGalleryCreateDestroyView(NewsBackOfficeMixinView,
generics.CreateAPIView, CreateDestroyGalleryViewMixin):
generics.DestroyAPIView):
"""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
@ -103,24 +110,6 @@ class NewsBackOfficeGalleryCreateDestroyView(NewsBackOfficeMixinView,
return gallery return gallery
def create(self, request, *args, **kwargs):
"""Overridden create method"""
super().create(request, *args, **kwargs)
return Response(status=status.HTTP_201_CREATED)
def destroy(self, request, *args, **kwargs):
"""Override destroy method."""
gallery_obj = self.get_object()
if settings.USE_CELERY:
on_commit(lambda: delete_image.delay(image_id=gallery_obj.image.id,
completely=False))
else:
on_commit(lambda: delete_image(image_id=gallery_obj.image.id,
completely=False))
# Delete an instances of NewsGallery model
gallery_obj.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
class NewsBackOfficeGalleryListView(NewsBackOfficeMixinView, generics.ListAPIView): class NewsBackOfficeGalleryListView(NewsBackOfficeMixinView, generics.ListAPIView):
"""Resource for returning gallery for news for back-office users.""" """Resource for returning gallery for news for back-office users."""

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.7 on 2019-11-15 07:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('notification', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='subscriber',
name='old_id',
field=models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='old id'),
),
]

View File

@ -74,21 +74,29 @@ class Subscriber(ProjectBaseMixin):
(USABLE, _('Usable')), (USABLE, _('Usable')),
) )
user = models.OneToOneField(User, blank=True, null=True, default=None, user = models.OneToOneField(
on_delete=models.SET_NULL, related_name='subscriber', User,
verbose_name=_('User')) blank=True,
email = models.EmailField(blank=True, null=True, default=None, unique=True, null=True,
verbose_name=_('Email')) default=None,
ip_address = models.GenericIPAddressField(blank=True, null=True, default=None, on_delete=models.SET_NULL,
verbose_name=_('IP address')) related_name='subscriber',
country_code = models.CharField(max_length=10, blank=True, null=True, default=None, verbose_name=_('User'),
verbose_name=_('Country code')) )
locale = models.CharField(blank=True, null=True, default=None, email = models.EmailField(blank=True, null=True, default=None, unique=True, verbose_name=_('Email'))
max_length=10, verbose_name=_('Locale identifier')) ip_address = models.GenericIPAddressField(blank=True, null=True, default=None, verbose_name=_('IP address'))
state = models.PositiveIntegerField(choices=STATE_CHOICES, default=USABLE, country_code = models.CharField(max_length=10, blank=True, null=True, default=None, verbose_name=_('Country code'))
verbose_name=_('State')) locale = models.CharField(blank=True, null=True, default=None, max_length=10, verbose_name=_('Locale identifier'))
update_code = models.CharField(max_length=254, blank=True, null=True, default=None, state = models.PositiveIntegerField(choices=STATE_CHOICES, default=USABLE, verbose_name=_('State'))
db_index=True, verbose_name=_('Token')) update_code = models.CharField(
max_length=254,
blank=True,
null=True,
default=None,
db_index=True,
verbose_name=_('Token'),
)
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
objects = SubscriberManager.from_queryset(SubscriberQuerySet)() objects = SubscriberManager.from_queryset(SubscriberQuerySet)()

View File

@ -1,21 +1,41 @@
from transfer.serializers.notification import SubscriberSerializer
from notification.models import Subscriber
from transfer.models import EmailAddresses
from django.db.models import Value, IntegerField, F
from pprint import pprint from pprint import pprint
from django.db.models import Count
from transfer.models import EmailAddresses, NewsletterSubscriber
from transfer.serializers.notification import SubscriberSerializer, NewsletterSubscriberSerializer
def transfer_subscriber(): def transfer_subscriber():
queryset = EmailAddresses.objects.filter(state="usable") queryset = EmailAddresses.objects.filter(state='usable')
serialized_data = SubscriberSerializer(data=list(queryset.values()), many=True) serialized_data = SubscriberSerializer(data=list(queryset.values()), 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'News serializer errors: {serialized_data.errors}')
def transfer_newsletter_subscriber():
queryset = NewsletterSubscriber.objects.all().values(
'id',
'email_address__email',
'email_address__account_id',
'email_address__ip',
'email_address__country_code',
'email_address__locale',
'created_at',
)
# serialized_data = NewsletterSubscriberSerializer(data=list(queryset.values()), many=True)
# if serialized_data.is_valid():
# serialized_data.save()
# else:
# pprint(f'NewsletterSubscriber serializer errors: {serialized_data.errors}')
data_types = { data_types = {
"subscriber": [transfer_subscriber] 'subscriber': [transfer_subscriber],
'newsletter_subscriber': [transfer_newsletter_subscriber],
} }

View File

@ -11,7 +11,7 @@ class ProductAdmin(BaseModelAdminMixin, admin.ModelAdmin):
list_filter = ('available', 'product_type') list_filter = ('available', 'product_type')
list_display = ('id', '__str__', 'get_category_display', 'product_type') list_display = ('id', '__str__', 'get_category_display', 'product_type')
raw_id_fields = ('subtypes', 'classifications', 'standards', raw_id_fields = ('subtypes', 'classifications', 'standards',
'tags', 'gallery') 'tags', 'gallery', 'establishment',)
@admin.register(ProductGallery) @admin.register(ProductGallery)

View File

@ -9,10 +9,8 @@ class ProductFilterSet(filters.FilterSet):
"""Product filter set.""" """Product filter set."""
establishment_id = filters.NumberFilter() establishment_id = filters.NumberFilter()
product_type = filters.ChoiceFilter(method='by_product_type', product_type = filters.CharFilter(method='by_product_type')
choices=models.ProductType.INDEX_NAME_TYPES) product_subtype = filters.CharFilter(method='by_product_subtype')
product_subtype = filters.ChoiceFilter(method='by_product_subtype',
choices=models.ProductSubType.INDEX_NAME_TYPES)
class Meta: class Meta:
"""Meta class.""" """Meta class."""

View File

@ -1,7 +1,7 @@
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from transfer.models import Assemblages from transfer.models import Assemblages
from transfer.serializers.product import AssemblageTagSerializer from transfer.serializers.product import AssemblageProductTagSerializer
class Command(BaseCommand): class Command(BaseCommand):
@ -10,7 +10,7 @@ class Command(BaseCommand):
def handle(self, *args, **kwarg): def handle(self, *args, **kwarg):
errors = [] errors = []
legacy_products = Assemblages.objects.filter(product_id__isnull=False) legacy_products = Assemblages.objects.filter(product_id__isnull=False)
serialized_data = AssemblageTagSerializer( serialized_data = AssemblageProductTagSerializer(
data=list(legacy_products.values()), data=list(legacy_products.values()),
many=True) many=True)
if serialized_data.is_valid(): if serialized_data.is_valid():

View File

@ -4,7 +4,6 @@ from establishment.management.commands.add_position import namedtuplefetchall
from tag.models import Tag, TagCategory from tag.models import Tag, TagCategory
from product.models import Product from product.models import Product
from tqdm import tqdm from tqdm import tqdm
from django.db.models.functions import Lower
class Command(BaseCommand): class Command(BaseCommand):
@ -27,8 +26,7 @@ class Command(BaseCommand):
def add_category_tag(self): def add_category_tag(self):
objects = [] objects = []
for c in tqdm(self.category_sql(), desc='Add category tags'): for c in tqdm(self.category_sql(), desc='Add category tags'):
categories = TagCategory.objects.filter(index_name=c.category, categories = TagCategory.objects.filter(index_name=c.category
value_type=c.value_type
) )
if not categories.exists(): if not categories.exists():
objects.append( objects.append(
@ -45,6 +43,7 @@ class Command(BaseCommand):
cursor.execute(''' cursor.execute('''
select select
DISTINCT DISTINCT
m.id as old_id,
trim(CONVERT(m.value USING utf8)) as tag_value, trim(CONVERT(m.value USING utf8)) as tag_value,
trim(CONVERT(v.key_name USING utf8)) as tag_category trim(CONVERT(v.key_name USING utf8)) as tag_category
FROM product_metadata m FROM product_metadata m
@ -57,16 +56,21 @@ class Command(BaseCommand):
for t in tqdm(self.tag_sql(), desc='Add tags'): for t in tqdm(self.tag_sql(), desc='Add tags'):
category = TagCategory.objects.get(index_name=t.tag_category) category = TagCategory.objects.get(index_name=t.tag_category)
tag = Tag.objects.filter( tags = Tag.objects.filter(
category=category, category=category,
value=t.tag_value value=t.tag_value
) )
if not tag.exists(): if not tags.exists():
objects.append(Tag(label={"en-GB": t.tag_value}, objects.append(Tag(label={"en-GB": t.tag_value},
category=category, category=category,
value=t.tag_value value=t.tag_value,
old_id_meta_product=t.old_id
)) ))
else:
qs = tags.filter(old_id_meta_product__isnull=True)\
.update(old_id_meta_product=t.old_id)
Tag.objects.bulk_create(objects) Tag.objects.bulk_create(objects)
self.stdout.write(self.style.WARNING(f'Add or get tag objects.')) self.stdout.write(self.style.WARNING(f'Add or get tag objects.'))
@ -75,6 +79,7 @@ class Command(BaseCommand):
cursor.execute(''' cursor.execute('''
select select
DISTINCT DISTINCT
m.id as old_id_tag,
m.product_id, m.product_id,
lower(trim(CONVERT(m.value USING utf8))) as tag_value, lower(trim(CONVERT(m.value USING utf8))) as tag_value,
trim(CONVERT(v.key_name USING utf8)) as tag_category trim(CONVERT(v.key_name USING utf8)) as tag_category
@ -84,15 +89,12 @@ class Command(BaseCommand):
return namedtuplefetchall(cursor) return namedtuplefetchall(cursor)
def add_product_tag(self): def add_product_tag(self):
objects = []
for t in tqdm(self.product_sql(), desc='Add product tag'): for t in tqdm(self.product_sql(), desc='Add product tag'):
category = TagCategory.objects.get(index_name=t.tag_category) tags = Tag.objects.filter(old_id_meta_product=t.old_id_tag)
tag = Tag.objects.annotate(lower_value=Lower('value')) product = Product.objects.get(old_id=t.product_id)
tag.filter(lower_value=t.tag_value, category=category) for tag in tags:
products = Product.objects.filter(old_id=t.product_id) if product not in tag.products.all():
if products.exists(): product.tags.add(tag)
products.tags.add(tag)
products.save()
self.stdout.write(self.style.WARNING(f'Add or get tag objects.')) self.stdout.write(self.style.WARNING(f'Add or get tag objects.'))
@ -108,9 +110,8 @@ class Command(BaseCommand):
tag.label = new_label tag.label = new_label
tag.save() tag.save()
def handle(self, *args, **kwargs): def handle(self, *args, **kwargs):
self.add_category_tag() self.add_category_tag()
self.add_tag() self.add_tag()
self.add_product_tag()
self.check_tag() self.check_tag()
self.add_product_tag()

View File

@ -0,0 +1,29 @@
# Generated by Django 2.2.7 on 2019-11-13 15:12
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('product', '0012_auto_20191112_1007'),
]
operations = [
migrations.AlterField(
model_name='product',
name='wine_village',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='location.WineVillage', verbose_name='wine village'),
),
migrations.AlterField(
model_name='productsubtype',
name='index_name',
field=models.CharField(db_index=True, max_length=50, unique=True, verbose_name='Index name'),
),
migrations.AlterField(
model_name='producttype',
name='index_name',
field=models.CharField(db_index=True, max_length=50, unique=True, verbose_name='Index name'),
),
]

View File

@ -3,6 +3,7 @@ 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.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.db.models import Case, When
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.core.validators import MaxValueValidator, MinValueValidator from django.core.validators import MaxValueValidator, MinValueValidator
@ -15,25 +16,16 @@ class ProductType(TranslatedFieldsMixin, ProjectBaseMixin):
STR_FIELD_NAME = 'name' STR_FIELD_NAME = 'name'
# INDEX NAME CHOICES # EXAMPLE OF INDEX NAME CHOICES
FOOD = 'food' FOOD = 'food'
WINE = 'wine' WINE = 'wine'
LIQUOR = 'liquor' LIQUOR = 'liquor'
SOUVENIR = 'souvenir' SOUVENIR = 'souvenir'
BOOK = 'book' BOOK = 'book'
INDEX_NAME_TYPES = (
(FOOD, _('Food')),
(WINE, _('Wine')),
(LIQUOR, _('Liquor')),
(SOUVENIR, _('Souvenir')),
(BOOK, _('Book')),
)
name = TJSONField(blank=True, null=True, default=None, name = TJSONField(blank=True, null=True, default=None,
verbose_name=_('Name'), help_text='{"en-GB":"some text"}') verbose_name=_('Name'), help_text='{"en-GB":"some text"}')
index_name = models.CharField(max_length=50, choices=INDEX_NAME_TYPES, index_name = models.CharField(max_length=50, unique=True, db_index=True,
unique=True, db_index=True,
verbose_name=_('Index name')) verbose_name=_('Index name'))
use_subtypes = models.BooleanField(_('Use subtypes'), default=True) use_subtypes = models.BooleanField(_('Use subtypes'), default=True)
tag_categories = models.ManyToManyField('tag.TagCategory', tag_categories = models.ManyToManyField('tag.TagCategory',
@ -52,24 +44,17 @@ class ProductSubType(TranslatedFieldsMixin, ProjectBaseMixin):
STR_FIELD_NAME = 'name' STR_FIELD_NAME = 'name'
# INDEX NAME CHOICES # EXAMPLE OF INDEX NAME CHOICES
RUM = 'rum' RUM = 'rum'
PLATE = 'plate' PLATE = 'plate'
OTHER = 'other' OTHER = 'other'
INDEX_NAME_TYPES = (
(RUM, _('Rum')),
(PLATE, _('Plate')),
(OTHER, _('Other')),
)
product_type = models.ForeignKey(ProductType, on_delete=models.CASCADE, product_type = models.ForeignKey(ProductType, on_delete=models.CASCADE,
related_name='subtypes', related_name='subtypes',
verbose_name=_('Product type')) verbose_name=_('Product type'))
name = TJSONField(blank=True, null=True, default=None, name = TJSONField(blank=True, null=True, default=None,
verbose_name=_('Name'), help_text='{"en-GB":"some text"}') verbose_name=_('Name'), help_text='{"en-GB":"some text"}')
index_name = models.CharField(max_length=50, choices=INDEX_NAME_TYPES, index_name = models.CharField(max_length=50, unique=True, db_index=True,
unique=True, db_index=True,
verbose_name=_('Index name')) verbose_name=_('Index name'))
class Meta: class Meta:
@ -92,7 +77,14 @@ class ProductQuerySet(models.QuerySet):
def with_base_related(self): def with_base_related(self):
return self.select_related('product_type', 'establishment') \ return self.select_related('product_type', 'establishment') \
.prefetch_related('product_type__subtypes') .prefetch_related('product_type__subtypes')
def with_extended_related(self):
"""Returns qs with almost all related objects."""
return self.with_base_related() \
.prefetch_related('tags', 'standards', 'classifications', 'classifications__standard',
'classifications__classification_type', 'classifications__tags') \
.select_related('wine_region', 'wine_sub_region')
def common(self): def common(self):
return self.filter(category=self.model.COMMON) return self.filter(category=self.model.COMMON)
@ -101,15 +93,36 @@ class ProductQuerySet(models.QuerySet):
return self.filter(category=self.model.ONLINE) return self.filter(category=self.model.ONLINE)
def wines(self): def wines(self):
return self.filter(type__index_name=ProductType.WINE) return self.filter(type__index_name__icontains=ProductType.WINE)
def by_product_type(self, product_type: str): def by_product_type(self, product_type: str):
"""Filter by type.""" """Filter by type."""
return self.filter(product_type__index_name=product_type) return self.filter(product_type__index_name__icontains=product_type)
def by_product_subtype(self, product_subtype: str): def by_product_subtype(self, product_subtype: str):
"""Filter by subtype.""" """Filter by subtype."""
return self.filter(subtypes__index_name=product_subtype) return self.filter(subtypes__index_name__icontains=product_subtype)
def by_country_code(self, country_code):
"""Filter by country of produce."""
return self.filter(establishment__address__city__country__code=country_code)
def published(self):
"""Filter products by published state."""
return self.filter(state=self.model.PUBLISHED)
def annotate_in_favorites(self, user):
"""Annotate flag in_favorites"""
favorite_product_ids = []
if user.is_authenticated:
favorite_product_ids = user.favorite_product_ids
return self.annotate(
in_favorites=Case(
When(id__in=favorite_product_ids, then=True),
default=False,
output_field=models.BooleanField(default=False)
)
)
class Product(TranslatedFieldsMixin, BaseAttributes): class Product(TranslatedFieldsMixin, BaseAttributes):
@ -155,7 +168,7 @@ class Product(TranslatedFieldsMixin, BaseAttributes):
related_name='products', related_name='products',
verbose_name=_('establishment')) verbose_name=_('establishment'))
public_mark = models.PositiveIntegerField(blank=True, null=True, default=None, public_mark = models.PositiveIntegerField(blank=True, null=True, default=None,
verbose_name=_('public mark'),) verbose_name=_('public mark'), )
wine_region = models.ForeignKey('location.WineRegion', on_delete=models.PROTECT, wine_region = models.ForeignKey('location.WineRegion', on_delete=models.PROTECT,
related_name='wines', related_name='wines',
blank=True, null=True, default=None, blank=True, null=True, default=None,
@ -173,7 +186,7 @@ class Product(TranslatedFieldsMixin, BaseAttributes):
help_text=_('attribute from legacy db')) help_text=_('attribute from legacy db'))
wine_village = models.ForeignKey('location.WineVillage', on_delete=models.PROTECT, wine_village = models.ForeignKey('location.WineVillage', on_delete=models.PROTECT,
blank=True, null=True, blank=True, null=True,
verbose_name=_('wine appellation')) verbose_name=_('wine village'))
slug = models.SlugField(unique=True, max_length=255, null=True, slug = models.SlugField(unique=True, max_length=255, null=True,
verbose_name=_('Slug')) verbose_name=_('Slug'))
favorites = generic.GenericRelation(to='favorites.Favorites') favorites = generic.GenericRelation(to='favorites.Favorites')
@ -258,7 +271,16 @@ class Product(TranslatedFieldsMixin, BaseAttributes):
@property @property
def related_tags(self): def related_tags(self):
return self.tags.exclude( return self.tags.exclude(
category__index_name__in=['sugar-content', 'wine-color', 'bottles-produced']) category__index_name__in=['sugar-content', 'wine-color', 'bottles-produced',
'serial-number', 'grape-variety'])
@property
def display_name(self):
name = f'{self.name} ' \
f'({self.vintage if self.vintage else "BSA"})'
if self.establishment.name:
name = f'{self.establishment.name} - ' + name
return name
class OnlineProductManager(ProductManager): class OnlineProductManager(ProductManager):

View File

@ -2,9 +2,11 @@
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from product import models
from product.serializers import ProductDetailSerializer
from gallery.models import Image from gallery.models import Image
from product import models
from product.serializers import ProductDetailSerializer, ProductTypeBaseSerializer, \
ProductSubTypeBaseSerializer
from tag.models import TagCategory
class ProductBackOfficeGallerySerializer(serializers.ModelSerializer): class ProductBackOfficeGallerySerializer(serializers.ModelSerializer):
@ -33,12 +35,16 @@ class ProductBackOfficeGallerySerializer(serializers.ModelSerializer):
if not product_qs.exists(): if not product_qs.exists():
raise serializers.ValidationError({'detail': _('Product not found')}) raise serializers.ValidationError({'detail': _('Product not found')})
if not image_qs.exists(): if not image_qs.exists():
raise serializers.ValidationError({'detail': _('Image not found')}) raise serializers.ValidationError({'detail': _('Image not found')})
product = product_qs.first() product = product_qs.first()
image = image_qs.first() image = image_qs.first()
if image in product.gallery.all():
raise serializers.ValidationError({'detail': _('Image is already added.')})
attrs['product'] = product attrs['product'] = product
attrs['image'] = image attrs['image'] = image
@ -46,8 +52,10 @@ class ProductBackOfficeGallerySerializer(serializers.ModelSerializer):
class ProductBackOfficeDetailSerializer(ProductDetailSerializer): class ProductBackOfficeDetailSerializer(ProductDetailSerializer):
"""Product back-office detail serializer."""
class Meta(ProductDetailSerializer.Meta): class Meta(ProductDetailSerializer.Meta):
"""Meta class."""
fields = ProductDetailSerializer.Meta.fields + [ fields = ProductDetailSerializer.Meta.fields + [
'description', 'description',
'available', 'available',
@ -58,13 +66,64 @@ class ProductBackOfficeDetailSerializer(ProductDetailSerializer):
'wine_village', 'wine_village',
'state', 'state',
] ]
extra_kwargs = {
'description': {'write_only': True},
'available': {'write_only': True}, class ProductTypeBackOfficeDetailSerializer(ProductTypeBaseSerializer):
'product_type': {'write_only': True}, """Product type back-office detail serializer."""
'establishment': {'write_only': True},
'wine_region': {'write_only': True}, class Meta(ProductTypeBaseSerializer.Meta):
'wine_sub_region': {'write_only': True}, """Meta class."""
'wine_village': {'write_only': True}, fields = ProductTypeBaseSerializer.Meta.fields + [
'state': {'write_only': True}, 'name',
} 'use_subtypes',
]
class ProductTypeTagCategorySerializer(serializers.ModelSerializer):
"""Serializer for attaching tag category to product type."""
product_type_id = serializers.PrimaryKeyRelatedField(
queryset=models.ProductType.objects.all(),
write_only=True)
tag_category_id = serializers.PrimaryKeyRelatedField(
queryset=TagCategory.objects.all(),
write_only=True)
class Meta(ProductTypeBaseSerializer.Meta):
"""Meta class."""
fields = [
'product_type_id',
'tag_category_id',
]
def validate(self, attrs):
"""Validation method."""
product_type = attrs.pop('product_type_id')
tag_category = attrs.get('tag_category_id')
if tag_category in product_type.tag_categories.all():
raise serializers.ValidationError({
'detail': _('Tag category is already attached.')})
attrs['product_type'] = product_type
attrs['tag_category'] = tag_category
return attrs
def create(self, validated_data):
"""Overridden create method."""
product_type = validated_data.get('product_type')
tag_category = validated_data.get('tag_category')
product_type.tag_categories.add(tag_category)
return product_type
class ProductSubTypeBackOfficeDetailSerializer(ProductSubTypeBaseSerializer):
"""Product sub type back-office detail serializer."""
class Meta(ProductSubTypeBaseSerializer.Meta):
"""Meta class."""
fields = ProductSubTypeBaseSerializer.Meta.fields + [
'product_type',
'name',
'index_name',
]

View File

@ -12,13 +12,27 @@ from utils import exceptions as utils_exceptions
from utils.serializers import TranslatedField, FavoritesCreateSerializer from utils.serializers import TranslatedField, FavoritesCreateSerializer
from main.serializers import AwardSerializer from main.serializers import AwardSerializer
from location.serializers import WineRegionBaseSerializer, WineSubRegionBaseSerializer from location.serializers import WineRegionBaseSerializer, WineSubRegionBaseSerializer
from tag.serializers import TagBaseSerializer from tag.serializers import TagBaseSerializer, TagCategoryShortSerializer
class ProductTagSerializer(TagBaseSerializer):
"""Serializer for model Tag."""
category = TagCategoryShortSerializer(read_only=True)
class Meta(TagBaseSerializer.Meta):
"""Meta class."""
fields = TagBaseSerializer.Meta.fields + (
'category',
)
class ProductSubTypeBaseSerializer(serializers.ModelSerializer): class ProductSubTypeBaseSerializer(serializers.ModelSerializer):
"""ProductSubType base serializer""" """ProductSubType base serializer"""
name_translated = TranslatedField() name_translated = TranslatedField()
index_name_display = serializers.CharField(source='get_index_name_display') index_name_display = serializers.CharField(source='get_index_name_display',
read_only=True)
class Meta: class Meta:
model = models.ProductSubType model = models.ProductSubType
@ -32,14 +46,13 @@ class ProductSubTypeBaseSerializer(serializers.ModelSerializer):
class ProductTypeBaseSerializer(serializers.ModelSerializer): class ProductTypeBaseSerializer(serializers.ModelSerializer):
"""ProductType base serializer""" """ProductType base serializer"""
name_translated = TranslatedField() name_translated = TranslatedField()
index_name_display = serializers.CharField(source='get_index_name_display')
class Meta: class Meta:
model = models.ProductType model = models.ProductType
fields = [ fields = [
'id', 'id',
'name_translated', 'name_translated',
'index_name_display', 'index_name',
] ]
@ -72,13 +85,17 @@ class ProductStandardBaseSerializer(serializers.ModelSerializer):
class ProductBaseSerializer(serializers.ModelSerializer): class ProductBaseSerializer(serializers.ModelSerializer):
"""Product base serializer.""" """Product base serializer."""
product_type = serializers.CharField(source='product_type_translated_name', read_only=True) name = serializers.CharField(source='display_name', read_only=True)
product_type = ProductTypeBaseSerializer(read_only=True)
subtypes = ProductSubTypeBaseSerializer(many=True, read_only=True) subtypes = ProductSubTypeBaseSerializer(many=True, read_only=True)
establishment_detail = EstablishmentShortSerializer(source='establishment', read_only=True) establishment_detail = EstablishmentShortSerializer(source='establishment', read_only=True)
tags = TagBaseSerializer(source='related_tags', many=True, read_only=True) tags = ProductTagSerializer(source='related_tags', many=True, read_only=True)
wine_region = WineRegionBaseSerializer(read_only=True)
wine_colors = TagBaseSerializer(many=True, read_only=True)
preview_image_url = serializers.URLField(source='preview_main_image_url', preview_image_url = serializers.URLField(source='preview_main_image_url',
allow_null=True, allow_null=True,
read_only=True) read_only=True)
in_favorites = serializers.BooleanField(allow_null=True)
class Meta: class Meta:
"""Meta class.""" """Meta class."""
@ -94,6 +111,9 @@ class ProductBaseSerializer(serializers.ModelSerializer):
'vintage', 'vintage',
'tags', 'tags',
'preview_image_url', 'preview_image_url',
'wine_region',
'wine_colors',
'in_favorites',
] ]
@ -104,9 +124,7 @@ class ProductDetailSerializer(ProductBaseSerializer):
awards = AwardSerializer(many=True, read_only=True) awards = AwardSerializer(many=True, read_only=True)
classifications = ProductClassificationBaseSerializer(many=True, read_only=True) classifications = ProductClassificationBaseSerializer(many=True, read_only=True)
standards = ProductStandardBaseSerializer(many=True, read_only=True) standards = ProductStandardBaseSerializer(many=True, read_only=True)
wine_region = WineRegionBaseSerializer(read_only=True)
wine_sub_region = WineSubRegionBaseSerializer(read_only=True) wine_sub_region = WineSubRegionBaseSerializer(read_only=True)
wine_colors = TagBaseSerializer(many=True, read_only=True)
bottles_produced = TagBaseSerializer(many=True, read_only=True) bottles_produced = TagBaseSerializer(many=True, read_only=True)
sugar_contents = TagBaseSerializer(many=True, read_only=True) sugar_contents = TagBaseSerializer(many=True, read_only=True)
image_url = serializers.ImageField(source='main_image_url', image_url = serializers.ImageField(source='main_image_url',
@ -120,9 +138,7 @@ class ProductDetailSerializer(ProductBaseSerializer):
'awards', 'awards',
'classifications', 'classifications',
'standards', 'standards',
'wine_region',
'wine_sub_region', 'wine_sub_region',
'wine_colors',
'bottles_produced', 'bottles_produced',
'sugar_contents', 'sugar_contents',
'image_url', 'image_url',

View File

@ -1,14 +1,24 @@
"""Product backoffice url patterns.""" """Product backoffice url patterns."""
from django.urls import path from django.urls import path
from product.urls.common import urlpatterns as common_urlpatterns
from product import views from product import views
urlpatterns = [ urlpatterns = [
path('', views.ProductListCreateBackOfficeView.as_view(), name='list-create'),
path('<int:pk>/', views.ProductDetailBackOfficeView.as_view(), name='rud'), path('<int:pk>/', views.ProductDetailBackOfficeView.as_view(), name='rud'),
path('<int:pk>/gallery/', views.ProductBackOfficeGalleryListView.as_view(), path('<int:pk>/gallery/', views.ProductBackOfficeGalleryListView.as_view(),
name='gallery-list'), name='gallery-list'),
path('<int:pk>/gallery/<int:image_id>/', views.ProductBackOfficeGalleryCreateDestroyView.as_view(), path('<int:pk>/gallery/<int:image_id>/', views.ProductBackOfficeGalleryCreateDestroyView.as_view(),
name='gallery-create-destroy'), name='gallery-create-destroy'),
# product types
path('types/', views.ProductTypeListCreateBackOfficeView.as_view(), name='type-list-create'),
path('types/<int:pk>/', views.ProductTypeRUDBackOfficeView.as_view(),
name='type-retrieve-update-destroy'),
path('types/attach-tag-category/', views.ProductTypeTagCategoryCreateBackOfficeView.as_view(),
name='type-tag-category-create'),
# product sub types
path('subtypes/', views.ProductSubTypeListCreateBackOfficeView.as_view(),
name='subtype-list-create'),
path('subtypes/<int:pk>/', views.ProductSubTypeRUDBackOfficeView.as_view(),
name='subtype-retrieve-update-destroy'),
] ]
urlpatterns.extend(common_urlpatterns)

View File

@ -16,5 +16,4 @@ 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'),
] ]

View File

@ -1,4 +1,4 @@
from .back import *
from .common import * from .common import *
from .back import *
from .mobile import * from .mobile import *
from .web import * from .web import *

View File

@ -1,25 +1,52 @@
"""Product app back-office views.""" """Product app back-office views."""
from django.conf import settings
from django.db.transaction import on_commit
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from rest_framework import generics, status, permissions from rest_framework import generics, status, permissions, views
from rest_framework.response import Response from rest_framework.response import Response
from gallery.tasks import delete_image
from product import serializers, models from product import serializers, models
from product.views import ProductBaseView
from utils.views import CreateDestroyGalleryViewMixin
class ProductBackOfficeMixinView: class ProductBackOfficeMixinView(ProductBaseView):
"""Product back-office mixin view.""" """Product back-office mixin view."""
permission_classes = (permissions.IsAuthenticated,) permission_classes = (permissions.IsAuthenticated,)
queryset = models.Product.objects.with_base_related() \
.order_by('-created', ) def get_queryset(self):
"""Override get_queryset method."""
qs = models.Product.objects.annotate_in_favorites(self.request.user)
return qs
class ProductTypeBackOfficeMixinView:
"""Product type back-office mixin view."""
permission_classes = (permissions.IsAuthenticated,)
queryset = models.ProductType.objects.all()
class ProductSubTypeBackOfficeMixinView:
"""Product sub type back-office mixin view."""
permission_classes = (permissions.IsAuthenticated,)
queryset = models.ProductSubType.objects.all()
class BackOfficeListCreateMixin(views.APIView):
"""Back-office list-create mixin view."""
def check_permissions(self, request):
"""
Check if the request should be permitted.
Raises an appropriate exception if the request is not permitted.
"""
if self.request.method != 'GET':
super().check_permissions(request)
class ProductBackOfficeGalleryCreateDestroyView(ProductBackOfficeMixinView, class ProductBackOfficeGalleryCreateDestroyView(ProductBackOfficeMixinView,
generics.CreateAPIView, CreateDestroyGalleryViewMixin):
generics.DestroyAPIView):
"""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.ProductBackOfficeGallerySerializer serializer_class = serializers.ProductBackOfficeGallerySerializer
@ -37,24 +64,6 @@ class ProductBackOfficeGalleryCreateDestroyView(ProductBackOfficeMixinView,
return gallery return gallery
def create(self, request, *args, **kwargs):
"""Overridden create method"""
super().create(request, *args, **kwargs)
return Response(status=status.HTTP_201_CREATED)
def destroy(self, request, *args, **kwargs):
"""Override destroy method."""
gallery_obj = self.get_object()
if settings.USE_CELERY:
on_commit(lambda: delete_image.delay(image_id=gallery_obj.image.id,
completely=False))
else:
on_commit(lambda: delete_image(image_id=gallery_obj.image.id,
completely=False))
# Delete an instances of ProductGallery model
gallery_obj.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
class ProductBackOfficeGalleryListView(ProductBackOfficeMixinView, generics.ListAPIView): class ProductBackOfficeGalleryListView(ProductBackOfficeMixinView, generics.ListAPIView):
"""Resource for returning gallery for product for back-office users.""" """Resource for returning gallery for product for back-office users."""
@ -78,3 +87,47 @@ class ProductBackOfficeGalleryListView(ProductBackOfficeMixinView, generics.List
class ProductDetailBackOfficeView(ProductBackOfficeMixinView, generics.RetrieveUpdateDestroyAPIView): class ProductDetailBackOfficeView(ProductBackOfficeMixinView, generics.RetrieveUpdateDestroyAPIView):
"""Product back-office R/U/D view.""" """Product back-office R/U/D view."""
serializer_class = serializers.ProductBackOfficeDetailSerializer serializer_class = serializers.ProductBackOfficeDetailSerializer
class ProductListCreateBackOfficeView(BackOfficeListCreateMixin, ProductBackOfficeMixinView,
generics.ListCreateAPIView):
"""Product back-office list-create view."""
serializer_class = serializers.ProductBackOfficeDetailSerializer
class ProductTypeListCreateBackOfficeView(BackOfficeListCreateMixin,
ProductTypeBackOfficeMixinView,
generics.ListCreateAPIView):
"""Product type back-office list-create view."""
serializer_class = serializers.ProductTypeBackOfficeDetailSerializer
class ProductTypeRUDBackOfficeView(BackOfficeListCreateMixin,
ProductTypeBackOfficeMixinView,
generics.RetrieveUpdateDestroyAPIView):
"""Product type back-office retrieve-update-destroy view."""
serializer_class = serializers.ProductTypeBackOfficeDetailSerializer
class ProductTypeTagCategoryCreateBackOfficeView(ProductTypeBackOfficeMixinView,
generics.CreateAPIView):
"""View for attaching tag category to product type."""
serializer_class = serializers.ProductTypeTagCategorySerializer
def create(self, request, *args, **kwargs):
super().create(request, *args, **kwargs)
return Response(status=status.HTTP_201_CREATED)
class ProductSubTypeListCreateBackOfficeView(BackOfficeListCreateMixin,
ProductSubTypeBackOfficeMixinView,
generics.ListCreateAPIView):
"""Product sub type back-office list-create view."""
serializer_class = serializers.ProductSubTypeBackOfficeDetailSerializer
class ProductSubTypeRUDBackOfficeView(BackOfficeListCreateMixin,
ProductSubTypeBackOfficeMixinView,
generics.RetrieveUpdateDestroyAPIView):
"""Product sub type back-office retrieve-update-destroy view."""
serializer_class = serializers.ProductSubTypeBackOfficeDetailSerializer

View File

@ -10,11 +10,15 @@ from comment.serializers import CommentRUDSerializer
class ProductBaseView(generics.GenericAPIView): class ProductBaseView(generics.GenericAPIView):
"""Product base view""" """Product base view"""
permission_classes = (permissions.AllowAny, ) permission_classes = (permissions.AllowAny,)
def get_queryset(self): def get_queryset(self):
"""Override get_queryset method.""" """Override get_queryset method."""
return Product.objects.with_base_related() return Product.objects.published() \
.with_base_related() \
.annotate_in_favorites(self.request.user) \
.by_country_code(self.request.country_code) \
.order_by('-created')
class ProductListView(ProductBaseView, generics.ListAPIView): class ProductListView(ProductBaseView, generics.ListAPIView):
@ -64,9 +68,9 @@ class ProductCommentListView(generics.ListAPIView):
"""Override get_queryset method""" """Override get_queryset method"""
product = get_object_or_404(Product, slug=self.kwargs['slug']) product = get_object_or_404(Product, slug=self.kwargs['slug'])
return Comment.objects.by_content_type(app_label='product', return Comment.objects.by_content_type(app_label='product',
model='product')\ model='product') \
.by_object_id(object_id=product.pk)\ .by_object_id(object_id=product.pk) \
.order_by('-created') .order_by('-created')
class ProductCommentRUDView(generics.RetrieveUpdateDestroyAPIView): class ProductCommentRUDView(generics.RetrieveUpdateDestroyAPIView):

View File

@ -0,0 +1,27 @@
# Generated by Django 2.2.4 on 2019-11-14 17:15
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('rating', '0002_auto_20191004_0928'),
]
operations = [
migrations.CreateModel(
name='ViewCount',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('object_id', models.PositiveIntegerField()),
('count', models.IntegerField()),
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
],
options={
'unique_together': {('object_id', 'content_type')},
},
),
]

View File

@ -0,0 +1,25 @@
# Generated by Django 2.2.4 on 2019-11-14 20:41
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('rating', '0003_viewcount'),
]
operations = [
migrations.AlterUniqueTogether(
name='viewcount',
unique_together=set(),
),
migrations.RemoveField(
model_name='viewcount',
name='content_type',
),
migrations.RemoveField(
model_name='viewcount',
name='object_id',
),
]

View File

@ -20,3 +20,7 @@ class Rating(models.Model):
return self.content_object.name return self.content_object.name
if hasattr(self.content_object, 'title'): if hasattr(self.content_object, 'title'):
return self.content_object.title_translated return self.content_object.title_translated
class ViewCount(models.Model):
count = models.IntegerField()

View File

@ -0,0 +1,29 @@
from transfer.models import PageTexts, PageCounters
from news.models import News
from rating.models import ViewCount
def transfer_news_view_count():
news_list = News.objects.filter(old_id__isnull=False)
for news_object in news_list:
try:
mysql_page_text = PageTexts.objects.get(id=news_object.old_id)
except PageTexts.DoesNotExist:
continue
try:
mysql_views_count = PageCounters.objects.get(page_id=mysql_page_text.page_id)
except PageCounters.DoesNotExist:
continue
view_count = ViewCount.objects.create(
count=mysql_views_count.count
)
news_object.views_count = view_count
news_object.save()
data_types = {
"rating_count": [transfer_news_view_count]
}

View File

@ -8,4 +8,4 @@ from utils.admin import BaseModelAdminMixin
class ReviewAdminModel(BaseModelAdminMixin, admin.ModelAdmin): class ReviewAdminModel(BaseModelAdminMixin, admin.ModelAdmin):
"""Admin model for model Review.""" """Admin model for model Review."""
raw_id_fields = ('reviewer', 'language', 'child', 'country') raw_id_fields = ('reviewer', 'child', 'country')

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.7 on 2019-11-13 09:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('review', '0014_auto_20191112_0538'),
]
operations = [
migrations.AddField(
model_name='review',
name='mark',
field=models.FloatField(blank=True, default=None, null=True, verbose_name='mark'),
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 2.2.7 on 2019-11-14 07:21
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('review', '0015_review_mark'),
]
operations = [
migrations.RemoveField(
model_name='review',
name='language',
),
]

View File

@ -0,0 +1,20 @@
# Generated by Django 2.2.4 on 2019-11-15 07:37
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('review', '0016_remove_review_language'),
]
operations = [
migrations.AlterField(
model_name='review',
name='reviewer',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='reviews', to=settings.AUTH_USER_MODEL, verbose_name='Reviewer'),
),
]

View File

@ -39,36 +39,49 @@ class Review(BaseAttributes, TranslatedFieldsMixin):
(READY, _('Ready')), (READY, _('Ready')),
) )
reviewer = models.ForeignKey('account.User', reviewer = models.ForeignKey(
related_name='reviews', 'account.User',
on_delete=models.CASCADE, related_name='reviews',
verbose_name=_('Reviewer')) on_delete=models.CASCADE,
verbose_name=_('Reviewer'),
null=True, default=None, blank=True
)
country = models.ForeignKey(
'location.Country',
on_delete=models.CASCADE,
related_name='country',
verbose_name=_('Country'),
null=True,
)
child = models.ForeignKey(
'self',
blank=True,
default=None,
null=True,
on_delete=models.CASCADE,
verbose_name=_('Child review'),
)
text = TJSONField( text = TJSONField(
_('text'), null=True, blank=True, _('text'),
default=None, help_text='{"en-GB":"Text review"}') null=True,
blank=True,
default=None,
help_text='{"en-GB":"Text review"}',
)
content_type = models.ForeignKey(generic.ContentType, on_delete=models.CASCADE) content_type = models.ForeignKey(generic.ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField() object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id') content_object = generic.GenericForeignKey('content_type', 'object_id')
language = models.ForeignKey('translation.Language',
on_delete=models.CASCADE,
related_name='reviews',
verbose_name=_('Review language'))
status = models.PositiveSmallIntegerField(choices=REVIEW_STATUSES, default=TO_INVESTIGATE) status = models.PositiveSmallIntegerField(choices=REVIEW_STATUSES, default=TO_INVESTIGATE)
child = models.ForeignKey('self', published_at = models.DateTimeField(
blank=True, default=None, null=True, _('Publish datetime'),
on_delete=models.CASCADE, blank=True,
verbose_name=_('Child review')) default=None,
published_at = models.DateTimeField(verbose_name=_('Publish datetime'), null=True,
blank=True, default=None, null=True, help_text=_('Review published datetime'),
help_text=_('Review published datetime')) )
vintage = models.IntegerField(verbose_name=_('Year of review'), vintage = models.IntegerField(_('Year of review'), validators=[MinValueValidator(1900), MaxValueValidator(2100)])
validators=[MinValueValidator(1900), mark = models.FloatField(verbose_name=_('mark'), blank=True, null=True, default=None)
MaxValueValidator(2100)])
country = models.ForeignKey('location.Country', on_delete=models.CASCADE,
related_name='country', verbose_name=_('Country'),
null=True)
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None) old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
objects = ReviewQuerySet.as_manager() objects = ReviewQuerySet.as_manager()

View File

@ -1,5 +1,6 @@
from rest_framework import serializers from rest_framework import serializers
from review.models import Review
from review.models import Review, Inquiries, GridItems
class ReviewBaseSerializer(serializers.ModelSerializer): class ReviewBaseSerializer(serializers.ModelSerializer):
@ -9,7 +10,6 @@ class ReviewBaseSerializer(serializers.ModelSerializer):
'id', 'id',
'reviewer', 'reviewer',
'text', 'text',
'language',
'status', 'status',
'child', 'child',
'published_at', 'published_at',
@ -27,3 +27,41 @@ class ReviewShortSerializer(ReviewBaseSerializer):
fields = ( fields = (
'text_translated', 'text_translated',
) )
class InquiriesBaseSerializer(serializers.ModelSerializer):
"""Serializer for model Inquiries."""
class Meta:
model = Inquiries
fields = (
'id',
'review',
'comment',
'final_comment',
'mark',
'attachment_file',
'author',
'bill_file',
'price',
'moment',
'gallery',
'decibels',
'nomination',
'nominee',
'published',
)
class GridItemsBaseSerializer(serializers.ModelSerializer):
"""Serializer for model GridItems."""
class Meta:
model = GridItems
fields = (
'id',
'inquiry',
'sub_name',
'name',
'value',
'desc',
'dish_title',
)

View File

@ -1,3 +1,133 @@
from django.test import TestCase from http.cookies import SimpleCookie
# Create your tests here. from rest_framework import status
from rest_framework.test import APITestCase
from account.models import User
from location.models import Country
from review.models import Review, Inquiries, GridItems
from translation.models import Language
class BaseTestCase(APITestCase):
def setUp(self):
self.username = 'test_user'
self.password = 'test_user_password'
self.email = 'test_user@mail.com'
self.user = User.objects.create_user(
username=self.username,
email=self.email,
password=self.password,
)
tokens = User.create_jwt_tokens(self.user)
self.client.cookies = SimpleCookie({
'access_token': tokens.get('access_token'),
'refresh_token': tokens.get('refresh_token'),
})
self.lang = Language.objects.create(
title='Russia',
locale='ru-RU'
)
self.country_ru = Country.objects.create(
name={'en-GB': 'Russian'},
code='RU',
)
self.test_review = Review.objects.create(
reviewer=self.user,
status=Review.READY,
vintage=2020,
country=self.country_ru,
text={'en-GB': 'Text review'},
created_by=self.user,
modified_by=self.user,
object_id=1,
content_type_id=1,
)
self.test_inquiry = Inquiries.objects.create(
review=self.test_review,
author=self.user,
comment='Test comment',
)
self.test_grid = GridItems.objects.create(
inquiry=self.test_inquiry,
name='Test name',
)
class InquiriesTestCase(BaseTestCase):
def setUp(self):
super().setUp()
def test_inquiry_list(self):
response = self.client.get('/api/back/review/inquiries/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_inquiry_list_by_review_id(self):
response = self.client.get(f'/api/back/review/{self.test_review.id}/inquiries/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_inquiry_post(self):
test_inquiry = {
'review': self.test_review.pk,
'author': self.user.pk,
'comment': 'New test comment',
}
response = self.client.post('/api/back/review/inquiries/', data=test_inquiry)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
def test_inquiry_detail(self):
response = self.client.get(f'/api/back/review/inquiries/{self.test_inquiry.id}/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_inquiry_detail_put(self):
data = {
'id': self.test_inquiry.id,
'review': self.test_review.pk,
'author': self.user.pk,
'comment': 'New test comment 2',
}
response = self.client.put(f'/api/back/review/inquiries/{self.test_inquiry.id}/', data=data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
class GridItemsTestCase(BaseTestCase):
def setUp(self):
super().setUp()
def test_grid_list(self):
response = self.client.get('/api/back/review/inquiries/grid/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_grid_list_by_inquiry_id(self):
response = self.client.get(f'/api/back/review/inquiries/{self.test_inquiry.id}/grid/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_grid_post(self):
test_grid = {
'inquiry': self.test_inquiry.pk,
'name': 'New test name',
}
response = self.client.post('/api/back/review/inquiries/grid/', data=test_grid)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
def test_grid_detail(self):
response = self.client.get(f'/api/back/review/inquiries/grid/{self.test_grid.id}/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_grid_detail_put(self):
data = {
'id': self.test_grid.id,
'inquiry': self.test_inquiry.pk,
'name': 'New test name 2',
}
response = self.client.put(f'/api/back/review/inquiries/grid/{self.test_inquiry.id}/', data=data)
self.assertEqual(response.status_code, status.HTTP_200_OK)

View File

@ -1,15 +1,20 @@
import json
from pprint import pprint from pprint import pprint
from django.db.models import Q from django.db.models import Q
from product.models import Product
from account.models import User
from account.transfer_data import STOP_LIST from account.transfer_data import STOP_LIST
from establishment.models import Establishment
from review.models import Inquiries as NewInquiries, Review from review.models import Inquiries as NewInquiries, Review
from transfer.models import Reviews, ReviewTexts, Inquiries, GridItems, InquiryPhotos from transfer.models import Reviews, ReviewTexts, Inquiries, GridItems, InquiryPhotos
from transfer.serializers.grid import GridItemsSerializer from transfer.serializers.grid import GridItemsSerializer
from transfer.serializers.inquiries import InquiriesSerializer from transfer.serializers.inquiries import InquiriesSerializer
from transfer.serializers.inquiry_gallery import InquiryGallerySerializer from transfer.serializers.inquiry_gallery import InquiryGallerySerializer
from transfer.serializers.reviews import LanguageSerializer, ReviewSerializer, Establishment from transfer.serializers.reviews import (
LanguageSerializer, ReviewSerializer, ReviewTextSerializer, ProductReviewSerializer
)
def transfer_languages(): def transfer_languages():
@ -28,77 +33,40 @@ def transfer_languages():
def transfer_reviews(): def transfer_reviews():
# TODO: убрать LIKE UPPER("%%paris%%"), accounts.email IN establishments = Establishment.objects.filter(old_id__isnull=False).values_list('old_id', flat=True)
queryset = Reviews.objects.raw("""SELECT reviews.id, reviews.vintage, reviews.establishment_id, queryset = Reviews.objects.filter(
reviews.reviewer_id, review_texts.text AS text, reviews.mark, reviews.published_at, establishment_id__in=list(establishments),
review_texts.created_at AS published, review_texts.locale AS locale, ).values('id', 'reviewer_id', 'aasm_state', 'created_at', 'establishment_id', 'mark', 'vintage')
reviews.aasm_state
FROM reviews
LEFT OUTER JOIN review_texts
ON (reviews.id = review_texts.review_id)
WHERE reviews.reviewer_id > 0
AND reviews.reviewer_id IS NOT NULL
AND review_texts.text IS NOT NULL
AND review_texts.locale IS NOT NULL
AND reviews.mark IS NOT NULL
AND reviews.reviewer_id IN (
SELECT accounts.id
FROM accounts
WHERE accounts.confirmed_at IS NOT NULL
AND NOT accounts.email IN (
"cyril@tomatic.net",
"cyril2@tomatic.net",
"cyril2@tomatic.net",
"d.sadykova@id-east.ru",
"d.sadykova@octopod.ru",
"n.yurchenko@id-east.ru"
))
AND reviews.establishment_id IN (
SELECT establishments.id
FROM establishments
INNER JOIN locations
ON (establishments.location_id = locations.id)
INNER JOIN cities
ON (locations.city_id = cities.id)
WHERE establishments.type IS NOT NULL AND locations.timezone IS NOT NULL
AND NOT establishments.type = "Wineyard"
)
ORDER BY review_texts.created_at DESC
""")
queryset_result = []
establishments_mark_list = {}
for query in queryset:
query = vars(query)
if query['establishment_id'] not in establishments_mark_list.keys():
if "aasm_state" in query and query['aasm_state'] is not None and query['aasm_state'] == "published":
establishments_mark_list[query['establishment_id']] = [
int(query['mark']),
json.dumps({query['locale']: query['text']})
]
else:
establishments_mark_list[query['establishment_id']] = int(query['mark'])
del (query['mark'])
queryset_result.append(query)
serialized_data = ReviewSerializer(data=queryset_result, many=True)
serialized_data = ReviewSerializer(data=list(queryset.values()), many=True)
if serialized_data.is_valid(): if serialized_data.is_valid():
serialized_data.save() serialized_data.save()
for establishment_id, mark in establishments_mark_list.items():
try:
establishment = Establishment.objects.get(old_id=establishment_id)
except Establishment.DoesNotExist:
continue
if isinstance(mark, list):
mark, review_text = mark
establishment.public_mark = mark
establishment.save()
else: else:
pprint(serialized_data.errors) pprint(f"ReviewSerializer serializer errors: {serialized_data.errors}")
def transfer_text_review():
reviews = Review.objects.filter(old_id__isnull=False).values_list('old_id', flat=True)
queryset = ReviewTexts.objects.filter(
review_id__in=list(reviews),
).exclude(
Q(text__isnull=True) | Q(text='')
).values('review_id', 'locale', 'text')
serialized_data = ReviewTextSerializer(data=list(queryset.values()), many=True)
if serialized_data.is_valid():
serialized_data.save()
else:
pprint(f"ReviewTextSerializer serializer errors: {serialized_data.errors}")
for review in Review.objects.filter(old_id__isnull=False):
text = review.text
if text and 'en-GB' not in text:
text.update({
'en-GB': next(iter(text.values()))
})
review.text = text
review.save()
def transfer_inquiries(): def transfer_inquiries():
@ -137,14 +105,38 @@ def transfer_inquiry_photos():
pprint(f"InquiryGallery serializer errors: {serialized_data.errors}") pprint(f"InquiryGallery serializer errors: {serialized_data.errors}")
def transfer_product_reviews():
products = Product.objects.filter(
old_id__isnull=False).values_list('old_id', flat=True)
users = User.objects.filter(
old_id__isnull=False).values_list('old_id', flat=True)
queryset = Reviews.objects.filter(
product_id__in=list(products),
reviewer_id__in=list(users),
).values('id', 'reviewer_id', 'aasm_state', 'created_at', 'product_id', 'mark', 'vintage')
serialized_data = ProductReviewSerializer(data=list(queryset.values()), many=True)
if serialized_data.is_valid():
serialized_data.save()
else:
pprint(f"ProductReviewSerializer serializer errors: {serialized_data.errors}")
data_types = { data_types = {
"overlook": [ "overlook": [
transfer_languages, # transfer_languages,
transfer_reviews transfer_reviews,
transfer_text_review,
], ],
'inquiries': [ 'inquiries': [
transfer_inquiries, transfer_inquiries,
transfer_grid, transfer_grid,
transfer_inquiry_photos, transfer_inquiry_photos,
],
"product_review": [
transfer_product_reviews,
] ]
} }

View File

@ -8,4 +8,10 @@ app_name = 'review'
urlpatterns = [ urlpatterns = [
path('', views.ReviewLstView.as_view(), name='review-list-create'), path('', views.ReviewLstView.as_view(), name='review-list-create'),
path('<int:id>/', views.ReviewRUDView.as_view(), name='review-crud'), path('<int:id>/', views.ReviewRUDView.as_view(), name='review-crud'),
path('<int:review_id>/inquiries/', views.InquiriesLstView.as_view(), name='inquiries-list'),
path('inquiries/', views.InquiriesLstView.as_view(), name='inquiries-list-create'),
path('inquiries/<int:id>/', views.InquiriesRUDView.as_view(), name='inquiries-crud'),
path('inquiries/<int:inquiry_id>/grid/', views.GridItemsLstView.as_view(), name='grid-list-create'),
path('inquiries/grid/', views.GridItemsLstView.as_view(), name='grid-list-create'),
path('inquiries/grid/<int:id>/', views.GridItemsRUDView.as_view(), name='grid-crud'),
] ]

View File

@ -1,6 +1,7 @@
from rest_framework import generics, permissions from rest_framework import generics, permissions
from review import serializers
from review import models from review import models
from review import serializers
from utils.permissions import IsReviewerManager, IsRestaurantReviewer from utils.permissions import IsReviewerManager, IsRestaurantReviewer
@ -8,12 +9,55 @@ class ReviewLstView(generics.ListCreateAPIView):
"""Comment list create view.""" """Comment list create view."""
serializer_class = serializers.ReviewBaseSerializer serializer_class = serializers.ReviewBaseSerializer
queryset = models.Review.objects.all() queryset = models.Review.objects.all()
permission_classes = [permissions.IsAuthenticatedOrReadOnly,] permission_classes = [permissions.IsAuthenticatedOrReadOnly, ]
class ReviewRUDView(generics.RetrieveUpdateDestroyAPIView): class ReviewRUDView(generics.RetrieveUpdateDestroyAPIView):
"""Comment RUD view.""" """Comment RUD view."""
serializer_class = serializers.ReviewBaseSerializer serializer_class = serializers.ReviewBaseSerializer
queryset = models.Review.objects.all() queryset = models.Review.objects.all()
permission_classes = [IsReviewerManager|IsRestaurantReviewer] permission_classes = [IsReviewerManager | IsRestaurantReviewer]
lookup_field = 'id'
class InquiriesLstView(generics.ListCreateAPIView):
"""Inquiries list create view."""
serializer_class = serializers.InquiriesBaseSerializer
queryset = models.Inquiries.objects.all()
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
def get_queryset(self):
review_id = self.kwargs.get('review_id')
if review_id:
return super().get_queryset().filter(review_id=review_id)
return super().get_queryset()
class InquiriesRUDView(generics.RetrieveUpdateDestroyAPIView):
"""Inquiries RUD view."""
serializer_class = serializers.InquiriesBaseSerializer
queryset = models.Inquiries.objects.all()
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
lookup_field = 'id'
class GridItemsLstView(generics.ListCreateAPIView):
"""GridItems list create view."""
serializer_class = serializers.GridItemsBaseSerializer
queryset = models.GridItems.objects.all()
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
def get_queryset(self):
inquiry_id = self.kwargs.get('inquiry_id')
if inquiry_id:
return super().get_queryset().filter(inquiry_id=inquiry_id)
return super().get_queryset()
class GridItemsRUDView(generics.RetrieveUpdateDestroyAPIView):
"""GridItems RUD view."""
serializer_class = serializers.GridItemsBaseSerializer
queryset = models.GridItems.objects.all()
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
lookup_field = 'id' lookup_field = 'id'

View File

@ -1,9 +1,11 @@
from search_indexes.documents.establishment import EstablishmentDocument from search_indexes.documents.establishment import EstablishmentDocument
from search_indexes.documents.news import NewsDocument from search_indexes.documents.news import NewsDocument
from search_indexes.documents.product import ProductDocument
# todo: make signal to update documents on related fields # todo: make signal to update documents on related fields
__all__ = [ __all__ = [
'EstablishmentDocument', 'EstablishmentDocument',
'NewsDocument', 'NewsDocument',
'ProductDocument',
] ]

View File

@ -31,6 +31,7 @@ class NewsDocument(Document):
'id': fields.IntegerField(attr='id'), 'id': fields.IntegerField(attr='id'),
'label': fields.ObjectField(attr='label_indexing', 'label': fields.ObjectField(attr='label_indexing',
properties=OBJECT_FIELD_PROPERTIES), properties=OBJECT_FIELD_PROPERTIES),
'value': fields.KeywordField()
}, },
multi=True) multi=True)
@ -49,7 +50,7 @@ class NewsDocument(Document):
related_models = [models.NewsType] related_models = [models.NewsType]
def get_queryset(self): def get_queryset(self):
return super().get_queryset().published().with_base_related() return super().get_queryset().published().with_base_related().sort_by_start()
def get_instances_from_related(self, related_instance): def get_instances_from_related(self, related_instance):
"""If related_models is set, define how to retrieve the Car instance(s) from the related model. """If related_models is set, define how to retrieve the Car instance(s) from the related model.

View File

@ -0,0 +1,116 @@
"""Product app documents."""
from django.conf import settings
from django_elasticsearch_dsl import Document, Index, fields
from search_indexes.utils import OBJECT_FIELD_PROPERTIES
from product import models
ProductIndex = Index(settings.ELASTICSEARCH_INDEX_NAMES.get(__name__, 'product'))
ProductIndex.settings(number_of_shards=5, number_of_replicas=2, mapping={'total_fields':{'limit': 3000}})
@ProductIndex.doc_type
class ProductDocument(Document):
"""Product document."""
description = fields.ObjectField(attr='description_indexing',
properties=OBJECT_FIELD_PROPERTIES)
product_type = fields.ObjectField(properties={
'id': fields.IntegerField(),
'name': fields.ObjectField(attr='name_indexing', properties=OBJECT_FIELD_PROPERTIES),
'index_name': fields.KeywordField(),
'use_subtypes': fields.BooleanField(),
})
subtypes = fields.ObjectField(
properties={
'id': fields.IntegerField(),
'name': fields.ObjectField(attr='name_indexing', properties=OBJECT_FIELD_PROPERTIES),
'index_name': fields.KeywordField(),
},
multi=True
)
establishment = fields.ObjectField(
properties={
'id': fields.IntegerField(),
'name': fields.KeywordField(),
'slug': fields.KeywordField(),
# 'city' TODO: city indexing
}
)
wine_colors = fields.ObjectField(
properties={
'id': fields.IntegerField(),
'label': fields.ObjectField(attr='label_indexing', properties=OBJECT_FIELD_PROPERTIES),
'value': fields.KeywordField(),
},
multi=True,
)
wine_region = fields.ObjectField(properties={
'id': fields.IntegerField(),
'name': fields.KeywordField(),
'country': fields.ObjectField(properties={
'id': fields.IntegerField(),
'name': fields.ObjectField(attr='name_indexing',
properties=OBJECT_FIELD_PROPERTIES),
'code': fields.KeywordField(),
}),
# 'coordinates': fields.GeoPointField(),
'description': fields.ObjectField(attr='description_indexing', properties=OBJECT_FIELD_PROPERTIES),
})
wine_sub_region = fields.ObjectField(properties={'name': fields.KeywordField()})
classifications = fields.ObjectField( # TODO
properties={
'classification_type': fields.ObjectField(properties={}),
'standard': fields.ObjectField(properties={
'name': fields.KeywordField(),
'standard_type': fields.IntegerField(),
# 'coordinates': fields.GeoPointField(),
}),
'tags': fields.ObjectField(
properties={
'label': fields.ObjectField(attr='label_indexing', properties=OBJECT_FIELD_PROPERTIES),
'value': fields.KeywordField(),
},
multi=True
),
},
multi=True
)
standards = fields.ObjectField(
properties={
'id': fields.IntegerField(),
'name': fields.KeywordField(),
'standard_type': fields.IntegerField(),
# 'coordinates': fields.GeoPointField(),
},
multi=True
)
wine_village = fields.ObjectField(properties={
'name': fields.KeywordField(),
})
tags = fields.ObjectField(
properties={
'id': fields.IntegerField(),
'label': fields.ObjectField(attr='label_indexing', properties=OBJECT_FIELD_PROPERTIES),
'value': fields.KeywordField(),
},
multi=True
)
class Django:
model = models.Product
fields = (
'id',
'category',
'name',
'available',
'public_mark',
'slug',
'old_id',
'state',
'old_unique_key',
'vintage',
)
related_models = [models.ProductType]
def get_queryset(self):
return super().get_queryset().published().with_base_related()

View File

@ -4,6 +4,7 @@ from elasticsearch_dsl import AttrDict
from django_elasticsearch_dsl_drf.serializers import DocumentSerializer from django_elasticsearch_dsl_drf.serializers import DocumentSerializer
from news.serializers import NewsTypeSerializer 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.utils import get_translated_value from search_indexes.utils import get_translated_value
@ -19,6 +20,73 @@ class TagsDocumentSerializer(serializers.Serializer):
return get_translated_value(obj.label) return get_translated_value(obj.label)
class EstablishmentTypeSerializer(serializers.Serializer):
"""Establishment type serializer for ES Document"""
id = serializers.IntegerField()
name_translated = serializers.SerializerMethodField()
index_name = serializers.CharField()
def get_name_translated(self, obj):
if isinstance(obj, dict):
return get_translated_value(obj.get('name'))
return get_translated_value(obj.name)
class ProductSubtypeDocumentSerializer(serializers.Serializer):
"""Product subtype serializer for ES Document."""
id = serializers.IntegerField()
name_translated = serializers.SerializerMethodField()
get_name_translated = lambda obj: get_translated_value(obj.name)
class WineRegionCountryDocumentSerialzer(serializers.Serializer):
"""Wine region country ES document serializer."""
id = serializers.IntegerField()
code = serializers.CharField()
name_translated = serializers.SerializerMethodField()
@staticmethod
def get_name_translated(obj):
return get_translated_value(obj.name)
def get_attribute(self, instance):
return instance.country if instance and instance.country else None
class WineRegionDocumentSerializer(serializers.Serializer):
"""Wine region ES document serializer."""
id = serializers.IntegerField()
name = serializers.CharField()
country = WineRegionCountryDocumentSerialzer(allow_null=True)
class WineColorDocumentSerializer(serializers.Serializer):
"""Wine color ES document serializer,"""
id = serializers.IntegerField()
label_translated = serializers.SerializerMethodField()
index_name = serializers.CharField(source='value')
@staticmethod
def get_label_translated(obj):
if isinstance(obj, dict):
return get_translated_value(obj.get('label'))
return get_translated_value(obj.label)
class ProductEstablishmentDocumentSerializer(serializers.Serializer):
"""Related to Product Establishment ES document serializer."""
id = serializers.IntegerField()
name = serializers.CharField()
slug = serializers.CharField()
class CityDocumentShortSerializer(serializers.Serializer): class CityDocumentShortSerializer(serializers.Serializer):
"""City serializer for ES Document,""" """City serializer for ES Document,"""
@ -94,6 +162,8 @@ class NewsDocumentSerializer(DocumentSerializer):
class EstablishmentDocumentSerializer(DocumentSerializer): class EstablishmentDocumentSerializer(DocumentSerializer):
"""Establishment document serializer.""" """Establishment document serializer."""
establishment_type = EstablishmentTypeSerializer()
establishment_subtypes = EstablishmentTypeSerializer(many=True)
address = AddressDocumentSerializer(allow_null=True) address = AddressDocumentSerializer(allow_null=True)
tags = TagsDocumentSerializer(many=True) tags = TagsDocumentSerializer(many=True)
schedule = ScheduleDocumentSerializer(many=True, allow_null=True) schedule = ScheduleDocumentSerializer(many=True, allow_null=True)
@ -123,3 +193,41 @@ class EstablishmentDocumentSerializer(DocumentSerializer):
# 'establishment_type', # 'establishment_type',
# 'establishment_subtypes', # 'establishment_subtypes',
) )
class ProductDocumentSerializer(DocumentSerializer):
"""Product document serializer"""
tags = TagsDocumentSerializer(many=True)
subtypes = ProductSubtypeDocumentSerializer(many=True)
wine_region = WineRegionDocumentSerializer(allow_null=True)
wine_colors = WineColorDocumentSerializer(many=True)
product_type = serializers.SerializerMethodField()
establishment_detail = ProductEstablishmentDocumentSerializer(source='establishment', allow_null=True)
@staticmethod
def get_product_type(obj):
return get_translated_value(obj.product_type.name if obj.product_type else {})
class Meta:
"""Meta class."""
document = ProductDocument
fields = (
'id',
'category',
'name',
'available',
'public_mark',
'slug',
'old_id',
'state',
'old_unique_key',
'vintage',
'tags',
'product_type',
'subtypes',
'wine_region',
'wine_colors',
'establishment_detail',
)

View File

@ -13,44 +13,20 @@ def update_document(sender, **kwargs):
model_name = sender._meta.model_name model_name = sender._meta.model_name
instance = kwargs['instance'] instance = kwargs['instance']
if app_label == 'location': app_label_model_name_to_filter = {
if model_name == 'country': ('location','country'): 'address__city__country',
establishments = Establishment.objects.filter( ('location','city'): 'address__city',
address__city__country=instance) ('location', 'address'): 'address',
for establishment in establishments:
registry.update(establishment)
if model_name == 'city':
establishments = Establishment.objects.filter(
address__city=instance)
for establishment in establishments:
registry.update(establishment)
if model_name == 'address':
establishments = Establishment.objects.filter(
address=instance)
for establishment in establishments:
registry.update(establishment)
if app_label == 'establishment':
# todo: remove after migration # todo: remove after migration
from establishment import models as establishment_models ('establishment', 'establishmenttype'): 'establishment_type',
if model_name == 'establishmenttype': ('establishment', 'establishmentsubtype'): 'establishment_subtypes',
if isinstance(instance, establishment_models.EstablishmentType): ('tag', 'tag'): 'tags',
establishments = Establishment.objects.filter( }
establishment_type=instance) filter_name = app_label_model_name_to_filter.get((app_label, model_name))
for establishment in establishments: if filter_name:
registry.update(establishment) qs = Establishment.objects.filter(**{filter_name: instance})
if model_name == 'establishmentsubtype': for product in qs:
if isinstance(instance, establishment_models.EstablishmentSubType): registry.update(product)
establishments = Establishment.objects.filter(
establishment_subtypes=instance)
for establishment in establishments:
registry.update(establishment)
if app_label == 'tag':
if model_name == 'tag':
establishments = Establishment.objects.filter(tags=instance)
for establishment in establishments:
registry.update(establishment)
@receiver(post_save) @receiver(post_save)
@ -59,21 +35,35 @@ def update_news(sender, **kwargs):
app_label = sender._meta.app_label app_label = sender._meta.app_label
model_name = sender._meta.model_name model_name = sender._meta.model_name
instance = kwargs['instance'] instance = kwargs['instance']
app_label_model_name_to_filter = {
('location','country'): 'country',
('news','newstype'): 'news_type',
('tag', 'tag'): 'tags',
}
filter_name = app_label_model_name_to_filter.get((app_label, model_name))
if filter_name:
qs = News.objects.filter(**{filter_name: instance})
for product in qs:
registry.update(product)
if app_label == 'location':
if model_name == 'country':
qs = News.objects.filter(country=instance)
for news in qs:
registry.update(news)
if app_label == 'news': @receiver(post_save)
if model_name == 'newstype': def update_product(sender, **kwargs):
qs = News.objects.filter(news_type=instance) from product.models import Product
for news in qs: app_label = sender._meta.app_label
registry.update(news) model_name = sender._meta.model_name
instance = kwargs['instance']
if app_label == 'tag': app_label_model_name_to_filter = {
if model_name == 'tag': ('product','productstandard'): 'standards',
qs = News.objects.filter(tags=instance) ('product', 'producttype'): 'product_type',
for news in qs: ('tag','tag'): 'tags',
registry.update(news) ('location', 'wineregion'): 'wine_region',
('location', 'winesubregion'): 'wine_sub_region',
('location', 'winevillage'): 'wine_village',
('establishment', 'establishment'): 'establishment',
}
filter_name = app_label_model_name_to_filter.get((app_label, model_name))
if filter_name:
qs = Product.objects.filter(**{filter_name: instance})
for product in qs:
registry.update(product)

View File

@ -8,6 +8,7 @@ router = routers.SimpleRouter()
router.register(r'establishments', views.EstablishmentDocumentViewSet, basename='establishment') router.register(r'establishments', views.EstablishmentDocumentViewSet, basename='establishment')
router.register(r'mobile/establishments', views.EstablishmentDocumentViewSet, basename='establishment-mobile') router.register(r'mobile/establishments', views.EstablishmentDocumentViewSet, basename='establishment-mobile')
router.register(r'news', views.NewsDocumentViewSet, basename='news') router.register(r'news', views.NewsDocumentViewSet, basename='news')
router.register(r'products', views.ProductDocumentViewSet, basename='product')
urlpatterns = router.urls urlpatterns = router.urls

View File

@ -3,224 +3,35 @@ from django_elasticsearch_dsl import fields
from utils.models import get_current_locale, get_default_locale from utils.models import get_current_locale, get_default_locale
ALL_LOCALES_LIST = [ ALL_LOCALES_LIST = [
'af-ZA',
'am-ET',
'ar-AE',
'ar-BH',
'ar-DZ',
'ar-EG',
'ar-IQ',
'ar-JO',
'ar-KW',
'ar-LB',
'ar-LY',
'ar-MA',
'arn-CL',
'ar-OM',
'ar-QA',
'ar-SA',
'ar-SY',
'ar-TN',
'ar-YE',
'as-IN',
'az-Cyrl-AZ',
'az-Latn-AZ',
'ba-RU',
'be-BY',
'bg-BG',
'bn-BD',
'bn-IN',
'bo-CN',
'br-FR',
'bs-Cyrl-BA',
'bs-Latn-BA',
'ca-ES',
'co-FR',
'cs-CZ',
'cy-GB',
'da-DK',
'de-AT',
'de-CH',
'de-DE',
'de-LI',
'de-LU',
'dsb-DE',
'dv-MV',
'el-GR',
'en-029',
'en-AU',
'en-BZ',
'en-CA',
'en-GB',
'en-IE',
'en-IN',
'en-JM',
'en-MY',
'en-NZ',
'en-PH',
'en-SG',
'en-TT',
'en-US',
'en-ZA',
'en-ZW',
'es-AR',
'es-BO',
'es-CL',
'es-CO',
'es-CR',
'es-DO',
'es-EC',
'es-ES',
'es-GT',
'es-HN',
'es-MX',
'es-NI',
'es-PA',
'es-PE',
'es-PR',
'es-PY',
'es-SV',
'es-US',
'es-UY',
'es-VE',
'et-EE',
'eu-ES',
'fa-IR',
'fi-FI',
'fil-PH',
'fo-FO',
'fr-BE',
'fr-CA',
'fr-CH',
'fr-FR',
'fr-LU',
'fr-MC',
'fy-NL',
'ga-IE',
'gd-GB',
'gl-ES',
'gsw-FR',
'gu-IN',
'ha-Latn-NG',
'he-IL',
'hi-IN',
'hr-BA',
'hr-HR', 'hr-HR',
'hsb-DE',
'hu-HU',
'hy-AM',
'id-ID',
'ig-NG',
'ii-CN',
'is-IS',
'it-CH',
'it-IT',
'iu-Cans-CA',
'iu-Latn-CA',
'ja-JP',
'ka-GE',
'kk-KZ',
'kl-GL',
'km-KH',
'kn-IN',
'kok-IN',
'ko-KR',
'ky-KG',
'lb-LU',
'lo-LA',
'lt-LT',
'lv-LV',
'mi-NZ',
'mk-MK',
'ml-IN',
'mn-MN',
'mn-Mong-CN',
'moh-CA',
'mr-IN',
'ms-BN',
'ms-MY',
'mt-MT',
'nb-NO',
'ne-NP',
'nl-BE',
'nl-NL',
'nn-NO',
'nso-ZA',
'oc-FR',
'or-IN',
'pa-IN',
'pl-PL',
'prs-AF',
'ps-AF',
'pt-BR',
'pt-PT',
'qut-GT',
'quz-BO',
'quz-EC',
'quz-PE',
'rm-CH',
'ro-RO', 'ro-RO',
'ru-RU',
'rw-RW',
'sah-RU',
'sa-IN',
'se-FI',
'se-NO',
'se-SE',
'si-LK',
'sk-SK',
'sl-SI', 'sl-SI',
'sma-NO', 'ka-GE',
'sma-SE', 'de-AT',
'smj-NO', 'de-DE',
'smj-SE', 'el-GR',
'smn-FI', 'hu-HU',
'sms-FI', 'nl-BE',
'sq-AL', 'ja-JP',
'sr-Cyrl-BA', 'it-IT',
'sr-Cyrl-CS', 'pl-PL',
'sr-Cyrl-ME', 'he-IL',
'sr-Cyrl-RS', 'pt-BR',
'sr-Latn-BA', 'hu_HU',
'sr-Latn-CS',
'sr-Latn-ME',
'sr-Latn-RS',
'sv-FI',
'sv-SE',
'sw-KE',
'syr-SY',
'ta-IN',
'te-IN',
'tg-Cyrl-TJ',
'th-TH',
'tk-TM',
'tn-ZA',
'tr-TR',
'tt-RU',
'tzm-Latn-DZ',
'ug-CN',
'uk-UA',
'ur-PK',
'uz-Cyrl-UZ',
'uz-Latn-UZ',
'vi-VN',
'wo-SN',
'xh-ZA',
'yo-NG',
'zh-CN',
'zh-HK',
'zh-MO',
'zh-SG',
'zh-TW',
'zu-ZA',
] ]
# object field properties # object field properties
OBJECT_FIELD_PROPERTIES = {locale: fields.TextField() for locale in ALL_LOCALES_LIST} OBJECT_FIELD_PROPERTIES = {locale: fields.TextField() for locale in ALL_LOCALES_LIST}
OBJECT_FIELD_PROPERTIES.update({ OBJECT_FIELD_PROPERTIES.update({
'en-AU': fields.TextField(analyzer='english'),
'en-US': fields.TextField(analyzer='english'),
'en-GB': fields.TextField(analyzer='english'), 'en-GB': fields.TextField(analyzer='english'),
'en-CA': fields.TextField(analyzer='english'),
'ru-RU': fields.TextField(analyzer='russian'), 'ru-RU': fields.TextField(analyzer='russian'),
'fr-FR': fields.TextField(analyzer='french') 'fr-FR': fields.TextField(analyzer='french'),
'fr-BE': fields.TextField(analyzer='french'),
'fr-MA': fields.TextField(analyzer='french'),
'fr-CA': fields.TextField(analyzer='french'),
}) })

View File

@ -9,6 +9,7 @@ from django_elasticsearch_dsl_drf.filter_backends import (
from django_elasticsearch_dsl_drf.viewsets import BaseDocumentViewSet from django_elasticsearch_dsl_drf.viewsets import BaseDocumentViewSet
from search_indexes import serializers, filters from search_indexes import serializers, filters
from search_indexes.documents import EstablishmentDocument, NewsDocument from search_indexes.documents import EstablishmentDocument, NewsDocument
from search_indexes.documents.product import ProductDocument
from utils.pagination import ProjectMobilePagination from utils.pagination import ProjectMobilePagination
@ -20,7 +21,6 @@ class NewsDocumentViewSet(BaseDocumentViewSet):
pagination_class = ProjectMobilePagination pagination_class = ProjectMobilePagination
permission_classes = (permissions.AllowAny,) permission_classes = (permissions.AllowAny,)
serializer_class = serializers.NewsDocumentSerializer serializer_class = serializers.NewsDocumentSerializer
ordering = ('id',)
filter_backends = [ filter_backends = [
filters.CustomSearchFilterBackend, filters.CustomSearchFilterBackend,
@ -28,9 +28,9 @@ class NewsDocumentViewSet(BaseDocumentViewSet):
] ]
search_fields = { search_fields = {
'title': {'fuzziness': 'auto:3,4'}, 'title': {'fuzziness': 'auto:2,5'},
'subtitle': {'fuzziness': 'auto'}, 'subtitle': {'fuzziness': 'auto:2,5'},
'description': {'fuzziness': 'auto'}, 'description': {'fuzziness': 'auto:2,5'},
} }
translated_search_fields = ( translated_search_fields = (
'title', 'title',
@ -43,6 +43,14 @@ class NewsDocumentViewSet(BaseDocumentViewSet):
'field': 'tags.id', 'field': 'tags.id',
'lookups': [ 'lookups': [
constants.LOOKUP_QUERY_IN, constants.LOOKUP_QUERY_IN,
constants.LOOKUP_QUERY_EXCLUDE
]
},
'tag_value': {
'field': 'tags.value',
'lookups': [
constants.LOOKUP_QUERY_IN,
constants.LOOKUP_QUERY_EXCLUDE
] ]
}, },
'slug': 'slug', 'slug': 'slug',
@ -73,22 +81,21 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet):
FilteringFilterBackend, FilteringFilterBackend,
filters.CustomSearchFilterBackend, filters.CustomSearchFilterBackend,
GeoSpatialFilteringFilterBackend, GeoSpatialFilteringFilterBackend,
DefaultOrderingFilterBackend, # DefaultOrderingFilterBackend,
] ]
search_fields = { search_fields = {
'name': {'fuzziness': 'auto:3,4', 'name': {'fuzziness': 'auto:2,5',
'boost': '2'}, 'boost': '2'},
'transliterated_name': {'fuzziness': 'auto:3,4', 'transliterated_name': {'fuzziness': 'auto:2,5',
'boost': '2'}, 'boost': '2'},
'index_name': {'fuzziness': 'auto:3,4', 'index_name': {'fuzziness': 'auto:2,5',
'boost': '2'}, 'boost': '2'},
'description': {'fuzziness': 'auto'}, 'description': {'fuzziness': 'auto:2,5'},
} }
translated_search_fields = ( translated_search_fields = (
'description', 'description',
) )
ordering = 'id'
filter_fields = { filter_fields = {
'slug': 'slug', 'slug': 'slug',
'tag': { 'tag': {
@ -184,3 +191,71 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet):
] ]
} }
} }
class ProductDocumentViewSet(BaseDocumentViewSet):
"""Product document ViewSet."""
document = ProductDocument
lookup_field = 'slug'
pagination_class = ProjectMobilePagination
permission_classes = (permissions.AllowAny,)
serializer_class = serializers.ProductDocumentSerializer
# def get_queryset(self):
# qs = super(ProductDocumentViewSet, self).get_queryset()
# qs = qs.filter('match', is_publish=True)
# return qs
filter_backends = [
FilteringFilterBackend,
filters.CustomSearchFilterBackend,
GeoSpatialFilteringFilterBackend,
DefaultOrderingFilterBackend,
]
search_fields = {
'name': {'fuzziness': 'auto:2,5',
'boost': '2'},
'transliterated_name': {'fuzziness': 'auto:2,5',
'boost': '2'},
'index_name': {'fuzziness': 'auto:2,5',
'boost': '2'},
'description': {'fuzziness': 'auto:2,5'},
}
translated_search_fields = (
'description',
)
filter_fields = {
'slug': 'slug',
'tags_id': {
'field': 'tags.id',
'lookups': [constants.LOOKUP_QUERY_IN]
},
'wine_colors_id': {
'field': 'wine_colors.id',
'lookups': [
constants.LOOKUP_QUERY_IN,
constants.LOOKUP_QUERY_EXCLUDE,
]
},
'wine_from_country_code': {
'field': 'wine_region.country.code',
},
'for_establishment': {
'field': 'establishment.slug',
},
'type': {
'field': 'product_type.index_name',
},
'subtype': {
'field': 'subtypes.index_name',
'lookups': [
constants.LOOKUP_QUERY_IN,
constants.LOOKUP_QUERY_EXCLUDE,
]
}
}
geo_spatial_filter_fields = {
}

View File

@ -30,9 +30,7 @@ class TagsBaseFilterSet(filters.FilterSet):
class TagCategoryFilterSet(TagsBaseFilterSet): class TagCategoryFilterSet(TagsBaseFilterSet):
"""TagCategory filterset.""" """TagCategory filterset."""
establishment_type = filters.ChoiceFilter( establishment_type = filters.CharFilter(method='by_establishment_type')
choices=EstablishmentType.INDEX_NAME_TYPES,
method='by_establishment_type')
class Meta: class Meta:
"""Meta class.""" """Meta class."""
@ -54,3 +52,17 @@ class TagsFilterSet(TagsBaseFilterSet):
model = models.Tag model = models.Tag
fields = ('type',) fields = ('type',)
# TMP TODO remove it later
# Временный хардкод для демонстрации 4 ноября, потом удалить!
def filter_by_type(self, queryset, name, value):
""" Overrides base filter. Temporary decision"""
if not (settings.NEWS_CHOSEN_TAGS and settings.ESTABLISHMENT_CHOSEN_TAGS):
return super().filter_by_type(queryset, name, value)
queryset = models.Tag.objects
if self.NEWS in value:
queryset = queryset.for_news().filter(value__in=settings.NEWS_CHOSEN_TAGS).distinct('value')
if self.ESTABLISHMENT in value:
queryset = queryset.for_establishments().filter(value__in=settings.ESTABLISHMENT_CHOSEN_TAGS).distinct(
'value')
return queryset

View File

@ -0,0 +1,22 @@
from django.core.management.base import BaseCommand
from transfer.models import Cepages
from transfer.serializers.tag import CepageTagSerializer
class Command(BaseCommand):
help = 'Add cepage tags'
def handle(self, *args, **kwarg):
errors = []
queryset = Cepages.objects.all()
serialized_data = CepageTagSerializer(
data=list(queryset.values()),
many=True)
if serialized_data.is_valid():
serialized_data.save()
else:
for d in serialized_data.errors: errors.append(d) if d else None
self.stdout.write(self.style.WARNING(f'Error count: {len(errors)}\nErrors: {errors}'))

View File

@ -0,0 +1,22 @@
from django.core.management.base import BaseCommand
from transfer.models import CepageRegions
from transfer.serializers.location import CepageWineRegionSerializer
class Command(BaseCommand):
help = 'Add cepage tag to wine region'
def handle(self, *args, **kwarg):
errors = []
legacy_products = CepageRegions.objects.exclude(cepage_id__isnull=True) \
.exclude(wine_region_id__isnull=True)
serialized_data = CepageWineRegionSerializer(
data=list(legacy_products.values()),
many=True)
if serialized_data.is_valid():
serialized_data.save()
else:
for d in serialized_data.errors: errors.append(d) if d else None
self.stdout.write(self.style.WARNING(f'Error count: {len(errors)}\nErrors: {errors}'))

View File

@ -0,0 +1,17 @@
from django.core.management.base import BaseCommand
from django.utils.text import slugify
from tag.models import TagCategory
from transfer import models as transfer_models
class Command(BaseCommand):
help = 'Fix wine color tag'
def handle(self, *args, **kwarg):
queryset = transfer_models.Cepages.objects.all()
cepage_list = [slugify(i) for i in queryset.values_list('name', flat=True)]
tag_categories = TagCategory.objects.filter(index_name__in=cepage_list)
deleted_tag_categories = tag_categories.count()
tag_categories.delete()
self.stdout.write(self.style.WARNING(f"Deleted tag categories: {deleted_tag_categories}"))

View File

@ -0,0 +1,18 @@
from django.core.management.base import BaseCommand
from location.models import WineRegion
from transfer.models import CepageRegions
class Command(BaseCommand):
help = 'Cleared wine region tag categories (cleared M2M relation with tag categories)'
def handle(self, *args, **kwarg):
cepage_wine_regions = CepageRegions.objects.exclude(cepage_id__isnull=True) \
.exclude(wine_region_id__isnull=True)\
.distinct() \
.values_list('wine_region_id', flat=True)
wine_regions = WineRegion.objects.filter(id__in=tuple(cepage_wine_regions))
for region in wine_regions:
region.tags.clear()
self.stdout.write(self.style.WARNING(f'Cleared wine region tag categories'))

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.7 on 2019-11-13 09:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tag', '0012_merge_20191112_1552'),
]
operations = [
migrations.AlterField(
model_name='tagcategory',
name='value_type',
field=models.CharField(choices=[('string', 'string'), ('list', 'list'), ('integer', 'integer'), ('float', 'float'), ('percentage', 'percentage')], default='list', max_length=255, verbose_name='value type'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.7 on 2019-11-13 11:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tag', '0013_auto_20191113_0930'),
]
operations = [
migrations.AddField(
model_name='tag',
name='old_id_meta_product',
field=models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='old id metadata product'),
),
]

View File

@ -39,6 +39,9 @@ class Tag(TranslatedFieldsMixin, models.Model):
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None) old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
old_id_meta_product = models.PositiveIntegerField(_('old id metadata product'),
blank=True, null=True, default=None)
objects = TagQuerySet.as_manager() objects = TagQuerySet.as_manager()
class Meta: class Meta:

View File

@ -13,7 +13,6 @@ class TagBaseSerializer(serializers.ModelSerializer):
"""Serializer for model Tag.""" """Serializer for model Tag."""
label_translated = TranslatedField() label_translated = TranslatedField()
# label_translated = serializers.CharField(source='value', read_only=True, allow_null=True)
index_name = serializers.CharField(source='value', read_only=True, allow_null=True) index_name = serializers.CharField(source='value', read_only=True, allow_null=True)
class Meta: class Meta:
@ -57,6 +56,21 @@ class TagCategoryBaseSerializer(serializers.ModelSerializer):
) )
class TagCategoryShortSerializer(serializers.ModelSerializer):
"""Serializer for model TagCategory."""
label_translated = TranslatedField()
value_type_display = serializers.CharField(source='get_value_type_display',
read_only=True)
class Meta(TagCategoryBaseSerializer.Meta):
"""Meta class."""
fields = [
'label_translated',
'value_type_display',
]
class TagCategoryBackOfficeDetailSerializer(TagCategoryBaseSerializer): class TagCategoryBackOfficeDetailSerializer(TagCategoryBaseSerializer):
"""Tag Category detail serializer for back-office users.""" """Tag Category detail serializer for back-office users."""

Some files were not shown because too many files have changed in this diff Show More