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:
commit
cf7cc9bd0c
|
|
@ -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,16 +260,32 @@ 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')\
|
||||
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')\
|
||||
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):
|
||||
"""UserRole model."""
|
||||
|
|
|
|||
|
|
@ -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, )
|
||||
|
|
|
|||
34
apps/advertisement/migrations/0006_auto_20191115_0750.py
Normal file
34
apps/advertisement/migrations/0006_auto_20191115_0750.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
30
apps/advertisement/migrations/0007_auto_20191115_0750.py
Normal file
30
apps/advertisement/migrations/0007_auto_20191115_0750.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
from .common import *
|
||||
from .mobile import *
|
||||
from .web import *
|
||||
39
apps/advertisement/serializers/common.py
Normal file
39
apps/advertisement/serializers/common.py
Normal 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',
|
||||
]
|
||||
15
apps/advertisement/serializers/mobile.py
Normal file
15
apps/advertisement/serializers/mobile.py
Normal 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',
|
||||
]
|
||||
|
|
@ -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',
|
||||
]
|
||||
|
|
|
|||
8
apps/advertisement/urls/common.py
Normal file
8
apps/advertisement/urls/common.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
"""Advertisement common urlpaths."""
|
||||
from django.urls import path
|
||||
|
||||
|
||||
app_name = 'advertisements'
|
||||
|
||||
common_urlpatterns = [
|
||||
]
|
||||
14
apps/advertisement/urls/mobile.py
Normal file
14
apps/advertisement/urls/mobile.py
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
from .common import *
|
||||
from .mobile import *
|
||||
from .web import *
|
||||
32
apps/advertisement/views/common.py
Normal file
32
apps/advertisement/views/common.py
Normal 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()
|
||||
9
apps/advertisement/views/mobile.py
Normal file
9
apps/advertisement/views/mobile.py
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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',)
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
|
|
|||
|
|
@ -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,46 +10,47 @@ 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:
|
||||
continue
|
||||
except Establishment.MultipleObjectsReturned:
|
||||
for id, es_id, facebook_url, twitter_url, instagram_url in queryset:
|
||||
establishment = Establishment.objects.filter(old_id=es_id).first()
|
||||
else:
|
||||
if facebook:
|
||||
if 'facebook.com/' not in facebook:
|
||||
facebook = 'https://www.facebook.com/' + facebook
|
||||
if not establishment:
|
||||
continue
|
||||
|
||||
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,
|
||||
title='facebook',
|
||||
url=facebook,
|
||||
network=facebook,
|
||||
url=facebook_url,
|
||||
)
|
||||
count += 1
|
||||
if twitter:
|
||||
if 'twitter.com/' not in twitter:
|
||||
twitter = 'https://www.twitter.com/' + twitter
|
||||
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,
|
||||
title='twitter',
|
||||
url=twitter,
|
||||
network=twitter,
|
||||
url=twitter_url,
|
||||
)
|
||||
count += 1
|
||||
if instagram:
|
||||
if 'instagram.com/' not in instagram:
|
||||
instagram = 'https://www.instagram.com/' + instagram
|
||||
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,
|
||||
title='instagram',
|
||||
url=instagram,
|
||||
network=instagram,
|
||||
url=instagram_url,
|
||||
)
|
||||
count += 1
|
||||
|
||||
|
|
|
|||
|
|
@ -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.'))
|
||||
|
|
|
|||
23
apps/establishment/migrations/0060_auto_20191113_1512.py
Normal file
23
apps/establishment/migrations/0060_auto_20191113_1512.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
35
apps/establishment/migrations/0061_auto_20191114_0550.py
Normal file
35
apps/establishment/migrations/0061_auto_20191114_0550.py
Normal 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,
|
||||
),
|
||||
]
|
||||
|
|
@ -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):
|
||||
"""
|
||||
|
|
@ -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'))
|
||||
|
|
@ -522,7 +510,7 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
|
|||
@property
|
||||
def last_published_review(self):
|
||||
"""Return last published review"""
|
||||
return self.reviews.published()\
|
||||
return self.reviews.published() \
|
||||
.order_by('-published_at').first()
|
||||
|
||||
@property
|
||||
|
|
@ -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.."""
|
||||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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 = [
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
@ -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,7 +413,8 @@ class EstablishmentWebCommentsTests(ChildTestCase):
|
|||
'text': 'Test new establishment'
|
||||
}
|
||||
|
||||
response = self.client.patch(f'/api/web/establishments/slug/{self.establishment.slug}/comments/{comment["id"]}/',
|
||||
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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
0
apps/location/management/__init__.py
Normal file
0
apps/location/management/__init__.py
Normal file
0
apps/location/management/commands/__init__.py
Normal file
0
apps/location/management/commands/__init__.py
Normal file
19
apps/location/migrations/0024_wineregion_tag_categories.py
Normal file
19
apps/location/migrations/0024_wineregion_tag_categories.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
23
apps/location/migrations/0025_auto_20191114_0809.py
Normal file
23
apps/location/migrations/0025_auto_20191114_0809.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
18
apps/location/migrations/0026_country_is_active.py
Normal file
18
apps/location/migrations/0026_country_is_active.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
|
|
|||
|
|
@ -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,14 +26,17 @@ 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,
|
||||
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.'))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
85
apps/main/migrations/0036_auto_20191115_0750.py
Normal file
85
apps/main/migrations/0036_auto_20191115_0750.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
]
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
20
apps/news/migrations/0035_news_views_count.py
Normal file
20
apps/news/migrations/0035_news_views_count.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
|
|
@ -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"""
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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() \
|
||||
|
||||
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."""
|
||||
|
|
|
|||
18
apps/notification/migrations/0002_subscriber_old_id.py
Normal file
18
apps/notification/migrations/0002_subscriber_old_id.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
|
|
@ -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)()
|
||||
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
@ -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(
|
||||
|
|
@ -45,6 +43,7 @@ class Command(BaseCommand):
|
|||
cursor.execute('''
|
||||
select
|
||||
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.add_product_tag()
|
||||
self.check_tag()
|
||||
self.add_product_tag()
|
||||
|
|
|
|||
29
apps/product/migrations/0013_auto_20191113_1512.py
Normal file
29
apps/product/migrations/0013_auto_20191113_1512.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
|
|
@ -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:
|
||||
|
|
@ -94,6 +79,13 @@ class ProductQuerySet(models.QuerySet):
|
|||
return self.select_related('product_type', 'establishment') \
|
||||
.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):
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -16,5 +16,4 @@ urlpatterns = [
|
|||
name='create-comment'),
|
||||
path('slug/<slug:slug>/comments/<int:comment_id>/', views.ProductCommentRUDView.as_view(),
|
||||
name='rud-comment'),
|
||||
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from .back import *
|
||||
from .common import *
|
||||
from .back import *
|
||||
from .mobile import *
|
||||
from .web import *
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,8 +68,8 @@ 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)\
|
||||
model='product') \
|
||||
.by_object_id(object_id=product.pk) \
|
||||
.order_by('-created')
|
||||
|
||||
|
||||
|
|
|
|||
27
apps/rating/migrations/0003_viewcount.py
Normal file
27
apps/rating/migrations/0003_viewcount.py
Normal 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')},
|
||||
},
|
||||
),
|
||||
]
|
||||
25
apps/rating/migrations/0004_auto_20191114_2041.py
Normal file
25
apps/rating/migrations/0004_auto_20191114_2041.py
Normal 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',
|
||||
),
|
||||
]
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
29
apps/rating/transfer_data.py
Normal file
29
apps/rating/transfer_data.py
Normal 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]
|
||||
}
|
||||
|
|
@ -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')
|
||||
|
|
|
|||
18
apps/review/migrations/0015_review_mark.py
Normal file
18
apps/review/migrations/0015_review_mark.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
17
apps/review/migrations/0016_remove_review_language.py
Normal file
17
apps/review/migrations/0016_remove_review_language.py
Normal 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',
|
||||
),
|
||||
]
|
||||
20
apps/review/migrations/0017_auto_20191115_0737.py
Normal file
20
apps/review/migrations/0017_auto_20191115_0737.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
|
|
@ -39,36 +39,49 @@ class Review(BaseAttributes, TranslatedFieldsMixin):
|
|||
(READY, _('Ready')),
|
||||
)
|
||||
|
||||
reviewer = models.ForeignKey('account.User',
|
||||
reviewer = models.ForeignKey(
|
||||
'account.User',
|
||||
related_name='reviews',
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name=_('Reviewer'))
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
]
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
116
apps/search_indexes/documents/product.py
Normal file
116
apps/search_indexes/documents/product.py
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
"""Product app documents."""
|
||||
from django.conf import settings
|
||||
from django_elasticsearch_dsl import Document, Index, fields
|
||||
from search_indexes.utils import OBJECT_FIELD_PROPERTIES
|
||||
from product import models
|
||||
|
||||
ProductIndex = Index(settings.ELASTICSEARCH_INDEX_NAMES.get(__name__, 'product'))
|
||||
ProductIndex.settings(number_of_shards=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()
|
||||
|
|
@ -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',
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
})
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
22
apps/tag/management/commands/add_cepage_tag.py
Normal file
22
apps/tag/management/commands/add_cepage_tag.py
Normal 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}'))
|
||||
|
|
@ -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}'))
|
||||
17
apps/tag/management/commands/fix_cepage_tag_categories.py
Normal file
17
apps/tag/management/commands/fix_cepage_tag_categories.py
Normal 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}"))
|
||||
|
|
@ -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'))
|
||||
18
apps/tag/migrations/0013_auto_20191113_0930.py
Normal file
18
apps/tag/migrations/0013_auto_20191113_0930.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
18
apps/tag/migrations/0014_tag_old_id_meta_product.py
Normal file
18
apps/tag/migrations/0014_tag_old_id_meta_product.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in New Issue
Block a user