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)
old_id = models.IntegerField(null=True, blank=True, default=None)
EMAIL_FIELD = 'email'
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
@ -261,15 +260,31 @@ class User(AbstractUser):
def favorite_establishment_ids(self):
"""Return establishment IDs that in favorites for current user."""
return self.favorites.by_content_type(app_label='establishment',
model='establishment')\
.values_list('object_id', flat=True)
model='establishment') \
.values_list('object_id', flat=True)
@property
def favorite_recipe_ids(self):
"""Return recipe IDs that in favorites for current user."""
return self.favorites.by_content_type(app_label='recipe',
model='recipe')\
.values_list('object_id', flat=True)
model='recipe') \
.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):
@ -277,4 +292,4 @@ class UserRole(ProjectBaseMixin):
user = models.ForeignKey(User, verbose_name=_('User'), on_delete=models.CASCADE)
role = models.ForeignKey(Role, verbose_name=_('Role'), on_delete=models.SET_NULL, null=True)
establishment = models.ForeignKey(Establishment, verbose_name=_('Establishment'),
on_delete=models.SET_NULL, null=True, blank=True)
on_delete=models.SET_NULL, null=True, blank=True)

View File

@ -2,8 +2,15 @@
from django.contrib import admin
from advertisement import models
from main.models import Page
class PageInline(admin.TabularInline):
model = Page
extra = 0
@admin.register(models.Advertisement)
class AdvertisementModelAdmin(admin.ModelAdmin):
"""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 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."""
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
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)
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:
verbose_name = _('Advertisement')
@ -25,3 +54,13 @@ class Advertisement(URLImageMixin, ProjectBaseMixin, PlatformMixin):
def __str__(self):
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"""
from rest_framework import serializers
from advertisement import models
from translation.serializers import LanguageSerializer
"""Serializers for web app advertisements"""
from advertisement.serializers import AdvertisementBaseSerializer
from main.serializers import PageBaseSerializer
class AdvertisementSerializer(serializers.ModelSerializer):
"""Serializer for model Advertisement."""
class AdvertisementPageTypeWebListSerializer(AdvertisementBaseSerializer):
"""Serializer for AdvertisementPageTypeWebView."""
class Meta:
model = models.Advertisement
fields = (
'id',
'uuid',
'url',
'image_url',
'width',
'height',
'block_level',
'source'
)
page = PageBaseSerializer(source='web_page', read_only=True)
class Meta(AdvertisementBaseSerializer.Meta):
"""Meta class."""
fields = AdvertisementBaseSerializer.Meta.fields + [
'page',
]

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 advertisement.views import web as views
from .common import common_urlpatterns
app_name = 'advertisements'
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"""
from rest_framework import generics
from rest_framework import permissions
from advertisement import models
from advertisement.serializers import web as serializers
"""Web views for app advertisement"""
from advertisement.serializers import AdvertisementPageTypeWebListSerializer
from .common import AdvertisementPageTypeListView
class AdvertisementListView(generics.ListAPIView):
"""List view for model Advertisement"""
pagination_class = None
model = models.Advertisement
permission_classes = (permissions.AllowAny,)
serializer_class = serializers.AdvertisementSerializer
class AdvertisementPageTypeWebListView(AdvertisementPageTypeListView):
"""Advertisement mobile list view."""
def get_queryset(self):
return models.Advertisement.objects\
.filter(page__page_name__contains=self.kwargs['page'])\
.filter(target_languages__locale=self.request.locale)
serializer_class = AdvertisementPageTypeWebListSerializer

View File

@ -1,9 +1,33 @@
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
class CollectionBackOfficeSerializer(serializers.ModelSerializer):
"""Collection serializer."""
class CollectionBackOfficeSerializer(CollectionBaseSerializer):
"""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:
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 = [
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 collection import models
from collection.serializers import common, back
from collection.serializers import back
class CollectionListCreateView(generics.ListCreateAPIView):
@ -16,4 +16,4 @@ class CollectionRUDView(generics.RetrieveUpdateDestroyAPIView):
queryset = models.Collection.objects.all()
serializer_class = back.CollectionBackOfficeSerializer
# todo: conf. permissions by TT
permission_classes = (permissions.IsAuthenticated, )
permission_classes = (permissions.IsAuthenticated, )

View File

@ -57,6 +57,8 @@ class ProductInline(admin.TabularInline):
class EstablishmentAdmin(BaseModelAdminMixin, admin.ModelAdmin):
"""Establishment admin."""
list_display = ['id', '__str__', 'image_tag', ]
search_fields = ['id', 'name', 'index_name', 'slug']
list_filter = ['public_mark', 'toque_number']
# inlines = [
# AwardInline, ContactPhoneInline, ContactEmailInline,
@ -94,3 +96,14 @@ class MenuAdmin(BaseModelAdminMixin, admin.ModelAdmin):
@admin.register(models.RatingStrategy)
class RatingStrategyAdmin(BaseModelAdminMixin, admin.ModelAdmin):
"""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',)
award_id = filters.NumberFilter(field_name='awards__id',)
search = filters.CharFilter(method='search_text')
type = filters.ChoiceFilter(choices=models.EstablishmentType.INDEX_NAME_TYPES,
method='by_type')
subtype = filters.ChoiceFilter(choices=models.EstablishmentSubType.INDEX_NAME_TYPES,
method='by_subtype')
type = filters.CharFilter(method='by_type')
subtype = filters.CharFilter(method='by_subtype')
class Meta:
"""Meta class."""

View File

@ -1,6 +1,6 @@
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
@ -10,47 +10,48 @@ class Command(BaseCommand):
def handle(self, *args, **kwargs):
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(
establishment_id__isnull=True
).values_list('id', 'establishment_id', 'facebook', 'twitter', 'instagram')
for id, es_id, facebook, twitter, instagram in queryset:
try:
establishment = Establishment.objects.get(old_id=es_id)
except Establishment.DoesNotExist:
for id, es_id, facebook_url, twitter_url, instagram_url in queryset:
establishment = Establishment.objects.filter(old_id=es_id).first()
if not establishment:
continue
except Establishment.MultipleObjectsReturned:
establishment = Establishment.objects.filter(old_id=es_id).first()
else:
if facebook:
if 'facebook.com/' not in facebook:
facebook = 'https://www.facebook.com/' + facebook
obj, _ = SocialNetwork.objects.get_or_create(
old_id=id,
establishment=establishment,
title='facebook',
url=facebook,
)
count += 1
if twitter:
if 'twitter.com/' not in twitter:
twitter = 'https://www.twitter.com/' + twitter
obj, _ = SocialNetwork.objects.get_or_create(
old_id=id,
establishment=establishment,
title='twitter',
url=twitter,
)
count += 1
if instagram:
if 'instagram.com/' not in instagram:
instagram = 'https://www.instagram.com/' + instagram
obj, _ = SocialNetwork.objects.get_or_create(
old_id=id,
establishment=establishment,
title='instagram',
url=instagram,
)
count += 1
if facebook_url:
if 'facebook.com/' not in facebook_url:
facebook_url = 'https://www.facebook.com/' + facebook_url
obj, _ = SocialNetwork.objects.get_or_create(
old_id=id,
establishment=establishment,
network=facebook,
url=facebook_url,
)
count += 1
if twitter_url:
if 'twitter.com/' not in twitter_url:
twitter_url = 'https://www.twitter.com/' + twitter_url
obj, _ = SocialNetwork.objects.get_or_create(
old_id=id,
establishment=establishment,
network=twitter,
url=twitter_url,
)
count += 1
if instagram_url:
if 'instagram.com/' not in instagram_url:
instagram = 'https://www.instagram.com/' + instagram_url
obj, _ = SocialNetwork.objects.get_or_create(
old_id=id,
establishment=establishment,
network=instagram,
url=instagram_url,
)
count += 1
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.db import connections
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
class Command(BaseCommand):
help = 'Add employee from old db to new db.'
@ -30,4 +33,7 @@ class Command(BaseCommand):
for e in tqdm(self.employees_sql()):
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.'))

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

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):
"""Social network serializers."""
class Meta:
model = models.SocialNetwork
fields = [
'id',
'establishment',
'title',
'network',
'url',
]
class PlatesSerializers(PlateSerializer):
"""Social network serializers."""
"""Plates serializers."""
currency_id = serializers.PrimaryKeyRelatedField(
source='currency',
@ -116,7 +125,8 @@ class PlatesSerializers(PlateSerializer):
class ContactPhoneBackSerializers(PlateSerializer):
"""Social network serializers."""
"""ContactPhone serializers."""
class Meta:
model = models.ContactPhone
fields = [
@ -127,7 +137,8 @@ class ContactPhoneBackSerializers(PlateSerializer):
class ContactEmailBackSerializers(PlateSerializer):
"""Social network serializers."""
"""ContactEmail serializers."""
class Meta:
model = models.ContactEmail
fields = [
@ -140,8 +151,8 @@ class ContactEmailBackSerializers(PlateSerializer):
# TODO: test decorator
@with_base_attributes
class EmployeeBackSerializers(serializers.ModelSerializer):
"""Employee serializers."""
"""Social network serializers."""
class Meta:
model = models.Employee
fields = [

View File

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

View File

@ -4,11 +4,14 @@ import logging
from celery import shared_task
from celery.schedules import crontab
from celery.task import periodic_task
from django.core import management
from django_elasticsearch_dsl.management.commands import search_index
from django_elasticsearch_dsl.registries import registry
from establishment import models
from location.models import Country
from search_indexes.documents.establishment import EstablishmentDocument
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,
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))
def rebuild_establishment_indices():
management.call_command(search_index.Command(), action='rebuild', models=[models.Establishment.__name__],
force=True)
def update_establishment_indices():
try:
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 http.cookies import SimpleCookie
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.
from translation.models import Language
from account.models import Role, UserRole
@ -19,7 +19,11 @@ class BaseTestCase(APITestCase):
self.email = 'sedragurda@desoz.com'
self.newsletter = True
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
tokens = User.create_jwt_tokens(self.user)
self.client.cookies = SimpleCookie(
@ -30,13 +34,14 @@ class BaseTestCase(APITestCase):
name="Test establishment type")
# Create lang object
self.lang = Language.objects.get(
self.lang = Language.objects.create(
title='Russia',
locale='ru-RU'
)
self.country_ru = Country.objects.get(
name={"en-GB": "Russian"}
self.country_ru = Country.objects.create(
name={'en-GB': 'Russian'},
code='RU',
)
self.region = Region.objects.create(name='Moscow area', code='01',
@ -72,10 +77,17 @@ class BaseTestCase(APITestCase):
establishment=self.establishment)
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):
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')
self.assertEqual(response.status_code, status.HTTP_200_OK)
@ -108,7 +120,7 @@ class EstablishmentBTests(BaseTestCase):
class EmployeeTests(BaseTestCase):
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)
data = {
@ -205,13 +217,40 @@ class PhoneTests(ChildTestCase):
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):
def test_social_CRUD(self):
response = self.client.get('/api/back/establishments/socials/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
data = {
'title': "Test social",
'network': self.social_choice.id,
'url': 'https://testsocial.com',
'establishment': self.establishment.id
}
@ -219,17 +258,17 @@ class SocialTests(ChildTestCase):
response = self.client.post('/api/back/establishments/socials/', data=data)
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)
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)
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)
@ -322,7 +361,7 @@ class EstablishmentShedulerTests(ChildTestCase):
class EstablishmentWebTests(BaseTestCase):
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')
self.assertEqual(response.status_code, status.HTTP_200_OK)
@ -351,7 +390,6 @@ class EstablishmentWebSimilarTests(ChildTestCase):
class EstablishmentWebCommentsTests(ChildTestCase):
def test_comments_CRUD(self):
response = self.client.get(f'/api/web/establishments/slug/{self.establishment.slug}/comments/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
@ -375,8 +413,9 @@ class EstablishmentWebCommentsTests(ChildTestCase):
'text': 'Test new establishment'
}
response = self.client.patch(f'/api/web/establishments/slug/{self.establishment.slug}/comments/{comment["id"]}/',
data=update_data)
response = self.client.patch(
f'/api/web/establishments/slug/{self.establishment.slug}/comments/{comment["id"]}/',
data=update_data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
response = self.client.delete(

View File

@ -6,7 +6,6 @@ from establishment import views
app_name = 'establishment'
urlpatterns = [
path('', views.EstablishmentListCreateView.as_view(), name='list'),
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('plates/', views.PlateListCreateView.as_view(), name='plates'),
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/<int:pk>/', views.SocialRUDView.as_view(), name='social-rud'),
path('phones/', views.PhonesListCreateView.as_view(), name='phones'),

View File

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

View File

@ -5,7 +5,7 @@ from establishment.filters import EstablishmentFilter
from establishment.serializers import EstablishmentBaseSerializer
from news.filters import NewsListFilterSet
from news.models import News
from news.serializers import NewsBaseSerializer
from news.serializers import NewsBaseSerializer, NewsListSerializer
from product.models import Product
from product.serializers import ProductBaseSerializer
from product.filters import ProductFilterSet
@ -47,7 +47,7 @@ class FavoritesProductListView(generics.ListAPIView):
class FavoritesNewsListView(generics.ListAPIView):
"""List views for news in favorites."""
serializer_class = NewsBaseSerializer
serializer_class = NewsListSerializer
filter_class = NewsListFilterSet
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)
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):
"""Country model."""
@ -29,8 +36,11 @@ class Country(TranslatedFieldsMixin, SVGImageMixin, ProjectBaseMixin):
low_price = models.IntegerField(default=25, verbose_name=_('Low price'))
high_price = models.IntegerField(default=50, verbose_name=_('High price'))
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)
objects = CountryQuerySet.as_manager()
@property
def time_format(self):
if self.code.lower() not in self.TWELVE_HOURS_FORMAT_COUNTRIES:
@ -200,7 +210,7 @@ class WineRegionQuerySet(models.QuerySet):
"""Wine region queryset."""
class WineRegion(models.Model):
class WineRegion(models.Model, TranslatedFieldsMixin):
"""Wine region model."""
name = models.CharField(_('name'), max_length=255)
country = models.ForeignKey(Country, on_delete=models.PROTECT,
@ -213,6 +223,9 @@ class WineRegion(models.Model):
description = TJSONField(blank=True, null=True, default=None,
verbose_name=_('description'),
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()
@ -221,6 +234,10 @@ class WineRegion(models.Model):
verbose_name_plural = _('wine regions')
verbose_name = _('wine region')
def __str__(self):
"""Override dunder method."""
return self.name
class WineSubRegionQuerySet(models.QuerySet):
"""Wine sub region QuerySet."""
@ -241,6 +258,10 @@ class WineSubRegion(models.Model):
verbose_name_plural = _('wine sub regions')
verbose_name = _('wine sub region')
def __str__(self):
"""Override dunder method."""
return self.name
class WineVillageQuerySet(models.QuerySet):
"""Wine village QuerySet."""
@ -264,6 +285,10 @@ class WineVillage(models.Model):
verbose_name = _('wine village')
verbose_name_plural = _('wine villages')
def __str__(self):
"""Override str dunder."""
return self.name
# todo: Make recalculate price levels
@receiver(post_save, sender=Country)

View File

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

View File

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

View File

@ -35,3 +35,8 @@ class CurrencyContentAdmin(admin.ModelAdmin):
@admin.register(models.Carousel)
class CarouselAdmin(admin.ModelAdmin):
"""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 main.models import Award, AwardType
from establishment.models import Employee
from tqdm import tqdm
class Command(BaseCommand):
@ -25,15 +26,18 @@ class Command(BaseCommand):
def handle(self, *args, **kwargs):
objects =[]
for a in self.award_sql():
profile = Employee.objects.filter(old_id=a.profile_id).first()
type = AwardType.objects.filter(old_id=a.award_type).first()
Award.objects.all().delete()
for a in tqdm(self.award_sql(), desc='Add award to profile'):
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
if profile and type:
award = Award(award_type=type, vintage_year=a.vintage_year,
title={"en-GB": a.title}, state=state,
content_object=profile, old_id=a.id)
objects.append(award)
if profiles.exists() and type.exists():
for profile in profiles:
award = Award(award_type=type.first(), vintage_year=a.vintage_year,
title={"en-GB": a.title}, state=state,
content_object=profile, old_id=a.id)
objects.append(award)
awards = Award.objects.bulk_create(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 main.models import AwardType
from location.models import Country
from tqdm import tqdm
class Command(BaseCommand):
help = '''Add award types from old db to new db.
@ -24,7 +24,7 @@ class Command(BaseCommand):
def handle(self, *args, **kwargs):
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()
if country:
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.utils.translation import gettext_lazy as _
from advertisement.models import Advertisement
from configuration.models import TranslationSettings
from location.models import Country
from main import methods
@ -99,27 +98,12 @@ class SiteSettings(ProjectBaseMixin):
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):
"""Feature model."""
slug = models.SlugField(max_length=255, unique=True)
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')
class Meta:
@ -310,3 +294,56 @@ class Carousel(models.Model):
elif self.link not in EMPTY_VALUES:
return 'external'
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."""
from rest_framework import serializers
from advertisement.serializers.web import AdvertisementSerializer
from location.serializers import CountrySerializer
from main import models
from utils.serializers import ProjectModelSerializer, TranslatedField, RecursiveFieldSerializer
@ -25,7 +24,7 @@ class SiteFeatureSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(source='feature.id')
slug = serializers.CharField(source='feature.slug')
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')
nested = RecursiveFieldSerializer(many=True, allow_null=True)
@ -94,11 +93,20 @@ class SiteSerializer(serializers.ModelSerializer):
class Meta:
"""Meta class."""
model = models.SiteSettings
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):
# """Site feature serializer."""
#
@ -167,15 +175,27 @@ class CarouselListSerializer(serializers.ModelSerializer):
]
class PageSerializer(serializers.ModelSerializer):
page_name = serializers.CharField()
advertisements = AdvertisementSerializer(source='advertisements', many=True)
class PageBaseSerializer(serializers.ModelSerializer):
"""Serializer for model Page"""
class Meta:
"""Meta class."""
model = models.Carousel
model = models.Page
fields = [
'id',
'page_name',
'advertisements'
'image_url',
'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):
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()
if 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."""
from django.contrib.contenttypes import fields as generic
from django.db import models
from django.db.models import Case, When
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
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.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):
"""NewsType model."""
@ -31,6 +56,10 @@ class NewsType(models.Model):
class NewsQuerySet(TranslationQuerysetMixin):
"""QuerySet for model News"""
def sort_by_start(self):
"""Return qs sorted by start DESC"""
return self.order_by('-start')
def rating_value(self):
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 country?
def should_read(self, news):
def should_read(self, news, user):
return self.model.objects.exclude(pk=news.pk).published(). \
annotate_in_favorites(user). \
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(). \
annotate_in_favorites(user). \
with_base_related().by_type(news.news_type). \
by_tags(news.tags.all()).distinct().order_by('-start')
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)
def annotate_in_favorites(self, user):
"""Annotate flag in_favorites"""
favorite_news_ids = []
if user.is_authenticated:
favorite_news_ids = user.favorite_news_ids
return self.annotate(
in_favorites=Case(
When(id__in=favorite_news_ids, then=True),
default=False,
output_field=models.BooleanField(default=False)
)
)
class News(BaseAttributes, TranslatedFieldsMixin):
@ -164,6 +184,7 @@ class News(BaseAttributes, TranslatedFieldsMixin):
tags = models.ManyToManyField('tag.Tag', related_name='news',
verbose_name=_('Tags'))
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)
favorites = generic.GenericRelation(to='favorites.Favorites')
agenda = models.ForeignKey('news.Agenda', blank=True, null=True,
@ -193,13 +214,11 @@ class News(BaseAttributes, TranslatedFieldsMixin):
def web_url(self):
return reverse('web:news:rud', kwargs={'slug': self.slug})
@property
def should_read(self):
return self.__class__.objects.should_read(self)[:3]
def should_read(self, user):
return self.__class__.objects.should_read(self, user)[:3]
@property
def same_theme(self):
return self.__class__.objects.same_theme(self)[:3]
def same_theme(self, user):
return self.__class__.objects.same_theme(self, user)[:3]
@property
def main_image(self):
@ -216,6 +235,13 @@ class News(BaseAttributes, TranslatedFieldsMixin):
if self.main_image:
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):
"""QuerySet for model News"""

View File

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

View File

@ -9,6 +9,7 @@ from gallery.tasks import delete_image
from news import filters, models, serializers
from rating.tasks import add_rating
from utils.permissions import IsCountryAdmin, IsContentPageManager
from utils.views import CreateDestroyGalleryViewMixin
class NewsMixinView:
@ -17,11 +18,13 @@ class NewsMixinView:
permission_classes = (permissions.AllowAny,)
serializer_class = serializers.NewsBaseSerializer
def get_queryset(self, *args, **kwargs):
def get_queryset(self):
"""Override get_queryset method."""
qs = models.News.objects.published() \
.with_base_related() \
.annotate_in_favorites(self.request.user) \
.order_by('-is_highlighted', '-created')
country_code = self.request.country_code
if country_code:
qs = qs.by_country_code(country_code)
@ -41,10 +44,10 @@ class NewsDetailView(NewsMixinView, generics.RetrieveAPIView):
lookup_field = 'slug'
serializer_class = serializers.NewsDetailWebSerializer
queryset = models.News.objects.all()
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):
@ -60,8 +63,13 @@ class NewsBackOfficeMixinView:
"""News back office mixin view."""
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,
@ -84,8 +92,7 @@ class NewsBackOfficeLCView(NewsBackOfficeMixinView,
class NewsBackOfficeGalleryCreateDestroyView(NewsBackOfficeMixinView,
generics.CreateAPIView,
generics.DestroyAPIView):
CreateDestroyGalleryViewMixin):
"""Resource for a create gallery for news for back-office users."""
serializer_class = serializers.NewsBackOfficeGallerySerializer
@ -103,24 +110,6 @@ class NewsBackOfficeGalleryCreateDestroyView(NewsBackOfficeMixinView,
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):
"""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')),
)
user = models.OneToOneField(User, blank=True, null=True, default=None,
on_delete=models.SET_NULL, related_name='subscriber',
verbose_name=_('User'))
email = models.EmailField(blank=True, null=True, default=None, unique=True,
verbose_name=_('Email'))
ip_address = models.GenericIPAddressField(blank=True, null=True, default=None,
verbose_name=_('IP address'))
country_code = models.CharField(max_length=10, blank=True, null=True, default=None,
verbose_name=_('Country code'))
locale = models.CharField(blank=True, null=True, default=None,
max_length=10, verbose_name=_('Locale identifier'))
state = models.PositiveIntegerField(choices=STATE_CHOICES, default=USABLE,
verbose_name=_('State'))
update_code = models.CharField(max_length=254, blank=True, null=True, default=None,
db_index=True, verbose_name=_('Token'))
user = models.OneToOneField(
User,
blank=True,
null=True,
default=None,
on_delete=models.SET_NULL,
related_name='subscriber',
verbose_name=_('User'),
)
email = models.EmailField(blank=True, null=True, default=None, unique=True, verbose_name=_('Email'))
ip_address = models.GenericIPAddressField(blank=True, null=True, default=None, verbose_name=_('IP address'))
country_code = models.CharField(max_length=10, blank=True, null=True, default=None, verbose_name=_('Country code'))
locale = models.CharField(blank=True, null=True, default=None, max_length=10, verbose_name=_('Locale identifier'))
state = models.PositiveIntegerField(choices=STATE_CHOICES, default=USABLE, verbose_name=_('State'))
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)()

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 django.db.models import Count
from transfer.models import EmailAddresses, NewsletterSubscriber
from transfer.serializers.notification import SubscriberSerializer, NewsletterSubscriberSerializer
def transfer_subscriber():
queryset = EmailAddresses.objects.filter(state="usable")
queryset = EmailAddresses.objects.filter(state='usable')
serialized_data = SubscriberSerializer(data=list(queryset.values()), many=True)
if serialized_data.is_valid():
serialized_data.save()
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 = {
"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_display = ('id', '__str__', 'get_category_display', 'product_type')
raw_id_fields = ('subtypes', 'classifications', 'standards',
'tags', 'gallery')
'tags', 'gallery', 'establishment',)
@admin.register(ProductGallery)

View File

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

View File

@ -1,7 +1,7 @@
from django.core.management.base import BaseCommand
from transfer.models import Assemblages
from transfer.serializers.product import AssemblageTagSerializer
from transfer.serializers.product import AssemblageProductTagSerializer
class Command(BaseCommand):
@ -10,7 +10,7 @@ class Command(BaseCommand):
def handle(self, *args, **kwarg):
errors = []
legacy_products = Assemblages.objects.filter(product_id__isnull=False)
serialized_data = AssemblageTagSerializer(
serialized_data = AssemblageProductTagSerializer(
data=list(legacy_products.values()),
many=True)
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 product.models import Product
from tqdm import tqdm
from django.db.models.functions import Lower
class Command(BaseCommand):
@ -27,8 +26,7 @@ class Command(BaseCommand):
def add_category_tag(self):
objects = []
for c in tqdm(self.category_sql(), desc='Add category tags'):
categories = TagCategory.objects.filter(index_name=c.category,
value_type=c.value_type
categories = TagCategory.objects.filter(index_name=c.category
)
if not categories.exists():
objects.append(
@ -44,7 +42,8 @@ class Command(BaseCommand):
with connections['legacy'].cursor() as cursor:
cursor.execute('''
select
DISTINCT
DISTINCT
m.id as old_id,
trim(CONVERT(m.value USING utf8)) as tag_value,
trim(CONVERT(v.key_name USING utf8)) as tag_category
FROM product_metadata m
@ -57,16 +56,21 @@ class Command(BaseCommand):
for t in tqdm(self.tag_sql(), desc='Add tags'):
category = TagCategory.objects.get(index_name=t.tag_category)
tag = Tag.objects.filter(
tags = Tag.objects.filter(
category=category,
value=t.tag_value
)
if not tag.exists():
if not tags.exists():
objects.append(Tag(label={"en-GB": t.tag_value},
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)
self.stdout.write(self.style.WARNING(f'Add or get tag objects.'))
@ -75,6 +79,7 @@ class Command(BaseCommand):
cursor.execute('''
select
DISTINCT
m.id as old_id_tag,
m.product_id,
lower(trim(CONVERT(m.value USING utf8))) as tag_value,
trim(CONVERT(v.key_name USING utf8)) as tag_category
@ -84,15 +89,12 @@ class Command(BaseCommand):
return namedtuplefetchall(cursor)
def add_product_tag(self):
objects = []
for t in tqdm(self.product_sql(), desc='Add product tag'):
category = TagCategory.objects.get(index_name=t.tag_category)
tag = Tag.objects.annotate(lower_value=Lower('value'))
tag.filter(lower_value=t.tag_value, category=category)
products = Product.objects.filter(old_id=t.product_id)
if products.exists():
products.tags.add(tag)
products.save()
tags = Tag.objects.filter(old_id_meta_product=t.old_id_tag)
product = Product.objects.get(old_id=t.product_id)
for tag in tags:
if product not in tag.products.all():
product.tags.add(tag)
self.stdout.write(self.style.WARNING(f'Add or get tag objects.'))
@ -108,9 +110,8 @@ class Command(BaseCommand):
tag.label = new_label
tag.save()
def handle(self, *args, **kwargs):
self.add_category_tag()
self.add_tag()
self.check_tag()
self.add_product_tag()
self.check_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.core.exceptions import ValidationError
from django.db import models
from django.db.models import Case, When
from django.utils.translation import gettext_lazy as _
from django.core.validators import MaxValueValidator, MinValueValidator
@ -15,25 +16,16 @@ class ProductType(TranslatedFieldsMixin, ProjectBaseMixin):
STR_FIELD_NAME = 'name'
# INDEX NAME CHOICES
# EXAMPLE OF INDEX NAME CHOICES
FOOD = 'food'
WINE = 'wine'
LIQUOR = 'liquor'
SOUVENIR = 'souvenir'
BOOK = 'book'
INDEX_NAME_TYPES = (
(FOOD, _('Food')),
(WINE, _('Wine')),
(LIQUOR, _('Liquor')),
(SOUVENIR, _('Souvenir')),
(BOOK, _('Book')),
)
name = TJSONField(blank=True, null=True, default=None,
verbose_name=_('Name'), help_text='{"en-GB":"some text"}')
index_name = models.CharField(max_length=50, choices=INDEX_NAME_TYPES,
unique=True, db_index=True,
index_name = models.CharField(max_length=50, unique=True, db_index=True,
verbose_name=_('Index name'))
use_subtypes = models.BooleanField(_('Use subtypes'), default=True)
tag_categories = models.ManyToManyField('tag.TagCategory',
@ -52,24 +44,17 @@ class ProductSubType(TranslatedFieldsMixin, ProjectBaseMixin):
STR_FIELD_NAME = 'name'
# INDEX NAME CHOICES
# EXAMPLE OF INDEX NAME CHOICES
RUM = 'rum'
PLATE = 'plate'
OTHER = 'other'
INDEX_NAME_TYPES = (
(RUM, _('Rum')),
(PLATE, _('Plate')),
(OTHER, _('Other')),
)
product_type = models.ForeignKey(ProductType, on_delete=models.CASCADE,
related_name='subtypes',
verbose_name=_('Product type'))
name = TJSONField(blank=True, null=True, default=None,
verbose_name=_('Name'), help_text='{"en-GB":"some text"}')
index_name = models.CharField(max_length=50, choices=INDEX_NAME_TYPES,
unique=True, db_index=True,
index_name = models.CharField(max_length=50, unique=True, db_index=True,
verbose_name=_('Index name'))
class Meta:
@ -92,7 +77,14 @@ class ProductQuerySet(models.QuerySet):
def with_base_related(self):
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):
return self.filter(category=self.model.COMMON)
@ -101,15 +93,36 @@ class ProductQuerySet(models.QuerySet):
return self.filter(category=self.model.ONLINE)
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):
"""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):
"""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):
@ -155,7 +168,7 @@ class Product(TranslatedFieldsMixin, BaseAttributes):
related_name='products',
verbose_name=_('establishment'))
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,
related_name='wines',
blank=True, null=True, default=None,
@ -173,7 +186,7 @@ class Product(TranslatedFieldsMixin, BaseAttributes):
help_text=_('attribute from legacy db'))
wine_village = models.ForeignKey('location.WineVillage', on_delete=models.PROTECT,
blank=True, null=True,
verbose_name=_('wine appellation'))
verbose_name=_('wine village'))
slug = models.SlugField(unique=True, max_length=255, null=True,
verbose_name=_('Slug'))
favorites = generic.GenericRelation(to='favorites.Favorites')
@ -258,7 +271,16 @@ class Product(TranslatedFieldsMixin, BaseAttributes):
@property
def related_tags(self):
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):

View File

@ -2,9 +2,11 @@
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from product import models
from product.serializers import ProductDetailSerializer
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):
@ -33,12 +35,16 @@ class ProductBackOfficeGallerySerializer(serializers.ModelSerializer):
if not product_qs.exists():
raise serializers.ValidationError({'detail': _('Product not found')})
if not image_qs.exists():
raise serializers.ValidationError({'detail': _('Image not found')})
product = product_qs.first()
image = image_qs.first()
if image in product.gallery.all():
raise serializers.ValidationError({'detail': _('Image is already added.')})
attrs['product'] = product
attrs['image'] = image
@ -46,8 +52,10 @@ class ProductBackOfficeGallerySerializer(serializers.ModelSerializer):
class ProductBackOfficeDetailSerializer(ProductDetailSerializer):
"""Product back-office detail serializer."""
class Meta(ProductDetailSerializer.Meta):
"""Meta class."""
fields = ProductDetailSerializer.Meta.fields + [
'description',
'available',
@ -58,13 +66,64 @@ class ProductBackOfficeDetailSerializer(ProductDetailSerializer):
'wine_village',
'state',
]
extra_kwargs = {
'description': {'write_only': True},
'available': {'write_only': True},
'product_type': {'write_only': True},
'establishment': {'write_only': True},
'wine_region': {'write_only': True},
'wine_sub_region': {'write_only': True},
'wine_village': {'write_only': True},
'state': {'write_only': True},
}
class ProductTypeBackOfficeDetailSerializer(ProductTypeBaseSerializer):
"""Product type back-office detail serializer."""
class Meta(ProductTypeBaseSerializer.Meta):
"""Meta class."""
fields = ProductTypeBaseSerializer.Meta.fields + [
'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 main.serializers import AwardSerializer
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):
"""ProductSubType base serializer"""
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:
model = models.ProductSubType
@ -32,14 +46,13 @@ class ProductSubTypeBaseSerializer(serializers.ModelSerializer):
class ProductTypeBaseSerializer(serializers.ModelSerializer):
"""ProductType base serializer"""
name_translated = TranslatedField()
index_name_display = serializers.CharField(source='get_index_name_display')
class Meta:
model = models.ProductType
fields = [
'id',
'name_translated',
'index_name_display',
'index_name',
]
@ -72,13 +85,17 @@ class ProductStandardBaseSerializer(serializers.ModelSerializer):
class ProductBaseSerializer(serializers.ModelSerializer):
"""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)
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',
allow_null=True,
read_only=True)
in_favorites = serializers.BooleanField(allow_null=True)
class Meta:
"""Meta class."""
@ -94,6 +111,9 @@ class ProductBaseSerializer(serializers.ModelSerializer):
'vintage',
'tags',
'preview_image_url',
'wine_region',
'wine_colors',
'in_favorites',
]
@ -104,9 +124,7 @@ class ProductDetailSerializer(ProductBaseSerializer):
awards = AwardSerializer(many=True, read_only=True)
classifications = ProductClassificationBaseSerializer(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_colors = TagBaseSerializer(many=True, read_only=True)
bottles_produced = TagBaseSerializer(many=True, read_only=True)
sugar_contents = TagBaseSerializer(many=True, read_only=True)
image_url = serializers.ImageField(source='main_image_url',
@ -120,9 +138,7 @@ class ProductDetailSerializer(ProductBaseSerializer):
'awards',
'classifications',
'standards',
'wine_region',
'wine_sub_region',
'wine_colors',
'bottles_produced',
'sugar_contents',
'image_url',

View File

@ -1,14 +1,24 @@
"""Product backoffice url patterns."""
from django.urls import path
from product.urls.common import urlpatterns as common_urlpatterns
from product import views
urlpatterns = [
path('', views.ProductListCreateBackOfficeView.as_view(), name='list-create'),
path('<int:pk>/', views.ProductDetailBackOfficeView.as_view(), name='rud'),
path('<int:pk>/gallery/', views.ProductBackOfficeGalleryListView.as_view(),
name='gallery-list'),
path('<int:pk>/gallery/<int:image_id>/', views.ProductBackOfficeGalleryCreateDestroyView.as_view(),
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'),
path('slug/<slug:slug>/comments/<int:comment_id>/', views.ProductCommentRUDView.as_view(),
name='rud-comment'),
]

View File

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

View File

@ -1,25 +1,52 @@
"""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 rest_framework import generics, status, permissions
from rest_framework import generics, status, permissions, views
from rest_framework.response import Response
from gallery.tasks import delete_image
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."""
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,
generics.CreateAPIView,
generics.DestroyAPIView):
CreateDestroyGalleryViewMixin):
"""Resource for a create gallery for product for back-office users."""
serializer_class = serializers.ProductBackOfficeGallerySerializer
@ -37,24 +64,6 @@ class ProductBackOfficeGalleryCreateDestroyView(ProductBackOfficeMixinView,
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):
"""Resource for returning gallery for product for back-office users."""
@ -78,3 +87,47 @@ class ProductBackOfficeGalleryListView(ProductBackOfficeMixinView, generics.List
class ProductDetailBackOfficeView(ProductBackOfficeMixinView, generics.RetrieveUpdateDestroyAPIView):
"""Product back-office R/U/D view."""
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):
"""Product base view"""
permission_classes = (permissions.AllowAny, )
permission_classes = (permissions.AllowAny,)
def get_queryset(self):
"""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):
@ -64,9 +68,9 @@ class ProductCommentListView(generics.ListAPIView):
"""Override get_queryset method"""
product = get_object_or_404(Product, slug=self.kwargs['slug'])
return Comment.objects.by_content_type(app_label='product',
model='product')\
.by_object_id(object_id=product.pk)\
.order_by('-created')
model='product') \
.by_object_id(object_id=product.pk) \
.order_by('-created')
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
if hasattr(self.content_object, 'title'):
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):
"""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')),
)
reviewer = models.ForeignKey('account.User',
related_name='reviews',
on_delete=models.CASCADE,
verbose_name=_('Reviewer'))
reviewer = models.ForeignKey(
'account.User',
related_name='reviews',
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'), null=True, blank=True,
default=None, help_text='{"en-GB":"Text review"}')
_('text'),
null=True,
blank=True,
default=None,
help_text='{"en-GB":"Text review"}',
)
content_type = models.ForeignKey(generic.ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
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)
child = models.ForeignKey('self',
blank=True, default=None, null=True,
on_delete=models.CASCADE,
verbose_name=_('Child review'))
published_at = models.DateTimeField(verbose_name=_('Publish datetime'),
blank=True, default=None, null=True,
help_text=_('Review published datetime'))
vintage = models.IntegerField(verbose_name=_('Year of review'),
validators=[MinValueValidator(1900),
MaxValueValidator(2100)])
country = models.ForeignKey('location.Country', on_delete=models.CASCADE,
related_name='country', verbose_name=_('Country'),
null=True)
published_at = models.DateTimeField(
_('Publish datetime'),
blank=True,
default=None,
null=True,
help_text=_('Review published datetime'),
)
vintage = models.IntegerField(_('Year of review'), validators=[MinValueValidator(1900), MaxValueValidator(2100)])
mark = models.FloatField(verbose_name=_('mark'), blank=True, null=True, default=None)
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
objects = ReviewQuerySet.as_manager()

View File

@ -1,5 +1,6 @@
from rest_framework import serializers
from review.models import Review
from review.models import Review, Inquiries, GridItems
class ReviewBaseSerializer(serializers.ModelSerializer):
@ -9,7 +10,6 @@ class ReviewBaseSerializer(serializers.ModelSerializer):
'id',
'reviewer',
'text',
'language',
'status',
'child',
'published_at',
@ -27,3 +27,41 @@ class ReviewShortSerializer(ReviewBaseSerializer):
fields = (
'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 django.db.models import Q
from product.models import Product
from account.models import User
from account.transfer_data import STOP_LIST
from establishment.models import Establishment
from review.models import Inquiries as NewInquiries, Review
from transfer.models import Reviews, ReviewTexts, Inquiries, GridItems, InquiryPhotos
from transfer.serializers.grid import GridItemsSerializer
from transfer.serializers.inquiries import InquiriesSerializer
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():
@ -28,77 +33,40 @@ def transfer_languages():
def transfer_reviews():
# TODO: убрать LIKE UPPER("%%paris%%"), accounts.email IN
queryset = Reviews.objects.raw("""SELECT reviews.id, reviews.vintage, reviews.establishment_id,
reviews.reviewer_id, review_texts.text AS text, reviews.mark, reviews.published_at,
review_texts.created_at AS published, review_texts.locale AS locale,
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)
establishments = Establishment.objects.filter(old_id__isnull=False).values_list('old_id', flat=True)
queryset = Reviews.objects.filter(
establishment_id__in=list(establishments),
).values('id', 'reviewer_id', 'aasm_state', 'created_at', 'establishment_id', 'mark', 'vintage')
serialized_data = ReviewSerializer(data=list(queryset.values()), many=True)
if serialized_data.is_valid():
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:
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():
@ -137,14 +105,38 @@ def transfer_inquiry_photos():
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 = {
"overlook": [
transfer_languages,
transfer_reviews
# transfer_languages,
transfer_reviews,
transfer_text_review,
],
'inquiries': [
transfer_inquiries,
transfer_grid,
transfer_inquiry_photos,
],
"product_review": [
transfer_product_reviews,
]
}

View File

@ -8,4 +8,10 @@ app_name = 'review'
urlpatterns = [
path('', views.ReviewLstView.as_view(), name='review-list-create'),
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 review import serializers
from review import models
from review import serializers
from utils.permissions import IsReviewerManager, IsRestaurantReviewer
@ -8,12 +9,55 @@ class ReviewLstView(generics.ListCreateAPIView):
"""Comment list create view."""
serializer_class = serializers.ReviewBaseSerializer
queryset = models.Review.objects.all()
permission_classes = [permissions.IsAuthenticatedOrReadOnly,]
permission_classes = [permissions.IsAuthenticatedOrReadOnly, ]
class ReviewRUDView(generics.RetrieveUpdateDestroyAPIView):
"""Comment RUD view."""
serializer_class = serializers.ReviewBaseSerializer
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'

View File

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

View File

@ -31,6 +31,7 @@ class NewsDocument(Document):
'id': fields.IntegerField(attr='id'),
'label': fields.ObjectField(attr='label_indexing',
properties=OBJECT_FIELD_PROPERTIES),
'value': fields.KeywordField()
},
multi=True)
@ -49,7 +50,7 @@ class NewsDocument(Document):
related_models = [models.NewsType]
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):
"""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 news.serializers import NewsTypeSerializer
from search_indexes.documents import EstablishmentDocument, NewsDocument
from search_indexes.documents.product import ProductDocument
from search_indexes.utils import get_translated_value
@ -19,6 +20,73 @@ class TagsDocumentSerializer(serializers.Serializer):
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):
"""City serializer for ES Document,"""
@ -94,6 +162,8 @@ class NewsDocumentSerializer(DocumentSerializer):
class EstablishmentDocumentSerializer(DocumentSerializer):
"""Establishment document serializer."""
establishment_type = EstablishmentTypeSerializer()
establishment_subtypes = EstablishmentTypeSerializer(many=True)
address = AddressDocumentSerializer(allow_null=True)
tags = TagsDocumentSerializer(many=True)
schedule = ScheduleDocumentSerializer(many=True, allow_null=True)
@ -123,3 +193,41 @@ class EstablishmentDocumentSerializer(DocumentSerializer):
# 'establishment_type',
# 'establishment_subtypes',
)
class ProductDocumentSerializer(DocumentSerializer):
"""Product document serializer"""
tags = TagsDocumentSerializer(many=True)
subtypes = ProductSubtypeDocumentSerializer(many=True)
wine_region = WineRegionDocumentSerializer(allow_null=True)
wine_colors = WineColorDocumentSerializer(many=True)
product_type = serializers.SerializerMethodField()
establishment_detail = ProductEstablishmentDocumentSerializer(source='establishment', allow_null=True)
@staticmethod
def get_product_type(obj):
return get_translated_value(obj.product_type.name if obj.product_type else {})
class Meta:
"""Meta class."""
document = ProductDocument
fields = (
'id',
'category',
'name',
'available',
'public_mark',
'slug',
'old_id',
'state',
'old_unique_key',
'vintage',
'tags',
'product_type',
'subtypes',
'wine_region',
'wine_colors',
'establishment_detail',
)

View File

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

View File

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

View File

@ -3,224 +3,35 @@ from django_elasticsearch_dsl import fields
from utils.models import get_current_locale, get_default_locale
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',
'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',
'ru-RU',
'rw-RW',
'sah-RU',
'sa-IN',
'se-FI',
'se-NO',
'se-SE',
'si-LK',
'sk-SK',
'sl-SI',
'sma-NO',
'sma-SE',
'smj-NO',
'smj-SE',
'smn-FI',
'sms-FI',
'sq-AL',
'sr-Cyrl-BA',
'sr-Cyrl-CS',
'sr-Cyrl-ME',
'sr-Cyrl-RS',
'sr-Latn-BA',
'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',
'ka-GE',
'de-AT',
'de-DE',
'el-GR',
'hu-HU',
'nl-BE',
'ja-JP',
'it-IT',
'pl-PL',
'he-IL',
'pt-BR',
'hu_HU',
]
# object field properties
OBJECT_FIELD_PROPERTIES = {locale: fields.TextField() for locale in ALL_LOCALES_LIST}
OBJECT_FIELD_PROPERTIES.update({
'en-AU': fields.TextField(analyzer='english'),
'en-US': fields.TextField(analyzer='english'),
'en-GB': fields.TextField(analyzer='english'),
'en-CA': fields.TextField(analyzer='english'),
'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 search_indexes import serializers, filters
from search_indexes.documents import EstablishmentDocument, NewsDocument
from search_indexes.documents.product import ProductDocument
from utils.pagination import ProjectMobilePagination
@ -20,7 +21,6 @@ class NewsDocumentViewSet(BaseDocumentViewSet):
pagination_class = ProjectMobilePagination
permission_classes = (permissions.AllowAny,)
serializer_class = serializers.NewsDocumentSerializer
ordering = ('id',)
filter_backends = [
filters.CustomSearchFilterBackend,
@ -28,9 +28,9 @@ class NewsDocumentViewSet(BaseDocumentViewSet):
]
search_fields = {
'title': {'fuzziness': 'auto:3,4'},
'subtitle': {'fuzziness': 'auto'},
'description': {'fuzziness': 'auto'},
'title': {'fuzziness': 'auto:2,5'},
'subtitle': {'fuzziness': 'auto:2,5'},
'description': {'fuzziness': 'auto:2,5'},
}
translated_search_fields = (
'title',
@ -43,6 +43,14 @@ class NewsDocumentViewSet(BaseDocumentViewSet):
'field': 'tags.id',
'lookups': [
constants.LOOKUP_QUERY_IN,
constants.LOOKUP_QUERY_EXCLUDE
]
},
'tag_value': {
'field': 'tags.value',
'lookups': [
constants.LOOKUP_QUERY_IN,
constants.LOOKUP_QUERY_EXCLUDE
]
},
'slug': 'slug',
@ -73,22 +81,21 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet):
FilteringFilterBackend,
filters.CustomSearchFilterBackend,
GeoSpatialFilteringFilterBackend,
DefaultOrderingFilterBackend,
# DefaultOrderingFilterBackend,
]
search_fields = {
'name': {'fuzziness': 'auto:3,4',
'name': {'fuzziness': 'auto:2,5',
'boost': '2'},
'transliterated_name': {'fuzziness': 'auto:3,4',
'transliterated_name': {'fuzziness': 'auto:2,5',
'boost': '2'},
'index_name': {'fuzziness': 'auto:3,4',
'index_name': {'fuzziness': 'auto:2,5',
'boost': '2'},
'description': {'fuzziness': 'auto'},
'description': {'fuzziness': 'auto:2,5'},
}
translated_search_fields = (
'description',
)
ordering = 'id'
filter_fields = {
'slug': 'slug',
'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):
"""TagCategory filterset."""
establishment_type = filters.ChoiceFilter(
choices=EstablishmentType.INDEX_NAME_TYPES,
method='by_establishment_type')
establishment_type = filters.CharFilter(method='by_establishment_type')
class Meta:
"""Meta class."""
@ -54,3 +52,17 @@ class TagsFilterSet(TagsBaseFilterSet):
model = models.Tag
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_meta_product = models.PositiveIntegerField(_('old id metadata product'),
blank=True, null=True, default=None)
objects = TagQuerySet.as_manager()
class Meta:

View File

@ -13,7 +13,6 @@ class TagBaseSerializer(serializers.ModelSerializer):
"""Serializer for model Tag."""
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)
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):
"""Tag Category detail serializer for back-office users."""

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