Merge branch 'develop' into feature/fix-country-region-city-transfer
This commit is contained in:
commit
1b7c83899a
|
|
@ -1,3 +1,4 @@
|
||||||
FROM mdillon/postgis:10
|
FROM mdillon/postgis:10
|
||||||
RUN localedef -i ru_RU -c -f UTF-8 -A /usr/share/locale/locale.alias ru_RU.UTF-8
|
RUN localedef -i ru_RU -c -f UTF-8 -A /usr/share/locale/locale.alias ru_RU.UTF-8
|
||||||
ENV LANG ru_RU.utf8
|
ENV LANG ru_RU.utf8
|
||||||
|
COPY hstore.sql /docker-entrypoint-initdb.d
|
||||||
1
_dockerfiles/db/hstore.sql
Normal file
1
_dockerfiles/db/hstore.sql
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
create extension hstore;
|
||||||
20
apps/account/migrations/0024_role_establishment_subtype.py
Normal file
20
apps/account/migrations/0024_role_establishment_subtype.py
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-12-06 06:55
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0067_auto_20191122_1244'),
|
||||||
|
('account', '0023_auto_20191204_0916'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='role',
|
||||||
|
name='establishment_subtype',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='establishment.EstablishmentSubType', verbose_name='Establishment subtype'),
|
||||||
|
),
|
||||||
|
]
|
||||||
23
apps/account/migrations/0025_auto_20191210_0623.py
Normal file
23
apps/account/migrations/0025_auto_20191210_0623.py
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-12-10 06:23
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('account', '0024_role_establishment_subtype'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='city',
|
||||||
|
field=models.TextField(blank=True, default=None, null=True, verbose_name='User last visited from city'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='locale',
|
||||||
|
field=models.CharField(blank=True, default=None, max_length=10, null=True, verbose_name='User last used locale'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
"""Account models"""
|
"""Account models"""
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from tabnanny import verbose
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import AbstractUser, UserManager as BaseUserManager
|
from django.contrib.auth.models import AbstractUser, UserManager as BaseUserManager
|
||||||
|
|
@ -15,7 +16,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework.authtoken.models import Token
|
from rest_framework.authtoken.models import Token
|
||||||
|
|
||||||
from authorization.models import Application
|
from authorization.models import Application
|
||||||
from establishment.models import Establishment
|
from establishment.models import Establishment, EstablishmentSubType
|
||||||
from location.models import Country
|
from location.models import Country
|
||||||
from main.models import SiteSettings
|
from main.models import SiteSettings
|
||||||
from utils.models import GMTokenGenerator
|
from utils.models import GMTokenGenerator
|
||||||
|
|
@ -33,7 +34,7 @@ class Role(ProjectBaseMixin):
|
||||||
REVIEWER_MANGER = 6
|
REVIEWER_MANGER = 6
|
||||||
RESTAURANT_REVIEWER = 7
|
RESTAURANT_REVIEWER = 7
|
||||||
SALES_MAN = 8
|
SALES_MAN = 8
|
||||||
WINERY_REVIEWER = 9
|
WINERY_REVIEWER = 9 # Establishments subtype "winery"
|
||||||
SELLER = 10
|
SELLER = 10
|
||||||
|
|
||||||
ROLE_CHOICES = (
|
ROLE_CHOICES = (
|
||||||
|
|
@ -54,6 +55,9 @@ class Role(ProjectBaseMixin):
|
||||||
null=True, blank=True, on_delete=models.SET_NULL)
|
null=True, blank=True, on_delete=models.SET_NULL)
|
||||||
site = models.ForeignKey(SiteSettings, verbose_name=_('Site settings'),
|
site = models.ForeignKey(SiteSettings, verbose_name=_('Site settings'),
|
||||||
null=True, blank=True, on_delete=models.SET_NULL)
|
null=True, blank=True, on_delete=models.SET_NULL)
|
||||||
|
establishment_subtype = models.ForeignKey(EstablishmentSubType,
|
||||||
|
verbose_name=_('Establishment subtype'),
|
||||||
|
null=True, blank=True, on_delete=models.SET_NULL)
|
||||||
|
|
||||||
|
|
||||||
class UserManager(BaseUserManager):
|
class UserManager(BaseUserManager):
|
||||||
|
|
@ -103,6 +107,10 @@ class User(AbstractUser):
|
||||||
email_confirmed = models.BooleanField(_('email status'), default=False)
|
email_confirmed = models.BooleanField(_('email status'), default=False)
|
||||||
newsletter = models.NullBooleanField(default=True)
|
newsletter = models.NullBooleanField(default=True)
|
||||||
old_id = models.IntegerField(null=True, blank=True, default=None)
|
old_id = models.IntegerField(null=True, blank=True, default=None)
|
||||||
|
locale = models.CharField(max_length=10, blank=True, default=None, null=True,
|
||||||
|
verbose_name=_('User last used locale'))
|
||||||
|
city = models.TextField(default=None, blank=True, null=True,
|
||||||
|
verbose_name=_('User last visited from city'))
|
||||||
|
|
||||||
EMAIL_FIELD = 'email'
|
EMAIL_FIELD = 'email'
|
||||||
USERNAME_FIELD = 'username'
|
USERNAME_FIELD = 'username'
|
||||||
|
|
|
||||||
|
|
@ -33,11 +33,14 @@ class BackUserSerializer(serializers.ModelSerializer):
|
||||||
'email_confirmed',
|
'email_confirmed',
|
||||||
'newsletter',
|
'newsletter',
|
||||||
'roles',
|
'roles',
|
||||||
|
'password',
|
||||||
|
'city',
|
||||||
|
'locale',
|
||||||
)
|
)
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'password': {'write_only': True}
|
'password': {'write_only': True},
|
||||||
}
|
}
|
||||||
read_only_fields = ('old_password', 'last_login', 'date_joined')
|
read_only_fields = ('old_password', 'last_login', 'date_joined', 'city', 'locale')
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
user = super().create(validated_data)
|
user = super().create(validated_data)
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,6 @@ app_name = 'account'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('role/', views.RoleLstView.as_view(), name='role-list-create'),
|
path('role/', views.RoleLstView.as_view(), name='role-list-create'),
|
||||||
path('user-role/', views.UserRoleLstView.as_view(), name='user-role-list-create'),
|
path('user-role/', views.UserRoleLstView.as_view(), name='user-role-list-create'),
|
||||||
path('user/', views.UserLstView.as_view(), name='user-list-create'),
|
path('user/', views.UserLstView.as_view(), name='user-create-list'),
|
||||||
path('user/<int:id>/', views.UserRUDView.as_view(), name='user-rud'),
|
path('user/<int:id>/', views.UserRUDView.as_view(), name='user-rud'),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -14,3 +14,8 @@ class PageInline(admin.TabularInline):
|
||||||
class AdvertisementModelAdmin(admin.ModelAdmin):
|
class AdvertisementModelAdmin(admin.ModelAdmin):
|
||||||
"""Admin model for model Advertisement"""
|
"""Admin model for model Advertisement"""
|
||||||
inlines = (PageInline, )
|
inlines = (PageInline, )
|
||||||
|
list_display = ('id', '__str__', 'block_level',
|
||||||
|
'start', 'end', 'page_type')
|
||||||
|
list_filter = ('url', 'block_level', 'start', 'end', 'page_type',
|
||||||
|
'pages__source')
|
||||||
|
date_hierarchy = 'created'
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,10 @@ class AdvertisementQuerySet(models.QuerySet):
|
||||||
"""Filter Advertisement by page type."""
|
"""Filter Advertisement by page type."""
|
||||||
return self.filter(page_type__name=page_type)
|
return self.filter(page_type__name=page_type)
|
||||||
|
|
||||||
|
def by_country(self, code: str):
|
||||||
|
"""Filter Advertisement by country code."""
|
||||||
|
return self.filter(sites__country__code=code)
|
||||||
|
|
||||||
def by_locale(self, locale):
|
def by_locale(self, locale):
|
||||||
"""Filter by locale."""
|
"""Filter by locale."""
|
||||||
return self.filter(target_languages__locale=locale)
|
return self.filter(target_languages__locale=locale)
|
||||||
|
|
@ -67,11 +71,11 @@ class Advertisement(ProjectBaseMixin):
|
||||||
return super().delete(using, keep_parents)
|
return super().delete(using, keep_parents)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mobile_page(self):
|
def mobile_pages(self):
|
||||||
"""Return mobile page"""
|
"""Return mobile page"""
|
||||||
return self.pages.by_platform(Page.MOBILE).first()
|
return self.pages.by_platform(Page.MOBILE)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def web_page(self):
|
def web_pages(self):
|
||||||
"""Return web page"""
|
"""Return web page"""
|
||||||
return self.pages.by_platform(Page.WEB).first()
|
return self.pages.by_platform(Page.WEB)
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,14 @@
|
||||||
"""Serializers for back office app advertisements"""
|
"""Serializers for back office app advertisements"""
|
||||||
from main.serializers import PageBaseSerializer
|
from advertisement.serializers import AdvertisementBaseSerializer
|
||||||
|
from main.serializers import PageExtendedSerializer
|
||||||
|
|
||||||
|
|
||||||
class AdvertisementPageBaseSerializer(PageBaseSerializer):
|
class AdvertisementDetailSerializer(AdvertisementBaseSerializer):
|
||||||
"""Base serializer for linking page w/ advertisement."""
|
"""Advertisement serializer for back office."""
|
||||||
|
pages = PageExtendedSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
class Meta(PageBaseSerializer.Meta):
|
class Meta(AdvertisementBaseSerializer.Meta):
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
fields = AdvertisementBaseSerializer.Meta.fields + [
|
||||||
PageBaseSerializer.Meta.extra_kwargs.update({
|
'pages',
|
||||||
'advertisement': {'write_only': True},
|
]
|
||||||
'image_url': {'required': True},
|
|
||||||
'width': {'required': True},
|
|
||||||
'height': {'required': True},
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class AdvertisementPageListCreateSerializer(AdvertisementPageBaseSerializer):
|
|
||||||
"""Serializer for linking page w/ advertisement."""
|
|
||||||
|
|
||||||
def create(self, validated_data):
|
|
||||||
"""Overridden create method."""
|
|
||||||
|
|
||||||
validated_data['advertisement'] = self.context.get('view').get_object()
|
|
||||||
return super().create(validated_data)
|
|
||||||
|
|
|
||||||
|
|
@ -2,23 +2,20 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from advertisement import models
|
from advertisement import models
|
||||||
from translation.serializers import LanguageSerializer
|
|
||||||
from main.serializers import SiteShortSerializer, PageBaseSerializer
|
|
||||||
from translation.models import Language
|
|
||||||
from main.models import SiteSettings
|
from main.models import SiteSettings
|
||||||
|
from main.serializers import PageTypeBaseSerializer
|
||||||
|
from translation.models import Language
|
||||||
|
|
||||||
|
|
||||||
class AdvertisementBaseSerializer(serializers.ModelSerializer):
|
class AdvertisementBaseSerializer(serializers.ModelSerializer):
|
||||||
"""Base serializer for model Advertisement."""
|
"""Base serializer for model Advertisement."""
|
||||||
|
page_type_detail = PageTypeBaseSerializer(read_only=True,
|
||||||
languages = LanguageSerializer(many=True, read_only=True,
|
source='page_type')
|
||||||
source='target_languages')
|
|
||||||
target_languages = serializers.PrimaryKeyRelatedField(
|
target_languages = serializers.PrimaryKeyRelatedField(
|
||||||
queryset=Language.objects.all(),
|
queryset=Language.objects.all(),
|
||||||
many=True,
|
many=True,
|
||||||
write_only=True
|
write_only=True
|
||||||
)
|
)
|
||||||
sites = SiteShortSerializer(many=True, read_only=True)
|
|
||||||
target_sites = serializers.PrimaryKeyRelatedField(
|
target_sites = serializers.PrimaryKeyRelatedField(
|
||||||
queryset=SiteSettings.objects.all(),
|
queryset=SiteSettings.objects.all(),
|
||||||
many=True,
|
many=True,
|
||||||
|
|
@ -33,22 +30,21 @@ class AdvertisementBaseSerializer(serializers.ModelSerializer):
|
||||||
'uuid',
|
'uuid',
|
||||||
'url',
|
'url',
|
||||||
'block_level',
|
'block_level',
|
||||||
'languages',
|
|
||||||
'target_languages',
|
'target_languages',
|
||||||
'sites',
|
|
||||||
'target_sites',
|
'target_sites',
|
||||||
'start',
|
'start',
|
||||||
'end',
|
'end',
|
||||||
|
'page_type',
|
||||||
|
'page_type_detail',
|
||||||
]
|
]
|
||||||
|
extra_kwargs = {
|
||||||
|
'page_type': {'required': True, 'write_only': True}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class AdvertisementPageTypeCommonListSerializer(AdvertisementBaseSerializer):
|
class AdvertisementSerializer(AdvertisementBaseSerializer):
|
||||||
"""Serializer for AdvertisementPageTypeCommonView."""
|
"""Serializer for model Advertisement."""
|
||||||
|
|
||||||
page = PageBaseSerializer(source='common_page', read_only=True)
|
|
||||||
|
|
||||||
class Meta(AdvertisementBaseSerializer.Meta):
|
class Meta(AdvertisementBaseSerializer.Meta):
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
fields = AdvertisementBaseSerializer.Meta.fields + [
|
fields = AdvertisementBaseSerializer.Meta.fields.copy()
|
||||||
'page',
|
fields.pop(fields.index('page_type_detail'))
|
||||||
]
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
"""Serializers for mobile app advertisements"""
|
"""Serializers for mobile app advertisements"""
|
||||||
from advertisement.serializers import AdvertisementBaseSerializer
|
from advertisement.serializers import AdvertisementSerializer
|
||||||
from main.serializers import PageBaseSerializer
|
from main.serializers import PageBaseSerializer
|
||||||
|
|
||||||
|
|
||||||
class AdvertisementPageTypeMobileListSerializer(AdvertisementBaseSerializer):
|
class AdvertisementPageTypeMobileListSerializer(AdvertisementSerializer):
|
||||||
"""Serializer for AdvertisementPageTypeMobileView."""
|
"""Serializer for AdvertisementPageTypeMobileView."""
|
||||||
|
|
||||||
page = PageBaseSerializer(source='mobile_page', read_only=True)
|
pages = PageBaseSerializer(many=True, source='mobile_pages', read_only=True)
|
||||||
|
|
||||||
class Meta(AdvertisementBaseSerializer.Meta):
|
class Meta(AdvertisementSerializer.Meta):
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
fields = AdvertisementBaseSerializer.Meta.fields + [
|
fields = AdvertisementSerializer.Meta.fields + [
|
||||||
'page',
|
'pages',
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
"""Serializers for web app advertisements"""
|
"""Serializers for web app advertisements"""
|
||||||
from advertisement.serializers import AdvertisementBaseSerializer
|
from advertisement.serializers import AdvertisementSerializer
|
||||||
from main.serializers import PageBaseSerializer
|
from main.serializers import PageBaseSerializer
|
||||||
|
|
||||||
|
|
||||||
class AdvertisementPageTypeWebListSerializer(AdvertisementBaseSerializer):
|
class AdvertisementPageTypeWebListSerializer(AdvertisementSerializer):
|
||||||
"""Serializer for AdvertisementPageTypeWebView."""
|
"""Serializer for AdvertisementPageTypeWebView."""
|
||||||
|
|
||||||
page = PageBaseSerializer(source='web_page', read_only=True)
|
pages = PageBaseSerializer(many=True, source='web_pages', read_only=True)
|
||||||
|
|
||||||
class Meta(AdvertisementBaseSerializer.Meta):
|
class Meta(AdvertisementSerializer.Meta):
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
fields = AdvertisementBaseSerializer.Meta.fields + [
|
fields = AdvertisementSerializer.Meta.fields + [
|
||||||
'page',
|
'pages',
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,10 @@ app_name = 'advertisements'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.AdvertisementListCreateView.as_view(), name='list-create'),
|
path('', views.AdvertisementListCreateView.as_view(), name='list-create'),
|
||||||
path('<int:pk>/', views.AdvertisementRUDView.as_view(), name='rud'),
|
path('<int:pk>/', views.AdvertisementRUDView.as_view(), name='rud'),
|
||||||
path('<int:pk>/pages/', views.AdvertisementPageListCreateView.as_view(),
|
path('<int:pk>/pages/', views.AdvertisementPageCreateView.as_view(),
|
||||||
name='page-list-create'),
|
name='ad-page-create'),
|
||||||
path('<int:ad_pk>/pages/<int:page_pk>/', views.AdvertisementPageRUDView.as_view(),
|
path('<int:ad_pk>/pages/<int:page_pk>/', views.AdvertisementPageUDView.as_view(),
|
||||||
name='page-rud')
|
name='ad-page-update-destroy')
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns += common_urlpatterns
|
urlpatterns += common_urlpatterns
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
"""Back office views for app advertisement"""
|
"""Back office views for app advertisement"""
|
||||||
from rest_framework import generics
|
from rest_framework import generics, status
|
||||||
|
from rest_framework.response import Response
|
||||||
from rest_framework import permissions
|
from rest_framework import permissions
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
|
from main.serializers import PageExtendedSerializer
|
||||||
from advertisement.models import Advertisement
|
from advertisement.models import Advertisement
|
||||||
from rest_framework.response import Response
|
|
||||||
from rest_framework import status
|
|
||||||
from advertisement.serializers import (AdvertisementBaseSerializer,
|
from advertisement.serializers import (AdvertisementBaseSerializer,
|
||||||
AdvertisementPageBaseSerializer,
|
AdvertisementDetailSerializer)
|
||||||
AdvertisementPageListCreateSerializer)
|
|
||||||
|
|
||||||
|
|
||||||
class AdvertisementBackOfficeViewMixin(generics.GenericAPIView):
|
class AdvertisementBackOfficeViewMixin(generics.GenericAPIView):
|
||||||
"""Base back office advertisement view."""
|
"""Base back office advertisement view."""
|
||||||
|
|
||||||
|
pagination_class = None
|
||||||
permission_classes = (permissions.IsAuthenticated, )
|
permission_classes = (permissions.IsAuthenticated, )
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
|
@ -31,14 +31,14 @@ class AdvertisementRUDView(AdvertisementBackOfficeViewMixin,
|
||||||
generics.RetrieveUpdateDestroyAPIView):
|
generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Retrieve|Update|Destroy advertisement page view."""
|
"""Retrieve|Update|Destroy advertisement page view."""
|
||||||
|
|
||||||
serializer_class = AdvertisementBaseSerializer
|
serializer_class = AdvertisementDetailSerializer
|
||||||
|
|
||||||
|
|
||||||
class AdvertisementPageListCreateView(AdvertisementBackOfficeViewMixin,
|
class AdvertisementPageCreateView(AdvertisementBackOfficeViewMixin,
|
||||||
generics.ListCreateAPIView):
|
generics.CreateAPIView):
|
||||||
"""Retrieve|Update|Destroy advertisement page view."""
|
"""Create advertisement page view."""
|
||||||
|
|
||||||
serializer_class = AdvertisementPageListCreateSerializer
|
serializer_class = PageExtendedSerializer
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
"""Returns the object the view is displaying."""
|
"""Returns the object the view is displaying."""
|
||||||
|
|
@ -56,12 +56,19 @@ class AdvertisementPageListCreateView(AdvertisementBackOfficeViewMixin,
|
||||||
"""Overridden get_queryset method."""
|
"""Overridden get_queryset method."""
|
||||||
return self.get_object().pages.all()
|
return self.get_object().pages.all()
|
||||||
|
|
||||||
|
def create(self, request, *args, **kwargs):
|
||||||
|
"""Overridden create method."""
|
||||||
|
request.data.update({'advertisement': self.get_object().pk})
|
||||||
|
super().create(request, *args, **kwargs)
|
||||||
|
return Response(status=status.HTTP_200_OK)
|
||||||
|
|
||||||
class AdvertisementPageRUDView(AdvertisementBackOfficeViewMixin,
|
|
||||||
generics.RetrieveUpdateDestroyAPIView):
|
|
||||||
"""Create|Retrieve|Update|Destroy advertisement page view."""
|
|
||||||
|
|
||||||
serializer_class = AdvertisementPageBaseSerializer
|
class AdvertisementPageUDView(AdvertisementBackOfficeViewMixin,
|
||||||
|
generics.UpdateAPIView,
|
||||||
|
generics.DestroyAPIView):
|
||||||
|
"""Update|Destroy advertisement page view."""
|
||||||
|
|
||||||
|
serializer_class = PageExtendedSerializer
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
"""Returns the object the view is displaying."""
|
"""Returns the object the view is displaying."""
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,7 @@ from rest_framework import generics
|
||||||
from rest_framework import permissions
|
from rest_framework import permissions
|
||||||
|
|
||||||
from advertisement.models import Advertisement
|
from advertisement.models import Advertisement
|
||||||
from advertisement.serializers import AdvertisementBaseSerializer, \
|
from advertisement.serializers import AdvertisementBaseSerializer
|
||||||
AdvertisementPageTypeCommonListSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class AdvertisementBaseView(generics.GenericAPIView):
|
class AdvertisementBaseView(generics.GenericAPIView):
|
||||||
|
|
@ -16,8 +15,7 @@ class AdvertisementBaseView(generics.GenericAPIView):
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Overridden get queryset method."""
|
"""Overridden get queryset method."""
|
||||||
return Advertisement.objects.with_base_related() \
|
return Advertisement.objects.with_base_related()
|
||||||
.by_locale(self.request.locale)
|
|
||||||
|
|
||||||
|
|
||||||
class AdvertisementPageTypeListView(AdvertisementBaseView, generics.ListAPIView):
|
class AdvertisementPageTypeListView(AdvertisementBaseView, generics.ListAPIView):
|
||||||
|
|
@ -28,5 +26,8 @@ class AdvertisementPageTypeListView(AdvertisementBaseView, generics.ListAPIView)
|
||||||
product_type = self.kwargs.get('page_type')
|
product_type = self.kwargs.get('page_type')
|
||||||
qs = super(AdvertisementPageTypeListView, self).get_queryset()
|
qs = super(AdvertisementPageTypeListView, self).get_queryset()
|
||||||
if product_type:
|
if product_type:
|
||||||
return qs.by_page_type(product_type)
|
return qs.by_page_type(product_type) \
|
||||||
|
.by_country(self.request.country_code) \
|
||||||
|
.by_locale(self.request.locale) \
|
||||||
|
.distinct('id')
|
||||||
return qs.none()
|
return qs.none()
|
||||||
|
|
|
||||||
|
|
@ -7,3 +7,4 @@ class AdvertisementPageTypeWebListView(AdvertisementPageTypeListView):
|
||||||
"""Advertisement mobile list view."""
|
"""Advertisement mobile list view."""
|
||||||
|
|
||||||
serializer_class = AdvertisementPageTypeWebListSerializer
|
serializer_class = AdvertisementPageTypeWebListSerializer
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,8 @@ from collection.models import Collection
|
||||||
from location.models import Address
|
from location.models import Address
|
||||||
from location.models import WineOriginAddressMixin
|
from location.models import WineOriginAddressMixin
|
||||||
from main.models import Award, Currency
|
from main.models import Award, Currency
|
||||||
from tag.models import Tag
|
|
||||||
from review.models import Review
|
from review.models import Review
|
||||||
|
from tag.models import Tag
|
||||||
from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin,
|
from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin,
|
||||||
TranslatedFieldsMixin, BaseAttributes, GalleryModelMixin,
|
TranslatedFieldsMixin, BaseAttributes, GalleryModelMixin,
|
||||||
IntermediateGalleryModelMixin, HasTagsMixin,
|
IntermediateGalleryModelMixin, HasTagsMixin,
|
||||||
|
|
@ -209,23 +209,34 @@ class EstablishmentQuerySet(models.QuerySet):
|
||||||
"""
|
"""
|
||||||
return self.annotate(mark_similarity=ExpressionWrapper(
|
return self.annotate(mark_similarity=ExpressionWrapper(
|
||||||
mark - F('intermediate_public_mark'),
|
mark - F('intermediate_public_mark'),
|
||||||
output_field=models.FloatField()
|
output_field=models.FloatField(default=0)
|
||||||
))
|
))
|
||||||
|
|
||||||
def similar(self, establishment_slug: str):
|
def similar_base(self, establishment):
|
||||||
|
|
||||||
|
filters = {
|
||||||
|
'reviews__status': Review.READY,
|
||||||
|
'establishment_type': establishment.establishment_type,
|
||||||
|
}
|
||||||
|
if establishment.establishment_subtypes.exists():
|
||||||
|
filters.update({'establishment_subtypes__in': establishment.establishment_subtypes.all()})
|
||||||
|
return self.exclude(id=establishment.id) \
|
||||||
|
.filter(**filters) \
|
||||||
|
.annotate_distance(point=establishment.location)
|
||||||
|
|
||||||
|
def similar_restaurants(self, slug):
|
||||||
"""
|
"""
|
||||||
Return QuerySet with objects that similar to Establishment.
|
Return QuerySet with objects that similar to Restaurant.
|
||||||
:param establishment_slug: str Establishment slug
|
:param restaurant_slug: str Establishment slug
|
||||||
"""
|
"""
|
||||||
establishment_qs = self.filter(slug=establishment_slug,
|
restaurant_qs = self.filter(slug=slug,
|
||||||
public_mark__isnull=False)
|
public_mark__isnull=False)
|
||||||
if establishment_qs.exists():
|
if restaurant_qs.exists():
|
||||||
establishment = establishment_qs.first()
|
establishment = restaurant_qs.first()
|
||||||
subquery_filter_by_distance = Subquery(
|
subquery_filter_by_distance = Subquery(
|
||||||
self.exclude(slug=establishment_slug)
|
self.similar_base(establishment)
|
||||||
.filter(image_url__isnull=False, public_mark__gte=10)
|
.filter(public_mark__gte=10,
|
||||||
.has_published_reviews()
|
establishment_gallery__is_main=True)
|
||||||
.annotate_distance(point=establishment.location)
|
|
||||||
.order_by('distance')[:settings.LIMITING_QUERY_OBJECTS]
|
.order_by('distance')[:settings.LIMITING_QUERY_OBJECTS]
|
||||||
.values('id')
|
.values('id')
|
||||||
)
|
)
|
||||||
|
|
@ -234,6 +245,36 @@ class EstablishmentQuerySet(models.QuerySet):
|
||||||
.annotate_mark_similarity(mark=establishment.public_mark) \
|
.annotate_mark_similarity(mark=establishment.public_mark) \
|
||||||
.order_by('mark_similarity') \
|
.order_by('mark_similarity') \
|
||||||
.distinct('mark_similarity', 'id')
|
.distinct('mark_similarity', 'id')
|
||||||
|
|
||||||
|
def by_wine_region(self, wine_region):
|
||||||
|
"""
|
||||||
|
Return filtered QuerySet by wine region in wine origin.
|
||||||
|
:param wine_region: wine region.
|
||||||
|
"""
|
||||||
|
return self.filter(wine_origin__wine_region=wine_region).distinct()
|
||||||
|
|
||||||
|
def by_wine_sub_region(self, wine_sub_region):
|
||||||
|
"""
|
||||||
|
Return filtered QuerySet by wine region in wine origin.
|
||||||
|
:param wine_sub_region: wine sub region.
|
||||||
|
"""
|
||||||
|
return self.filter(wine_origin__wine_sub_region=wine_sub_region).distinct()
|
||||||
|
|
||||||
|
def similar_wineries(self, slug: str):
|
||||||
|
"""
|
||||||
|
Return QuerySet with objects that similar to Winery.
|
||||||
|
:param establishment_slug: str Establishment slug
|
||||||
|
"""
|
||||||
|
winery_qs = self.filter(slug=slug)
|
||||||
|
if winery_qs.exists():
|
||||||
|
winery = winery_qs.first()
|
||||||
|
return self.similar_base(winery) \
|
||||||
|
.order_by(F('wine_origins__wine_region').asc(),
|
||||||
|
F('wine_origins__wine_sub_region').asc()) \
|
||||||
|
.annotate_distance(point=winery.location) \
|
||||||
|
.order_by('distance') \
|
||||||
|
.distinct('distance', 'wine_origins__wine_region',
|
||||||
|
'wine_origins__wine_sub_region', 'id')
|
||||||
else:
|
else:
|
||||||
return self.none()
|
return self.none()
|
||||||
|
|
||||||
|
|
@ -457,15 +498,9 @@ class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin,
|
||||||
def visible_tags(self):
|
def visible_tags(self):
|
||||||
return super().visible_tags \
|
return super().visible_tags \
|
||||||
.exclude(category__index_name__in=['guide', 'collection', 'purchased_item',
|
.exclude(category__index_name__in=['guide', 'collection', 'purchased_item',
|
||||||
'business_tag', 'business_tags_de']) \
|
'business_tag', 'business_tags_de', 'tag'])
|
||||||
.exclude(value__in=['rss', 'rss_selection'])
|
|
||||||
# todo: recalculate toque_number
|
# todo: recalculate toque_number
|
||||||
|
|
||||||
@property
|
|
||||||
def visible_tags_detail(self):
|
|
||||||
"""Removes some tags from detail Establishment representation"""
|
|
||||||
return self.visible_tags.exclude(category__index_name__in=['tag'])
|
|
||||||
|
|
||||||
def recalculate_toque_number(self):
|
def recalculate_toque_number(self):
|
||||||
toque_number = 0
|
toque_number = 0
|
||||||
if self.address and self.public_mark:
|
if self.address and self.public_mark:
|
||||||
|
|
|
||||||
|
|
@ -450,14 +450,18 @@ class EstablishmentSimilarSerializer(EstablishmentBaseSerializer):
|
||||||
|
|
||||||
address = AddressDetailSerializer(read_only=True)
|
address = AddressDetailSerializer(read_only=True)
|
||||||
schedule = ScheduleRUDSerializer(many=True, allow_null=True)
|
schedule = ScheduleRUDSerializer(many=True, allow_null=True)
|
||||||
establishment_type = EstablishmentTypeGeoSerializer()
|
type = EstablishmentTypeGeoSerializer(source='establishment_type')
|
||||||
artisan_category = TagBaseSerializer(many=True, allow_null=True)
|
artisan_category = TagBaseSerializer(many=True, allow_null=True, read_only=True)
|
||||||
|
restaurant_category = TagBaseSerializer(many=True, allow_null=True, read_only=True)
|
||||||
|
restaurant_cuisine = TagBaseSerializer(many=True, allow_null=True, read_only=True)
|
||||||
|
|
||||||
class Meta(EstablishmentBaseSerializer.Meta):
|
class Meta(EstablishmentBaseSerializer.Meta):
|
||||||
fields = EstablishmentBaseSerializer.Meta.fields + [
|
fields = EstablishmentBaseSerializer.Meta.fields + [
|
||||||
'schedule',
|
'schedule',
|
||||||
'establishment_type',
|
'type',
|
||||||
'artisan_category',
|
'artisan_category',
|
||||||
|
'restaurant_category',
|
||||||
|
'restaurant_cuisine',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ from account.models import User
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from http.cookies import SimpleCookie
|
from http.cookies import SimpleCookie
|
||||||
from main.models import Currency
|
from main.models import Currency
|
||||||
from establishment.models import Establishment, EstablishmentType, Menu, SocialChoice, SocialNetwork
|
from establishment.models import Establishment, EstablishmentType, EstablishmentSubType,\
|
||||||
|
Menu, SocialChoice, SocialNetwork
|
||||||
# Create your tests here.
|
# Create your tests here.
|
||||||
from translation.models import Language
|
from translation.models import Language
|
||||||
from account.models import Role, UserRole
|
from account.models import Role, UserRole
|
||||||
|
|
@ -87,7 +88,7 @@ class BaseTestCase(APITestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentBTests(BaseTestCase):
|
class EstablishmentBackTests(BaseTestCase):
|
||||||
def test_establishment_CRUD(self):
|
def test_establishment_CRUD(self):
|
||||||
params = {'page': 1, 'page_size': 1, }
|
params = {'page': 1, 'page_size': 1, }
|
||||||
response = self.client.get('/api/back/establishments/', params, format='json')
|
response = self.client.get('/api/back/establishments/', params, format='json')
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ urlpatterns = [
|
||||||
path('', views.EstablishmentListView.as_view(), name='list'),
|
path('', views.EstablishmentListView.as_view(), name='list'),
|
||||||
path('recent-reviews/', views.EstablishmentRecentReviewListView.as_view(),
|
path('recent-reviews/', views.EstablishmentRecentReviewListView.as_view(),
|
||||||
name='recent-reviews'),
|
name='recent-reviews'),
|
||||||
path('slug/<slug:slug>/similar/', views.EstablishmentSimilarListView.as_view(), name='similar'),
|
|
||||||
path('slug/<slug:slug>/comments/', views.EstablishmentCommentListView.as_view(), name='list-comments'),
|
path('slug/<slug:slug>/comments/', views.EstablishmentCommentListView.as_view(), name='list-comments'),
|
||||||
path('slug/<slug:slug>/comments/create/', views.EstablishmentCommentCreateView.as_view(),
|
path('slug/<slug:slug>/comments/create/', views.EstablishmentCommentCreateView.as_view(),
|
||||||
name='create-comment'),
|
name='create-comment'),
|
||||||
|
|
@ -17,4 +16,11 @@ urlpatterns = [
|
||||||
name='rud-comment'),
|
name='rud-comment'),
|
||||||
path('slug/<slug:slug>/favorites/', views.EstablishmentFavoritesCreateDestroyView.as_view(),
|
path('slug/<slug:slug>/favorites/', views.EstablishmentFavoritesCreateDestroyView.as_view(),
|
||||||
name='create-destroy-favorites'),
|
name='create-destroy-favorites'),
|
||||||
|
|
||||||
|
# similar establishments
|
||||||
|
path('slug/<slug:slug>/similar/', views.RestaurantSimilarListView.as_view(),
|
||||||
|
name='similar-restaurants'),
|
||||||
|
path('slug/<slug:slug>/similar/wineries/', views.WinerySimilarListView.as_view(),
|
||||||
|
name='similar-restaurants'),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,9 @@ from django.http import Http404, HttpResponse
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from rest_framework import generics, permissions, status
|
from rest_framework import generics, permissions, status
|
||||||
|
|
||||||
from utils.permissions import IsCountryAdmin, IsEstablishmentManager
|
|
||||||
from establishment import filters, models, serializers
|
from establishment import filters, models, serializers
|
||||||
from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer
|
from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer
|
||||||
from utils.permissions import IsCountryAdmin, IsEstablishmentManager
|
from utils.permissions import IsCountryAdmin, IsEstablishmentManager, IsWineryReviewer
|
||||||
from utils.views import CreateDestroyGalleryViewMixin
|
from utils.views import CreateDestroyGalleryViewMixin
|
||||||
from timetable.models import Timetable
|
from timetable.models import Timetable
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
@ -25,7 +24,8 @@ class EstablishmentListCreateView(EstablishmentMixinViews, generics.ListCreateAP
|
||||||
"""Establishment list/create view."""
|
"""Establishment list/create view."""
|
||||||
|
|
||||||
filter_class = filters.EstablishmentFilter
|
filter_class = filters.EstablishmentFilter
|
||||||
permission_classes = [IsCountryAdmin | IsEstablishmentManager]
|
|
||||||
|
permission_classes = [IsWineryReviewer | IsCountryAdmin | IsEstablishmentManager]
|
||||||
queryset = models.Establishment.objects.all()
|
queryset = models.Establishment.objects.all()
|
||||||
serializer_class = serializers.EstablishmentListCreateSerializer
|
serializer_class = serializers.EstablishmentListCreateSerializer
|
||||||
|
|
||||||
|
|
@ -34,14 +34,14 @@ class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
lookup_field = 'slug'
|
lookup_field = 'slug'
|
||||||
queryset = models.Establishment.objects.all()
|
queryset = models.Establishment.objects.all()
|
||||||
serializer_class = serializers.EstablishmentRUDSerializer
|
serializer_class = serializers.EstablishmentRUDSerializer
|
||||||
permission_classes = [IsCountryAdmin | IsEstablishmentManager]
|
permission_classes = [IsWineryReviewer | IsCountryAdmin | IsEstablishmentManager]
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView):
|
class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Establishment schedule RUD view"""
|
"""Establishment schedule RUD view"""
|
||||||
lookup_field = 'slug'
|
lookup_field = 'slug'
|
||||||
serializer_class = ScheduleRUDSerializer
|
serializer_class = ScheduleRUDSerializer
|
||||||
permission_classes = [IsEstablishmentManager]
|
permission_classes = [IsWineryReviewer |IsEstablishmentManager]
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -67,21 +67,21 @@ class EstablishmentScheduleCreateView(generics.CreateAPIView):
|
||||||
lookup_field = 'slug'
|
lookup_field = 'slug'
|
||||||
serializer_class = ScheduleCreateSerializer
|
serializer_class = ScheduleCreateSerializer
|
||||||
queryset = Timetable.objects.all()
|
queryset = Timetable.objects.all()
|
||||||
permission_classes = [IsEstablishmentManager]
|
permission_classes = [IsWineryReviewer | IsEstablishmentManager]
|
||||||
|
|
||||||
|
|
||||||
class MenuListCreateView(generics.ListCreateAPIView):
|
class MenuListCreateView(generics.ListCreateAPIView):
|
||||||
"""Menu list create view."""
|
"""Menu list create view."""
|
||||||
serializer_class = serializers.MenuSerializers
|
serializer_class = serializers.MenuSerializers
|
||||||
queryset = models.Menu.objects.all()
|
queryset = models.Menu.objects.all()
|
||||||
permission_classes = [IsEstablishmentManager]
|
permission_classes = [IsWineryReviewer | IsEstablishmentManager]
|
||||||
|
|
||||||
|
|
||||||
class MenuRUDView(generics.RetrieveUpdateDestroyAPIView):
|
class MenuRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Menu RUD view."""
|
"""Menu RUD view."""
|
||||||
serializer_class = serializers.MenuRUDSerializers
|
serializer_class = serializers.MenuRUDSerializers
|
||||||
queryset = models.Menu.objects.all()
|
queryset = models.Menu.objects.all()
|
||||||
permission_classes = [IsEstablishmentManager]
|
permission_classes = [IsWineryReviewer | IsEstablishmentManager]
|
||||||
|
|
||||||
|
|
||||||
class SocialChoiceListCreateView(generics.ListCreateAPIView):
|
class SocialChoiceListCreateView(generics.ListCreateAPIView):
|
||||||
|
|
@ -119,14 +119,14 @@ class PlateListCreateView(generics.ListCreateAPIView):
|
||||||
serializer_class = serializers.PlatesSerializers
|
serializer_class = serializers.PlatesSerializers
|
||||||
queryset = models.Plate.objects.all()
|
queryset = models.Plate.objects.all()
|
||||||
pagination_class = None
|
pagination_class = None
|
||||||
permission_classes = [IsEstablishmentManager]
|
permission_classes = [IsWineryReviewer | IsEstablishmentManager]
|
||||||
|
|
||||||
|
|
||||||
class PlateRUDView(generics.RetrieveUpdateDestroyAPIView):
|
class PlateRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Plate RUD view."""
|
"""Plate RUD view."""
|
||||||
serializer_class = serializers.PlatesSerializers
|
serializer_class = serializers.PlatesSerializers
|
||||||
queryset = models.Plate.objects.all()
|
queryset = models.Plate.objects.all()
|
||||||
permission_classes = [IsEstablishmentManager]
|
permission_classes = [IsWineryReviewer | IsEstablishmentManager]
|
||||||
|
|
||||||
|
|
||||||
class PhonesListCreateView(generics.ListCreateAPIView):
|
class PhonesListCreateView(generics.ListCreateAPIView):
|
||||||
|
|
@ -134,14 +134,14 @@ class PhonesListCreateView(generics.ListCreateAPIView):
|
||||||
serializer_class = serializers.ContactPhoneBackSerializers
|
serializer_class = serializers.ContactPhoneBackSerializers
|
||||||
queryset = models.ContactPhone.objects.all()
|
queryset = models.ContactPhone.objects.all()
|
||||||
pagination_class = None
|
pagination_class = None
|
||||||
permission_classes = [IsEstablishmentManager]
|
permission_classes = [IsWineryReviewer | IsEstablishmentManager]
|
||||||
|
|
||||||
|
|
||||||
class PhonesRUDView(generics.RetrieveUpdateDestroyAPIView):
|
class PhonesRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Phones RUD view."""
|
"""Phones RUD view."""
|
||||||
serializer_class = serializers.ContactPhoneBackSerializers
|
serializer_class = serializers.ContactPhoneBackSerializers
|
||||||
queryset = models.ContactPhone.objects.all()
|
queryset = models.ContactPhone.objects.all()
|
||||||
permission_classes = [IsEstablishmentManager]
|
permission_classes = [IsWineryReviewer | IsEstablishmentManager]
|
||||||
|
|
||||||
|
|
||||||
class EmailListCreateView(generics.ListCreateAPIView):
|
class EmailListCreateView(generics.ListCreateAPIView):
|
||||||
|
|
@ -149,14 +149,14 @@ class EmailListCreateView(generics.ListCreateAPIView):
|
||||||
serializer_class = serializers.ContactEmailBackSerializers
|
serializer_class = serializers.ContactEmailBackSerializers
|
||||||
queryset = models.ContactEmail.objects.all()
|
queryset = models.ContactEmail.objects.all()
|
||||||
pagination_class = None
|
pagination_class = None
|
||||||
permission_classes = [IsEstablishmentManager]
|
permission_classes = [IsWineryReviewer | IsEstablishmentManager]
|
||||||
|
|
||||||
|
|
||||||
class EmailRUDView(generics.RetrieveUpdateDestroyAPIView):
|
class EmailRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Email RUD view."""
|
"""Email RUD view."""
|
||||||
serializer_class = serializers.ContactEmailBackSerializers
|
serializer_class = serializers.ContactEmailBackSerializers
|
||||||
queryset = models.ContactEmail.objects.all()
|
queryset = models.ContactEmail.objects.all()
|
||||||
permission_classes = [IsEstablishmentManager]
|
permission_classes = [IsWineryReviewer | IsEstablishmentManager]
|
||||||
|
|
||||||
|
|
||||||
class EmployeeListCreateView(generics.ListCreateAPIView):
|
class EmployeeListCreateView(generics.ListCreateAPIView):
|
||||||
|
|
|
||||||
|
|
@ -77,16 +77,28 @@ class EstablishmentRecentReviewListView(EstablishmentListView):
|
||||||
return qs.last_reviewed(point=point)
|
return qs.last_reviewed(point=point)
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentSimilarListView(EstablishmentListView):
|
class EstablishmentSimilarList(EstablishmentListView):
|
||||||
"""Resource for getting a list of establishments."""
|
"""Resource for getting a list of similar establishments."""
|
||||||
|
|
||||||
serializer_class = serializers.EstablishmentSimilarSerializer
|
serializer_class = serializers.EstablishmentSimilarSerializer
|
||||||
pagination_class = EstablishmentPortionPagination
|
pagination_class = EstablishmentPortionPagination
|
||||||
|
|
||||||
|
|
||||||
|
class RestaurantSimilarListView(EstablishmentSimilarList):
|
||||||
|
"""Resource for getting a list of similar restaurants."""
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Override get_queryset method"""
|
"""Override get_queryset method"""
|
||||||
qs = super().get_queryset()
|
return EstablishmentMixinView.get_queryset(self) \
|
||||||
return qs.similar(establishment_slug=self.kwargs.get('slug'))
|
.similar_restaurants(slug=self.kwargs.get('slug'))
|
||||||
|
|
||||||
|
|
||||||
|
class WinerySimilarListView(EstablishmentSimilarList):
|
||||||
|
"""Resource for getting a list of similar wineries."""
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
"""Override get_queryset method"""
|
||||||
|
return EstablishmentMixinView.get_queryset(self) \
|
||||||
|
.similar_wineries(slug=self.kwargs.get('slug'))
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentTypeListView(generics.ListAPIView):
|
class EstablishmentTypeListView(generics.ListAPIView):
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,9 @@ class BaseTestCase(APITestCase):
|
||||||
start=datetime.fromisoformat("2020-12-03 12:00:00"),
|
start=datetime.fromisoformat("2020-12-03 12:00:00"),
|
||||||
end=datetime.fromisoformat("2020-12-03 12:00:00"),
|
end=datetime.fromisoformat("2020-12-03 12:00:00"),
|
||||||
state=News.PUBLISHED,
|
state=News.PUBLISHED,
|
||||||
slug='test-news'
|
slugs={'en-GB': 'test-news'}
|
||||||
)
|
)
|
||||||
|
self.slug = next(iter(self.test_news.slugs.values()))
|
||||||
|
|
||||||
self.test_content_type = ContentType.objects.get(
|
self.test_content_type = ContentType.objects.get(
|
||||||
app_label="news", model="news")
|
app_label="news", model="news")
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,8 @@ class FavoritesEstablishmentListView(generics.ListAPIView):
|
||||||
"""Override get_queryset method"""
|
"""Override get_queryset method"""
|
||||||
return Establishment.objects.filter(favorites__user=self.request.user) \
|
return Establishment.objects.filter(favorites__user=self.request.user) \
|
||||||
.order_by('-favorites').with_base_related() \
|
.order_by('-favorites').with_base_related() \
|
||||||
|
.with_certain_tag_category_related('category', 'restaurant_category') \
|
||||||
|
.with_certain_tag_category_related('cuisine', 'restaurant_cuisine') \
|
||||||
.with_certain_tag_category_related('shop_category', 'artisan_category')
|
.with_certain_tag_category_related('shop_category', 'artisan_category')
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
24
apps/location/filters.py
Normal file
24
apps/location/filters.py
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
from django.core.validators import EMPTY_VALUES
|
||||||
|
from django_filters import rest_framework as filters
|
||||||
|
|
||||||
|
from location import models
|
||||||
|
|
||||||
|
|
||||||
|
class CityBackFilter(filters.FilterSet):
|
||||||
|
"""Employee filter set."""
|
||||||
|
|
||||||
|
search = filters.CharFilter(method='search_by_name')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
|
||||||
|
model = models.City
|
||||||
|
fields = (
|
||||||
|
'search',
|
||||||
|
)
|
||||||
|
|
||||||
|
def search_by_name(self, queryset, name, value):
|
||||||
|
"""Search by name or last name."""
|
||||||
|
if value not in EMPTY_VALUES:
|
||||||
|
return queryset.search_by_name(value)
|
||||||
|
return queryset
|
||||||
|
|
@ -5,6 +5,9 @@ from django.db.models.signals import post_save
|
||||||
from django.db.transaction import on_commit
|
from django.db.transaction import on_commit
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from functools import reduce
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
from django.contrib.postgres.fields import ArrayField
|
from django.contrib.postgres.fields import ArrayField
|
||||||
|
|
||||||
|
|
@ -97,6 +100,18 @@ class Region(models.Model):
|
||||||
class CityQuerySet(models.QuerySet):
|
class CityQuerySet(models.QuerySet):
|
||||||
"""Extended queryset for City model."""
|
"""Extended queryset for City model."""
|
||||||
|
|
||||||
|
def _generic_search(self, value, filter_fields_names: List[str]):
|
||||||
|
"""Generic method for searching value in specified fields"""
|
||||||
|
filters = [
|
||||||
|
{f'{field}__icontains': value}
|
||||||
|
for field in filter_fields_names
|
||||||
|
]
|
||||||
|
return self.filter(reduce(lambda x, y: x | y, [models.Q(**i) for i in filters]))
|
||||||
|
|
||||||
|
def search_by_name(self, value):
|
||||||
|
"""Search by name or last_name."""
|
||||||
|
return self._generic_search(value, ['name', 'code', 'postal_code'])
|
||||||
|
|
||||||
def by_country_code(self, code):
|
def by_country_code(self, code):
|
||||||
"""Return establishments by country code"""
|
"""Return establishments by country code"""
|
||||||
return self.filter(country__code=code)
|
return self.filter(country__code=code)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ from rest_framework.permissions import IsAuthenticatedOrReadOnly
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from utils.serializers import ImageBaseSerializer
|
from utils.serializers import ImageBaseSerializer
|
||||||
|
|
||||||
|
from location import filters
|
||||||
|
|
||||||
# Address
|
# Address
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -31,6 +33,8 @@ class CityListCreateView(common.CityViewMixin, generics.ListCreateAPIView):
|
||||||
"""Create view for model City."""
|
"""Create view for model City."""
|
||||||
serializer_class = serializers.CitySerializer
|
serializer_class = serializers.CitySerializer
|
||||||
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
|
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
|
||||||
|
queryset = models.City.objects.all()
|
||||||
|
filter_class = filters.CityBackFilter
|
||||||
|
|
||||||
|
|
||||||
class CityRUDView(common.CityViewMixin, generics.RetrieveUpdateDestroyAPIView):
|
class CityRUDView(common.CityViewMixin, generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
|
|
||||||
|
|
@ -51,3 +51,6 @@ class PageTypeAdmin(admin.ModelAdmin):
|
||||||
@admin.register(models.Page)
|
@admin.register(models.Page)
|
||||||
class PageAdmin(admin.ModelAdmin):
|
class PageAdmin(admin.ModelAdmin):
|
||||||
"""Page admin."""
|
"""Page admin."""
|
||||||
|
list_display = ('id', '__str__', 'advertisement')
|
||||||
|
list_filter = ('advertisement__url', 'source')
|
||||||
|
date_hierarchy = 'created'
|
||||||
|
|
|
||||||
32
apps/main/management/commands/add_footers.py
Normal file
32
apps/main/management/commands/add_footers.py
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
from main.models import SiteSettings, Footer
|
||||||
|
from transfer.models import Footers
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = '''Add footers from legacy DB.'''
|
||||||
|
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
objects = []
|
||||||
|
deleted = 0
|
||||||
|
footers_list = Footers.objects.all()
|
||||||
|
|
||||||
|
for old_footer in tqdm(footers_list, desc='Add footers'):
|
||||||
|
site = SiteSettings.objects.filter(old_id=old_footer.site_id).first()
|
||||||
|
if site:
|
||||||
|
if site.footers.exists():
|
||||||
|
site.footers.all().delete()
|
||||||
|
deleted += 1
|
||||||
|
footer = Footer(
|
||||||
|
site=site,
|
||||||
|
about_us=old_footer.about_us,
|
||||||
|
copyright=old_footer.copyright,
|
||||||
|
created=old_footer.created_at,
|
||||||
|
modified=old_footer.updated_at
|
||||||
|
)
|
||||||
|
objects.append(footer)
|
||||||
|
Footer.objects.bulk_create(objects)
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.WARNING(f'Created {len(objects)}/Deleted {deleted} footer objects.'))
|
||||||
29
apps/main/migrations/0040_footer.py
Normal file
29
apps/main/migrations/0040_footer.py
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-12-09 13:21
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('main', '0039_sitefeature_old_id'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Footer',
|
||||||
|
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')),
|
||||||
|
('about_us', models.TextField(verbose_name='about_us')),
|
||||||
|
('copyright', models.TextField(verbose_name='copyright')),
|
||||||
|
('site', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='footers', to='main.SiteSettings', verbose_name='footer')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/main/migrations/0041_auto_20191211_0631.py
Normal file
18
apps/main/migrations/0041_auto_20191211_0631.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-12-11 06:31
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('advertisement', '0008_auto_20191116_1135'),
|
||||||
|
('main', '0040_footer'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='page',
|
||||||
|
unique_together={('advertisement', 'source')},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -305,7 +305,7 @@ class PageQuerySet(models.QuerySet):
|
||||||
|
|
||||||
def by_platform(self, platform: int):
|
def by_platform(self, platform: int):
|
||||||
"""Filter by platform."""
|
"""Filter by platform."""
|
||||||
return self.filter(source=platform)
|
return self.filter(source__in=[Page.ALL, platform])
|
||||||
|
|
||||||
|
|
||||||
class Page(URLImageMixin, PlatformMixin, ProjectBaseMixin):
|
class Page(URLImageMixin, PlatformMixin, ProjectBaseMixin):
|
||||||
|
|
@ -325,6 +325,7 @@ class Page(URLImageMixin, PlatformMixin, ProjectBaseMixin):
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
verbose_name = _('page')
|
verbose_name = _('page')
|
||||||
verbose_name_plural = _('pages')
|
verbose_name_plural = _('pages')
|
||||||
|
unique_together = ('advertisement', 'source')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""Overridden dunder method."""
|
"""Overridden dunder method."""
|
||||||
|
|
@ -351,3 +352,12 @@ class PageType(ProjectBaseMixin):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""Overridden dunder method."""
|
"""Overridden dunder method."""
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class Footer(ProjectBaseMixin):
|
||||||
|
site = models.ForeignKey(
|
||||||
|
'main.SiteSettings', related_name='footers', verbose_name=_('footer'),
|
||||||
|
on_delete=models.PROTECT
|
||||||
|
)
|
||||||
|
about_us = models.TextField(_('about_us'))
|
||||||
|
copyright = models.TextField(_('copyright'))
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ class FeatureSerializer(serializers.ModelSerializer):
|
||||||
'site_settings',
|
'site_settings',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CurrencySerializer(ProjectModelSerializer):
|
class CurrencySerializer(ProjectModelSerializer):
|
||||||
"""Currency serializer."""
|
"""Currency serializer."""
|
||||||
|
|
||||||
|
|
@ -36,6 +37,33 @@ class CurrencySerializer(ProjectModelSerializer):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class FooterSerializer(serializers.ModelSerializer):
|
||||||
|
"""Footer serializer."""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.Footer
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'about_us',
|
||||||
|
'copyright',
|
||||||
|
'created',
|
||||||
|
'modified',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class FooterBackSerializer(FooterSerializer):
|
||||||
|
site_id = serializers.PrimaryKeyRelatedField(
|
||||||
|
queryset=models.SiteSettings.objects.all(),
|
||||||
|
source='site'
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.Footer
|
||||||
|
fields = FooterSerializer.Meta.fields + [
|
||||||
|
'site_id'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class SiteFeatureSerializer(serializers.ModelSerializer):
|
class SiteFeatureSerializer(serializers.ModelSerializer):
|
||||||
id = serializers.IntegerField(source='feature.id')
|
id = serializers.IntegerField(source='feature.id')
|
||||||
slug = serializers.CharField(source='feature.slug')
|
slug = serializers.CharField(source='feature.slug')
|
||||||
|
|
@ -68,6 +96,7 @@ class SiteSettingsSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
country_name = serializers.CharField(source='country.name_translated', read_only=True)
|
country_name = serializers.CharField(source='country.name_translated', read_only=True)
|
||||||
time_format = serializers.CharField(source='country.time_format', read_only=True)
|
time_format = serializers.CharField(source='country.time_format', read_only=True)
|
||||||
|
footers = FooterSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -87,6 +116,7 @@ class SiteSettingsSerializer(serializers.ModelSerializer):
|
||||||
'published_features',
|
'published_features',
|
||||||
'currency',
|
'currency',
|
||||||
'country_name',
|
'country_name',
|
||||||
|
'footers',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -122,8 +152,6 @@ class SiteShortSerializer(serializers.ModelSerializer):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class AwardBaseSerializer(serializers.ModelSerializer):
|
class AwardBaseSerializer(serializers.ModelSerializer):
|
||||||
"""Award base serializer."""
|
"""Award base serializer."""
|
||||||
|
|
||||||
|
|
@ -204,10 +232,26 @@ class PageBaseSerializer(serializers.ModelSerializer):
|
||||||
'advertisement',
|
'advertisement',
|
||||||
]
|
]
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'establishment': {'write_only': True}
|
'advertisement': {'write_only': True},
|
||||||
|
'image_url': {'required': True},
|
||||||
|
'width': {'required': True},
|
||||||
|
'height': {'required': True},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PageExtendedSerializer(PageBaseSerializer):
|
||||||
|
"""Extended serializer for model Page."""
|
||||||
|
source_display = serializers.CharField(read_only=True,
|
||||||
|
source='get_source_display')
|
||||||
|
|
||||||
|
class Meta(PageBaseSerializer.Meta):
|
||||||
|
"""Meta class."""
|
||||||
|
fields = PageBaseSerializer.Meta.fields + [
|
||||||
|
'source',
|
||||||
|
'source_display',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class PageTypeBaseSerializer(serializers.ModelSerializer):
|
class PageTypeBaseSerializer(serializers.ModelSerializer):
|
||||||
"""Serializer fro model PageType."""
|
"""Serializer fro model PageType."""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,10 @@ urlpatterns = [
|
||||||
name='site-feature-list-create'),
|
name='site-feature-list-create'),
|
||||||
path('site-feature/<int:id>/', views.SiteFeatureRUDBackView.as_view(),
|
path('site-feature/<int:id>/', views.SiteFeatureRUDBackView.as_view(),
|
||||||
name='site-feature-rud'),
|
name='site-feature-rud'),
|
||||||
|
path('footer/', views.FooterBackView.as_view(), name='footer-list-create'),
|
||||||
|
path('footer/<int:pk>/', views.FooterRUDBackView.as_view(), name='footer-rud'),
|
||||||
|
path('page-types/', views.PageTypeListCreateView.as_view(),
|
||||||
|
name='page-types-list-create')
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ from rest_framework import generics, permissions
|
||||||
|
|
||||||
from main import serializers
|
from main import serializers
|
||||||
from main.filters import AwardFilter
|
from main.filters import AwardFilter
|
||||||
from main.models import Award
|
from main.models import Award, Footer, PageType
|
||||||
from main.views import SiteSettingsView, SiteListView
|
from main.views import SiteSettingsView, SiteListView
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -67,3 +67,25 @@ class SiteSettingsBackOfficeView(SiteSettingsView):
|
||||||
class SiteListBackOfficeView(SiteListView):
|
class SiteListBackOfficeView(SiteListView):
|
||||||
"""Site settings View."""
|
"""Site settings View."""
|
||||||
serializer_class = serializers.SiteSerializer
|
serializer_class = serializers.SiteSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class FooterBackView(generics.ListCreateAPIView):
|
||||||
|
"""Footer back list/create view."""
|
||||||
|
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||||
|
serializer_class = serializers.FooterBackSerializer
|
||||||
|
queryset = Footer.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class FooterRUDBackView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
"""Footer back RUD view."""
|
||||||
|
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||||
|
serializer_class = serializers.FooterBackSerializer
|
||||||
|
queryset = Footer.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class PageTypeListCreateView(generics.ListCreateAPIView):
|
||||||
|
"""PageType back office view."""
|
||||||
|
permission_classes = (permissions.IsAuthenticatedOrReadOnly, )
|
||||||
|
pagination_class = None
|
||||||
|
serializer_class = serializers.PageTypeBaseSerializer
|
||||||
|
queryset = PageType.objects.all()
|
||||||
|
|
|
||||||
18
apps/news/migrations/0038_news_backoffice_title.py
Normal file
18
apps/news/migrations/0038_news_backoffice_title.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-12-10 12:20
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('news', '0037_auto_20191129_1320'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='news',
|
||||||
|
name='backoffice_title',
|
||||||
|
field=models.TextField(default=None, null=True, verbose_name='Title for searching via BO'),
|
||||||
|
),
|
||||||
|
]
|
||||||
29
apps/news/migrations/0039_news_slugs.py
Normal file
29
apps/news/migrations/0039_news_slugs.py
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-12-10 13:49
|
||||||
|
|
||||||
|
import django.contrib.postgres.fields.hstore
|
||||||
|
from django.db import migrations
|
||||||
|
from django.contrib.postgres.operations import HStoreExtension
|
||||||
|
|
||||||
|
def migrate_slugs(apps, schemaeditor):
|
||||||
|
News = apps.get_model('news', 'News')
|
||||||
|
for news in News.objects.all():
|
||||||
|
if news.slug:
|
||||||
|
news.slugs = {'en-GB': news.slug}
|
||||||
|
news.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('news', '0038_news_backoffice_title'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
HStoreExtension(),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='news',
|
||||||
|
name='slugs',
|
||||||
|
field=django.contrib.postgres.fields.hstore.HStoreField(blank=True, default=None, help_text='{"en-GB":"some slug"}', null=True, verbose_name='Slugs for current news obj'),
|
||||||
|
),
|
||||||
|
migrations.RunPython(migrate_slugs, migrations.RunPython.noop)
|
||||||
|
]
|
||||||
17
apps/news/migrations/0040_remove_news_slug.py
Normal file
17
apps/news/migrations/0040_remove_news_slug.py
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-12-10 16:22
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('news', '0039_news_slugs'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='news',
|
||||||
|
name='slug',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -12,6 +12,7 @@ from utils.models import (BaseAttributes, TJSONField, TranslatedFieldsMixin, Has
|
||||||
FavoritesMixin)
|
FavoritesMixin)
|
||||||
from utils.querysets import TranslationQuerysetMixin
|
from utils.querysets import TranslationQuerysetMixin
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.contrib.postgres.fields import HStoreField
|
||||||
|
|
||||||
|
|
||||||
class Agenda(ProjectBaseMixin, TranslatedFieldsMixin):
|
class Agenda(ProjectBaseMixin, TranslatedFieldsMixin):
|
||||||
|
|
@ -168,6 +169,8 @@ class News(GalleryModelMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixi
|
||||||
title = TJSONField(blank=True, null=True, default=None,
|
title = TJSONField(blank=True, null=True, default=None,
|
||||||
verbose_name=_('title'),
|
verbose_name=_('title'),
|
||||||
help_text='{"en-GB":"some text"}')
|
help_text='{"en-GB":"some text"}')
|
||||||
|
backoffice_title = models.TextField(null=True, default=None,
|
||||||
|
verbose_name=_('Title for searching via BO'))
|
||||||
subtitle = TJSONField(blank=True, null=True, default=None,
|
subtitle = TJSONField(blank=True, null=True, default=None,
|
||||||
verbose_name=_('subtitle'),
|
verbose_name=_('subtitle'),
|
||||||
help_text='{"en-GB":"some text"}')
|
help_text='{"en-GB":"some text"}')
|
||||||
|
|
@ -178,8 +181,9 @@ class News(GalleryModelMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixi
|
||||||
verbose_name=_('Start'))
|
verbose_name=_('Start'))
|
||||||
end = models.DateTimeField(blank=True, null=True, default=None,
|
end = models.DateTimeField(blank=True, null=True, default=None,
|
||||||
verbose_name=_('End'))
|
verbose_name=_('End'))
|
||||||
slug = models.SlugField(unique=True, max_length=255,
|
slugs = HStoreField(null=True, blank=True, default=None,
|
||||||
verbose_name=_('News slug'))
|
verbose_name=_('Slugs for current news obj'),
|
||||||
|
help_text='{"en-GB":"some slug"}')
|
||||||
state = models.PositiveSmallIntegerField(default=WAITING, choices=STATE_CHOICES,
|
state = models.PositiveSmallIntegerField(default=WAITING, choices=STATE_CHOICES,
|
||||||
verbose_name=_('State'))
|
verbose_name=_('State'))
|
||||||
is_highlighted = models.BooleanField(default=False,
|
is_highlighted = models.BooleanField(default=False,
|
||||||
|
|
@ -228,7 +232,7 @@ class News(GalleryModelMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixi
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def web_url(self):
|
def web_url(self):
|
||||||
return reverse('web:news:rud', kwargs={'slug': self.slug})
|
return reverse('web:news:rud', kwargs={'slug': next(iter(self.slugs.values()))})
|
||||||
|
|
||||||
def should_read(self, user):
|
def should_read(self, user):
|
||||||
return self.__class__.objects.should_read(self, user)[:3]
|
return self.__class__.objects.should_read(self, user)[:3]
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ class NewsBaseSerializer(ProjectModelSerializer):
|
||||||
'is_highlighted',
|
'is_highlighted',
|
||||||
'news_type',
|
'news_type',
|
||||||
'tags',
|
'tags',
|
||||||
'slug',
|
'slugs',
|
||||||
'view_counter',
|
'view_counter',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -169,9 +169,31 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer):
|
||||||
|
|
||||||
fields = NewsBaseSerializer.Meta.fields + (
|
fields = NewsBaseSerializer.Meta.fields + (
|
||||||
'title',
|
'title',
|
||||||
|
'backoffice_title',
|
||||||
'subtitle',
|
'subtitle',
|
||||||
'is_published',
|
'is_published',
|
||||||
)
|
)
|
||||||
|
extra_kwargs = {
|
||||||
|
'backoffice_title': {'allow_null': False},
|
||||||
|
}
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
slugs = validated_data.get('slugs')
|
||||||
|
if slugs:
|
||||||
|
if models.News.objects.filter(
|
||||||
|
slugs__values__contains=list(slugs.values())
|
||||||
|
).exists():
|
||||||
|
raise serializers.ValidationError({'slugs': _('News with this slug already exists.')})
|
||||||
|
return super().create(validated_data)
|
||||||
|
|
||||||
|
def update(self, instance, validated_data):
|
||||||
|
slugs = validated_data.get('slugs')
|
||||||
|
if slugs:
|
||||||
|
if models.News.objects.filter(
|
||||||
|
slugs__values__contains=list(slugs.values())
|
||||||
|
).exclude(pk=instance.pk).exists():
|
||||||
|
raise serializers.ValidationError({'slugs': _('News with this slug already exists.')})
|
||||||
|
return super().update(instance, validated_data)
|
||||||
|
|
||||||
|
|
||||||
class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer,
|
class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer,
|
||||||
|
|
@ -252,7 +274,7 @@ class NewsFavoritesCreateSerializer(FavoritesCreateSerializer):
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
"""Overridden validate method"""
|
"""Overridden validate method"""
|
||||||
# Check establishment object
|
# Check establishment object
|
||||||
news_qs = models.News.objects.filter(slug=self.slug)
|
news_qs = models.News.objects.filter(slugs__values__contains=[self.slug])
|
||||||
|
|
||||||
# Check establishment obj by slug from lookup_kwarg
|
# Check establishment obj by slug from lookup_kwarg
|
||||||
if not news_qs.exists():
|
if not news_qs.exists():
|
||||||
|
|
|
||||||
|
|
@ -66,10 +66,11 @@ class BaseTestCase(APITestCase):
|
||||||
start=datetime.now() + timedelta(hours=-2),
|
start=datetime.now() + timedelta(hours=-2),
|
||||||
end=datetime.now() + timedelta(hours=2),
|
end=datetime.now() + timedelta(hours=2),
|
||||||
state=News.PUBLISHED,
|
state=News.PUBLISHED,
|
||||||
slug='test-news-slug',
|
slugs={'en-GB': 'test-news-slug'},
|
||||||
country=self.country_ru,
|
country=self.country_ru,
|
||||||
site=self.site_ru
|
site=self.site_ru
|
||||||
)
|
)
|
||||||
|
self.slug = next(iter(self.test_news.slugs.values()))
|
||||||
|
|
||||||
|
|
||||||
class NewsTestCase(BaseTestCase):
|
class NewsTestCase(BaseTestCase):
|
||||||
|
|
@ -84,7 +85,7 @@ class NewsTestCase(BaseTestCase):
|
||||||
"start": datetime.now() + timedelta(hours=-2),
|
"start": datetime.now() + timedelta(hours=-2),
|
||||||
"end": datetime.now() + timedelta(hours=2),
|
"end": datetime.now() + timedelta(hours=2),
|
||||||
"state": News.PUBLISHED,
|
"state": News.PUBLISHED,
|
||||||
"slug": 'test-news-slug_post',
|
"slugs": {'en-GB': 'test-news-slug_post'},
|
||||||
"country_id": self.country_ru.id,
|
"country_id": self.country_ru.id,
|
||||||
"site_id": self.site_ru.id
|
"site_id": self.site_ru.id
|
||||||
}
|
}
|
||||||
|
|
@ -97,7 +98,7 @@ class NewsTestCase(BaseTestCase):
|
||||||
response = self.client.get(reverse('web:news:list'))
|
response = self.client.get(reverse('web:news:list'))
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
response = self.client.get(f"/api/web/news/slug/{self.test_news.slug}/")
|
response = self.client.get(f"/api/web/news/slug/{self.slug}/")
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
response = self.client.get("/api/web/news/types/")
|
response = self.client.get("/api/web/news/types/")
|
||||||
|
|
@ -117,7 +118,7 @@ class NewsTestCase(BaseTestCase):
|
||||||
data = {
|
data = {
|
||||||
'id': self.test_news.id,
|
'id': self.test_news.id,
|
||||||
'description': {"ru-RU": "Description test news!"},
|
'description': {"ru-RU": "Description test news!"},
|
||||||
'slug': self.test_news.slug,
|
'slugs': self.test_news.slugs,
|
||||||
'start': self.test_news.start,
|
'start': self.test_news.start,
|
||||||
'news_type_id': self.test_news.news_type_id,
|
'news_type_id': self.test_news.news_type_id,
|
||||||
'country_id': self.country_ru.id,
|
'country_id': self.country_ru.id,
|
||||||
|
|
@ -133,10 +134,10 @@ class NewsTestCase(BaseTestCase):
|
||||||
"object_id": self.test_news.id
|
"object_id": self.test_news.id
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.client.post(f'/api/web/news/slug/{self.test_news.slug}/favorites/', data=data)
|
response = self.client.post(f'/api/web/news/slug/{self.slug}/favorites/', data=data)
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
|
||||||
response = self.client.delete(f'/api/web/news/slug/{self.test_news.slug}/favorites/', format='json')
|
response = self.client.delete(f'/api/web/news/slug/{self.slug}/favorites/', format='json')
|
||||||
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,10 @@ class NewsMixinView:
|
||||||
qs = qs.by_country_code(country_code)
|
qs = qs.by_country_code(country_code)
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
return self.get_queryset() \
|
||||||
|
.filter(slugs__values__contains=[self.kwargs['slug']]).first()
|
||||||
|
|
||||||
|
|
||||||
class NewsListView(NewsMixinView, generics.ListAPIView):
|
class NewsListView(NewsMixinView, generics.ListAPIView):
|
||||||
"""News list view."""
|
"""News list view."""
|
||||||
|
|
@ -46,7 +50,7 @@ class NewsListView(NewsMixinView, generics.ListAPIView):
|
||||||
class NewsDetailView(NewsMixinView, generics.RetrieveAPIView):
|
class NewsDetailView(NewsMixinView, generics.RetrieveAPIView):
|
||||||
"""News detail view."""
|
"""News detail view."""
|
||||||
|
|
||||||
lookup_field = 'slug'
|
lookup_field = None
|
||||||
serializer_class = serializers.NewsDetailWebSerializer
|
serializer_class = serializers.NewsDetailWebSerializer
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from tqdm import tqdm
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = '''Add add product tags networks from old db to new db.
|
help = '''Add product tags networks from old db to new db.
|
||||||
Run after add_product!!!'''
|
Run after add_product!!!'''
|
||||||
|
|
||||||
def category_sql(self):
|
def category_sql(self):
|
||||||
|
|
@ -101,14 +101,12 @@ class Command(BaseCommand):
|
||||||
p.tags.clear()
|
p.tags.clear()
|
||||||
print('End clear tags product')
|
print('End clear tags product')
|
||||||
|
|
||||||
|
|
||||||
def remove_tags(self):
|
def remove_tags(self):
|
||||||
print('Begin delete many tags')
|
print('Begin delete many tags')
|
||||||
Tag.objects.\
|
Tag.objects.\
|
||||||
filter(news__isnull=True, establishments__isnull=True).delete()
|
filter(news__isnull=True, establishments__isnull=True).delete()
|
||||||
print('End delete many tags')
|
print('End delete many tags')
|
||||||
|
|
||||||
|
|
||||||
def product_sql(self):
|
def product_sql(self):
|
||||||
with connections['legacy'].cursor() as cursor:
|
with connections['legacy'].cursor() as cursor:
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
|
|
|
||||||
20
apps/product/management/commands/check_serial_number.py
Normal file
20
apps/product/management/commands/check_serial_number.py
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from tag.models import Tag, TagCategory
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = '''Check product serial number from old db to new db.
|
||||||
|
Run after add_product_tag!!!'''
|
||||||
|
|
||||||
|
def check_serial_number(self):
|
||||||
|
category = TagCategory.objects.get(index_name='serial_number')
|
||||||
|
tags = Tag.objects.filter(category=category, products__isnull=False)
|
||||||
|
for tag in tqdm(tags, desc='Update serial number for product'):
|
||||||
|
tag.products.all().update(serial_number=tag.value)
|
||||||
|
tag.products.clear()
|
||||||
|
|
||||||
|
self.stdout.write(self.style.WARNING(f'Check serial number product end.'))
|
||||||
|
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
self.check_serial_number()
|
||||||
18
apps/product/migrations/0019_product_serial_number.py
Normal file
18
apps/product/migrations/0019_product_serial_number.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-11-21 09:24
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('product', '0018_purchasedproduct'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='product',
|
||||||
|
name='serial_number',
|
||||||
|
field=models.CharField(default=None, max_length=255, null=True, verbose_name='Serial number'),
|
||||||
|
),
|
||||||
|
]
|
||||||
14
apps/product/migrations/0020_merge_20191209_0911.py
Normal file
14
apps/product/migrations/0020_merge_20191209_0911.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-12-09 09:11
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('product', '0019_auto_20191204_1420'),
|
||||||
|
('product', '0019_product_serial_number'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
]
|
||||||
|
|
@ -218,6 +218,10 @@ class Product(GalleryModelMixin, TranslatedFieldsMixin, BaseAttributes,
|
||||||
comments = generic.GenericRelation(to='comment.Comment')
|
comments = generic.GenericRelation(to='comment.Comment')
|
||||||
awards = generic.GenericRelation(to='main.Award', related_query_name='product')
|
awards = generic.GenericRelation(to='main.Award', related_query_name='product')
|
||||||
|
|
||||||
|
serial_number = models.CharField(max_length=255,
|
||||||
|
default=None, null=True,
|
||||||
|
verbose_name=_('Serial number'))
|
||||||
|
|
||||||
objects = ProductManager.from_queryset(ProductQuerySet)()
|
objects = ProductManager.from_queryset(ProductQuerySet)()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,8 @@ from comment.models import Comment
|
||||||
from comment.serializers import CommentSerializer
|
from comment.serializers import CommentSerializer
|
||||||
from establishment.serializers import EstablishmentProductShortSerializer
|
from establishment.serializers import EstablishmentProductShortSerializer
|
||||||
from establishment.serializers.common import _EstablishmentAddressShortSerializer
|
from establishment.serializers.common import _EstablishmentAddressShortSerializer
|
||||||
from location.serializers import WineOriginRegionBaseSerializer, WineOriginBaseSerializer
|
from location.serializers import WineOriginRegionBaseSerializer,\
|
||||||
|
WineOriginBaseSerializer, EstablishmentWineOriginBaseSerializer
|
||||||
from main.serializers import AwardSerializer
|
from main.serializers import AwardSerializer
|
||||||
from product import models
|
from product import models
|
||||||
from review.serializers import ReviewShortSerializer
|
from review.serializers import ReviewShortSerializer
|
||||||
|
|
@ -95,6 +96,7 @@ class ProductBaseSerializer(serializers.ModelSerializer):
|
||||||
preview_image_url = serializers.URLField(allow_null=True,
|
preview_image_url = serializers.URLField(allow_null=True,
|
||||||
read_only=True)
|
read_only=True)
|
||||||
in_favorites = serializers.BooleanField(allow_null=True)
|
in_favorites = serializers.BooleanField(allow_null=True)
|
||||||
|
wine_origins = EstablishmentWineOriginBaseSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -113,6 +115,7 @@ class ProductBaseSerializer(serializers.ModelSerializer):
|
||||||
'wine_regions',
|
'wine_regions',
|
||||||
'wine_colors',
|
'wine_colors',
|
||||||
'in_favorites',
|
'in_favorites',
|
||||||
|
'wine_origins',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from establishment import models
|
||||||
|
|
||||||
EstablishmentIndex = Index(settings.ELASTICSEARCH_INDEX_NAMES.get(__name__,
|
EstablishmentIndex = Index(settings.ELASTICSEARCH_INDEX_NAMES.get(__name__,
|
||||||
'establishment'))
|
'establishment'))
|
||||||
EstablishmentIndex.settings(number_of_shards=1, number_of_replicas=1)
|
EstablishmentIndex.settings(number_of_shards=5, number_of_replicas=2)
|
||||||
|
|
||||||
|
|
||||||
@EstablishmentIndex.doc_type
|
@EstablishmentIndex.doc_type
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ class NewsDocument(Document):
|
||||||
'name': fields.KeywordField()})
|
'name': fields.KeywordField()})
|
||||||
title = fields.ObjectField(attr='title_indexing',
|
title = fields.ObjectField(attr='title_indexing',
|
||||||
properties=OBJECT_FIELD_PROPERTIES)
|
properties=OBJECT_FIELD_PROPERTIES)
|
||||||
|
slugs = fields.ObjectField(properties=OBJECT_FIELD_PROPERTIES)
|
||||||
|
backoffice_title = fields.TextField(analyzer='english')
|
||||||
subtitle = fields.ObjectField(attr='subtitle_indexing',
|
subtitle = fields.ObjectField(attr='subtitle_indexing',
|
||||||
properties=OBJECT_FIELD_PROPERTIES)
|
properties=OBJECT_FIELD_PROPERTIES)
|
||||||
description = fields.ObjectField(attr='description_indexing',
|
description = fields.ObjectField(attr='description_indexing',
|
||||||
|
|
@ -43,13 +45,16 @@ class NewsDocument(Document):
|
||||||
multi=True)
|
multi=True)
|
||||||
favorites_for_users = fields.ListField(field=fields.IntegerField())
|
favorites_for_users = fields.ListField(field=fields.IntegerField())
|
||||||
start = fields.DateField(attr='start')
|
start = fields.DateField(attr='start')
|
||||||
|
|
||||||
|
def prepare_slugs(self, instance):
|
||||||
|
return {locale: instance.slugs.get(locale) for locale in OBJECT_FIELD_PROPERTIES}
|
||||||
|
|
||||||
class Django:
|
class Django:
|
||||||
|
|
||||||
model = models.News
|
model = models.News
|
||||||
fields = (
|
fields = (
|
||||||
'id',
|
'id',
|
||||||
'end',
|
'end',
|
||||||
'slug',
|
|
||||||
'state',
|
'state',
|
||||||
'is_highlighted',
|
'is_highlighted',
|
||||||
'template',
|
'template',
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django_elasticsearch_dsl import Document, Index, fields
|
from django_elasticsearch_dsl import Document, Index, fields
|
||||||
from tag import models
|
from tag import models
|
||||||
|
from news.models import News
|
||||||
|
|
||||||
TagCategoryIndex = Index(settings.ELASTICSEARCH_INDEX_NAMES.get(__name__, 'tag_category'))
|
TagCategoryIndex = Index(settings.ELASTICSEARCH_INDEX_NAMES.get(__name__, 'tag_category'))
|
||||||
TagCategoryIndex.settings(number_of_shards=2, number_of_replicas=2)
|
TagCategoryIndex.settings(number_of_shards=2, number_of_replicas=2)
|
||||||
|
|
@ -26,8 +27,20 @@ class TagCategoryDocument(Document):
|
||||||
'public',
|
'public',
|
||||||
'value_type'
|
'value_type'
|
||||||
)
|
)
|
||||||
related_models = [models.Tag]
|
related_models = [models.Tag, News]
|
||||||
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return super().get_queryset().with_base_related()
|
return super().get_queryset().with_base_related()
|
||||||
|
|
||||||
|
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.
|
||||||
|
The related_models option should be used with caution because it can lead in the index
|
||||||
|
to the updating of a lot of items.
|
||||||
|
"""
|
||||||
|
if isinstance(related_instance, News):
|
||||||
|
tag_categories = []
|
||||||
|
for tag in related_instance.tags.all():
|
||||||
|
if tag.category not in tag_categories:
|
||||||
|
tag_categories.append(tag.category)
|
||||||
|
return tag_categories
|
||||||
|
|
@ -221,7 +221,7 @@ class NewsDocumentSerializer(InFavoritesMixin, DocumentSerializer):
|
||||||
'news_type',
|
'news_type',
|
||||||
'tags',
|
'tags',
|
||||||
'start',
|
'start',
|
||||||
'slug',
|
'slugs',
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
@ -243,8 +243,8 @@ class WineOriginSerializer(serializers.Serializer):
|
||||||
class EstablishmentDocumentSerializer(InFavoritesMixin, DocumentSerializer):
|
class EstablishmentDocumentSerializer(InFavoritesMixin, DocumentSerializer):
|
||||||
"""Establishment document serializer."""
|
"""Establishment document serializer."""
|
||||||
|
|
||||||
establishment_type = EstablishmentTypeSerializer()
|
type = EstablishmentTypeSerializer(source='establishment_type')
|
||||||
establishment_subtypes = EstablishmentTypeSerializer(many=True)
|
subtypes = EstablishmentTypeSerializer(many=True, source='establishment_subtypes')
|
||||||
address = AddressDocumentSerializer(allow_null=True)
|
address = AddressDocumentSerializer(allow_null=True)
|
||||||
tags = TagsDocumentSerializer(many=True, source='visible_tags')
|
tags = TagsDocumentSerializer(many=True, source='visible_tags')
|
||||||
restaurant_category = TagsDocumentSerializer(many=True, allow_null=True)
|
restaurant_category = TagsDocumentSerializer(many=True, allow_null=True)
|
||||||
|
|
@ -280,8 +280,8 @@ class EstablishmentDocumentSerializer(InFavoritesMixin, DocumentSerializer):
|
||||||
'wine_origins',
|
'wine_origins',
|
||||||
# 'works_now',
|
# 'works_now',
|
||||||
# 'collections',
|
# 'collections',
|
||||||
# 'establishment_type',
|
'type',
|
||||||
# 'establishment_subtypes',
|
'subtypes',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -314,7 +314,8 @@ class MobileEstablishmentDocumentViewSet(EstablishmentDocumentViewSet):
|
||||||
filter_backends = [
|
filter_backends = [
|
||||||
FilteringFilterBackend,
|
FilteringFilterBackend,
|
||||||
filters.CustomSearchFilterBackend,
|
filters.CustomSearchFilterBackend,
|
||||||
GeoSpatialFilteringFilterBackend,
|
filters.CustomGeoSpatialFilteringFilterBackend,
|
||||||
|
GeoSpatialOrderingFilterBackend,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,14 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.fields import SerializerMethodField
|
from rest_framework.fields import SerializerMethodField
|
||||||
|
|
||||||
from establishment.models import (Establishment, EstablishmentType)
|
from establishment.models import Establishment
|
||||||
from news.models import News, NewsType
|
from establishment.models import EstablishmentType
|
||||||
|
from news.models import News
|
||||||
|
from news.models import NewsType
|
||||||
from tag import models
|
from tag import models
|
||||||
from utils.exceptions import (ObjectAlreadyAdded, BindingObjectNotFound,
|
from utils.exceptions import BindingObjectNotFound
|
||||||
RemovedBindingObjectNotFound)
|
from utils.exceptions import ObjectAlreadyAdded
|
||||||
|
from utils.exceptions import RemovedBindingObjectNotFound
|
||||||
from utils.serializers import TranslatedField
|
from utils.serializers import TranslatedField
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -95,6 +98,72 @@ class TagCategoryBaseSerializer(serializers.ModelSerializer):
|
||||||
return TagBaseSerializer(instance=tags, many=True, read_only=True).data
|
return TagBaseSerializer(instance=tags, many=True, read_only=True).data
|
||||||
|
|
||||||
|
|
||||||
|
class FiltersTagCategoryBaseSerializer(serializers.ModelSerializer):
|
||||||
|
"""Serializer for model TagCategory."""
|
||||||
|
|
||||||
|
label_translated = TranslatedField()
|
||||||
|
filters = SerializerMethodField()
|
||||||
|
param_name = SerializerMethodField()
|
||||||
|
type = SerializerMethodField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
|
||||||
|
model = models.TagCategory
|
||||||
|
fields = (
|
||||||
|
'id',
|
||||||
|
'label_translated',
|
||||||
|
'index_name',
|
||||||
|
'param_name',
|
||||||
|
'type',
|
||||||
|
'filters',
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_type(self, obj):
|
||||||
|
return obj in ['open_now', ]
|
||||||
|
|
||||||
|
def get_param_name(self, obj):
|
||||||
|
if obj == 'service':
|
||||||
|
return 'tags_id__in'
|
||||||
|
|
||||||
|
elif obj == 'pop':
|
||||||
|
return 'tags_id__in'
|
||||||
|
|
||||||
|
elif obj == 'open_now':
|
||||||
|
return 'open_now'
|
||||||
|
|
||||||
|
elif obj == 'wine_region':
|
||||||
|
return 'wine_region_id__in'
|
||||||
|
|
||||||
|
return '%s__in' % obj.index_name
|
||||||
|
|
||||||
|
def get_fields(self, *args, **kwargs):
|
||||||
|
fields = super(FiltersTagCategoryBaseSerializer, self).get_fields()
|
||||||
|
|
||||||
|
if self.get_type(self):
|
||||||
|
fields.pop('filters', None)
|
||||||
|
else:
|
||||||
|
fields.pop('type', None)
|
||||||
|
|
||||||
|
return fields
|
||||||
|
|
||||||
|
def get_filters(self, obj):
|
||||||
|
query_params = dict(self.context['request'].query_params)
|
||||||
|
|
||||||
|
params = {}
|
||||||
|
if 'establishment_type' in query_params:
|
||||||
|
params = {
|
||||||
|
'establishments__isnull': False,
|
||||||
|
}
|
||||||
|
elif 'product_type' in query_params:
|
||||||
|
params = {
|
||||||
|
'products__isnull': False,
|
||||||
|
}
|
||||||
|
|
||||||
|
tags = obj.tags.filter(**params).distinct()
|
||||||
|
return TagBaseSerializer(instance=tags, many=True, read_only=True).data
|
||||||
|
|
||||||
|
|
||||||
class TagCategoryShortSerializer(serializers.ModelSerializer):
|
class TagCategoryShortSerializer(serializers.ModelSerializer):
|
||||||
"""Serializer for model TagCategory."""
|
"""Serializer for model TagCategory."""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ app_name = 'tag'
|
||||||
|
|
||||||
router = SimpleRouter()
|
router = SimpleRouter()
|
||||||
router.register(r'categories', views.TagCategoryViewSet)
|
router.register(r'categories', views.TagCategoryViewSet)
|
||||||
|
router.register(r'filters', views.FiltersTagCategoryViewSet)
|
||||||
router.register(r'chosen_tags', views.ChosenTagsView)
|
router.register(r'chosen_tags', views.ChosenTagsView)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,17 @@
|
||||||
"""Tag views."""
|
"""Tag views."""
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from rest_framework import generics
|
||||||
|
from rest_framework import mixins
|
||||||
from rest_framework import permissions
|
from rest_framework import permissions
|
||||||
from rest_framework import viewsets, mixins, status, generics
|
from rest_framework import status
|
||||||
|
from rest_framework import viewsets
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from tag import filters, models, serializers
|
from location.models import WineRegion
|
||||||
|
from tag import filters
|
||||||
|
from tag import models
|
||||||
|
from tag import serializers
|
||||||
|
|
||||||
|
|
||||||
class ChosenTagsView(generics.ListAPIView, viewsets.GenericViewSet):
|
class ChosenTagsView(generics.ListAPIView, viewsets.GenericViewSet):
|
||||||
|
|
@ -36,7 +42,8 @@ class ChosenTagsView(generics.ListAPIView, viewsets.GenericViewSet):
|
||||||
serializer = self.get_serializer(queryset, many=True)
|
serializer = self.get_serializer(queryset, many=True)
|
||||||
result_list = serializer.data
|
result_list = serializer.data
|
||||||
if request.query_params.get('type') and (settings.ESTABLISHMENT_CHOSEN_TAGS or settings.NEWS_CHOSEN_TAGS):
|
if request.query_params.get('type') and (settings.ESTABLISHMENT_CHOSEN_TAGS or settings.NEWS_CHOSEN_TAGS):
|
||||||
ordered_list = settings.ESTABLISHMENT_CHOSEN_TAGS if request.query_params.get('type') == 'establishment' else settings.NEWS_CHOSEN_TAGS
|
ordered_list = settings.ESTABLISHMENT_CHOSEN_TAGS if request.query_params.get(
|
||||||
|
'type') == 'establishment' else settings.NEWS_CHOSEN_TAGS
|
||||||
result_list = sorted(result_list, key=lambda x: ordered_list.index(x['index_name']))
|
result_list = sorted(result_list, key=lambda x: ordered_list.index(x['index_name']))
|
||||||
return Response(result_list)
|
return Response(result_list)
|
||||||
|
|
||||||
|
|
@ -53,6 +60,104 @@ class TagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
|
||||||
serializer_class = serializers.TagCategoryBaseSerializer
|
serializer_class = serializers.TagCategoryBaseSerializer
|
||||||
|
|
||||||
|
|
||||||
|
# User`s views & viewsets
|
||||||
|
class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
|
||||||
|
"""ViewSet for TagCategory model."""
|
||||||
|
|
||||||
|
filterset_class = filters.TagCategoryFilterSet
|
||||||
|
pagination_class = None
|
||||||
|
permission_classes = (permissions.AllowAny,)
|
||||||
|
queryset = models.TagCategory.objects.with_tags().with_base_related(). \
|
||||||
|
distinct()
|
||||||
|
serializer_class = serializers.FiltersTagCategoryBaseSerializer
|
||||||
|
|
||||||
|
def list(self, request, *args, **kwargs):
|
||||||
|
queryset = self.filter_queryset(self.get_queryset())
|
||||||
|
serializer = self.get_serializer(queryset, many=True)
|
||||||
|
|
||||||
|
result_list = serializer.data
|
||||||
|
query_params = request.query_params
|
||||||
|
|
||||||
|
params_type = query_params['type']
|
||||||
|
|
||||||
|
if params_type == 'restaurant' and 'toque_number__in' in query_params:
|
||||||
|
toques = {
|
||||||
|
"index_name": "toque_number",
|
||||||
|
"label_translated": "Toques",
|
||||||
|
"param_name": "toque_number__in",
|
||||||
|
"filters": [{
|
||||||
|
"id": toque_id,
|
||||||
|
"index_name": "toque_%d" % toque_id,
|
||||||
|
"label_translated": "Toque %d" % toque_id
|
||||||
|
} for toque_id in range(6)]
|
||||||
|
}
|
||||||
|
result_list.append(toques)
|
||||||
|
|
||||||
|
if params_type == 'winery' and 'wine_region_id__in' in query_params:
|
||||||
|
try:
|
||||||
|
wine_region_id = int(query_params['wine_region_id__in'])
|
||||||
|
|
||||||
|
wine_regions = {
|
||||||
|
"index_name": "wine_region",
|
||||||
|
"label_translated": "Wine region",
|
||||||
|
"param_name": "wine_region_id__in",
|
||||||
|
"filters": [{
|
||||||
|
"id": obj.id,
|
||||||
|
"index_name": obj.name.lower().replace(' ', '_'),
|
||||||
|
"label_translated": obj.name
|
||||||
|
} for obj in WineRegion.objects.filter(id=wine_region_id)]
|
||||||
|
}
|
||||||
|
|
||||||
|
result_list.append(wine_regions)
|
||||||
|
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if params_type == 'restaurant' and 'works_noon__in' in query_params:
|
||||||
|
week_days = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")
|
||||||
|
works_noon = {
|
||||||
|
"index_name": "works_noon",
|
||||||
|
"label_translated": "Open noon",
|
||||||
|
"param_name": "works_noon__in",
|
||||||
|
"filters": [{
|
||||||
|
"id": weekday,
|
||||||
|
"index_name": week_days[weekday].lower(),
|
||||||
|
"label_translated": week_days[weekday]
|
||||||
|
} for weekday in range(7)]
|
||||||
|
}
|
||||||
|
result_list.append(works_noon)
|
||||||
|
|
||||||
|
if params_type == 'restaurant' and 'works_evening__in' in query_params:
|
||||||
|
week_days = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")
|
||||||
|
works_evening = {
|
||||||
|
"index_name": "works_evening",
|
||||||
|
"label_translated": "Open evening",
|
||||||
|
"param_name": "works_evening__in",
|
||||||
|
"filters": [{
|
||||||
|
"id": weekday,
|
||||||
|
"index_name": week_days[weekday].lower(),
|
||||||
|
"label_translated": week_days[weekday]
|
||||||
|
} for weekday in range(7)]
|
||||||
|
}
|
||||||
|
result_list.append(works_evening)
|
||||||
|
|
||||||
|
if params_type in ('restaurant', 'artisan') and 'works_now' in query_params:
|
||||||
|
works_now = {
|
||||||
|
"index_name": "open_now",
|
||||||
|
"label_translated": "Open now",
|
||||||
|
"param_name": "open_now",
|
||||||
|
"type": True
|
||||||
|
}
|
||||||
|
result_list.append(works_now)
|
||||||
|
|
||||||
|
if 'tags_id__in' in query_params:
|
||||||
|
# filtering by params_type and tags id
|
||||||
|
# todo: result_list.append( filtering_data )
|
||||||
|
pass
|
||||||
|
|
||||||
|
return Response(result_list)
|
||||||
|
|
||||||
|
|
||||||
# BackOffice user`s views & viewsets
|
# BackOffice user`s views & viewsets
|
||||||
class BindObjectMixin:
|
class BindObjectMixin:
|
||||||
"""Bind object mixin."""
|
"""Bind object mixin."""
|
||||||
|
|
|
||||||
|
|
@ -1208,3 +1208,17 @@ class NewsletterSubscriber(MigrateMixin):
|
||||||
class Meta:
|
class Meta:
|
||||||
managed = False
|
managed = False
|
||||||
db_table = 'newsletter_subscriptions'
|
db_table = 'newsletter_subscriptions'
|
||||||
|
|
||||||
|
|
||||||
|
class Footers(MigrateMixin):
|
||||||
|
using = 'legacy'
|
||||||
|
|
||||||
|
about_us = models.TextField(blank=True, null=True)
|
||||||
|
copyright = models.TextField(blank=True, null=True)
|
||||||
|
site = models.ForeignKey('Sites', models.DO_NOTHING, blank=True, null=True)
|
||||||
|
created_at = models.DateTimeField()
|
||||||
|
updated_at = models.DateTimeField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
managed = False
|
||||||
|
db_table = 'footers'
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ class GuideSerializer(TransferSerializerMixin):
|
||||||
class GuideFilterSerializer(TransferSerializerMixin):
|
class GuideFilterSerializer(TransferSerializerMixin):
|
||||||
id = serializers.IntegerField()
|
id = serializers.IntegerField()
|
||||||
year = serializers.CharField(allow_null=True)
|
year = serializers.CharField(allow_null=True)
|
||||||
establishment_type = serializers.CharField(allow_null=True)
|
type = serializers.CharField(allow_null=True, source='establishment_type')
|
||||||
countries = serializers.CharField(allow_null=True)
|
countries = serializers.CharField(allow_null=True)
|
||||||
regions = serializers.CharField(allow_null=True)
|
regions = serializers.CharField(allow_null=True)
|
||||||
subregions = serializers.CharField(allow_null=True)
|
subregions = serializers.CharField(allow_null=True)
|
||||||
|
|
@ -86,7 +86,7 @@ class GuideFilterSerializer(TransferSerializerMixin):
|
||||||
fields = (
|
fields = (
|
||||||
'id',
|
'id',
|
||||||
'year',
|
'year',
|
||||||
'establishment_type',
|
'type',
|
||||||
'countries',
|
'countries',
|
||||||
'regions',
|
'regions',
|
||||||
'subregions',
|
'subregions',
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
"""Custom middleware."""
|
"""Custom middlewares."""
|
||||||
from django.utils import translation, timezone
|
from django.utils import translation, timezone
|
||||||
from account.models import User
|
|
||||||
|
|
||||||
|
from account.models import User
|
||||||
from configuration.models import TranslationSettings
|
from configuration.models import TranslationSettings
|
||||||
|
from main.methods import determine_user_city
|
||||||
from translation.models import Language
|
from translation.models import Language
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -18,7 +19,11 @@ def user_last_visit(get_response):
|
||||||
def middleware(request):
|
def middleware(request):
|
||||||
response = get_response(request)
|
response = get_response(request)
|
||||||
if request.user.is_authenticated:
|
if request.user.is_authenticated:
|
||||||
User.objects.filter(pk=request.user.pk).update(last_login=timezone.now())
|
User.objects.filter(pk=request.user.pk).update(**{
|
||||||
|
'last_login': timezone.now(),
|
||||||
|
'locale': request.locale,
|
||||||
|
'city': determine_user_city(request),
|
||||||
|
})
|
||||||
return response
|
return response
|
||||||
return middleware
|
return middleware
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ from rest_framework_simplejwt.tokens import AccessToken
|
||||||
from account.models import UserRole, Role
|
from account.models import UserRole, Role
|
||||||
from authorization.models import JWTRefreshToken
|
from authorization.models import JWTRefreshToken
|
||||||
from utils.tokens import GMRefreshToken
|
from utils.tokens import GMRefreshToken
|
||||||
|
from establishment.models import EstablishmentSubType
|
||||||
|
from location.models import Address
|
||||||
|
|
||||||
class IsAuthenticatedAndTokenIsValid(permissions.BasePermission):
|
class IsAuthenticatedAndTokenIsValid(permissions.BasePermission):
|
||||||
"""
|
"""
|
||||||
|
|
@ -56,8 +57,9 @@ class IsGuest(permissions.IsAuthenticatedOrReadOnly):
|
||||||
"""
|
"""
|
||||||
Object-level permission to only allow owners of an object to edit it.
|
Object-level permission to only allow owners of an object to edit it.
|
||||||
"""
|
"""
|
||||||
|
SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS')
|
||||||
def has_permission(self, request, view):
|
def has_permission(self, request, view):
|
||||||
|
|
||||||
rules = [
|
rules = [
|
||||||
request.user.is_superuser,
|
request.user.is_superuser,
|
||||||
request.method in permissions.SAFE_METHODS
|
request.method in permissions.SAFE_METHODS
|
||||||
|
|
@ -306,7 +308,6 @@ class IsEstablishmentManager(IsStandardUser):
|
||||||
rules = [
|
rules = [
|
||||||
# special!
|
# special!
|
||||||
super().has_permission(request, view)
|
super().has_permission(request, view)
|
||||||
# super().has_object_permission(request, view, obj)
|
|
||||||
]
|
]
|
||||||
|
|
||||||
role = Role.objects.filter(role=Role.ESTABLISHMENT_MANAGER) \
|
role = Role.objects.filter(role=Role.ESTABLISHMENT_MANAGER) \
|
||||||
|
|
@ -319,7 +320,6 @@ class IsEstablishmentManager(IsStandardUser):
|
||||||
).exists(),
|
).exists(),
|
||||||
# special!
|
# special!
|
||||||
super().has_permission(request, view)
|
super().has_permission(request, view)
|
||||||
# super().has_object_permission(request, view, obj)
|
|
||||||
]
|
]
|
||||||
|
|
||||||
return any(rules)
|
return any(rules)
|
||||||
|
|
@ -368,7 +368,7 @@ class IsRestaurantReviewer(IsStandardUser):
|
||||||
# and request.user.email_confirmed,
|
# and request.user.email_confirmed,
|
||||||
if hasattr(request.data, 'user') and hasattr(request.data, 'object_id'):
|
if hasattr(request.data, 'user') and hasattr(request.data, 'object_id'):
|
||||||
role = Role.objects.filter(role=Role.RESTAURANT_REVIEWER) \
|
role = Role.objects.filter(role=Role.RESTAURANT_REVIEWER) \
|
||||||
.first() # 'Comments moderator'
|
.first()
|
||||||
|
|
||||||
rules = [
|
rules = [
|
||||||
UserRole.objects.filter(user=request.user, role=role,
|
UserRole.objects.filter(user=request.user, role=role,
|
||||||
|
|
@ -394,3 +394,58 @@ class IsRestaurantReviewer(IsStandardUser):
|
||||||
]
|
]
|
||||||
|
|
||||||
return any(rules)
|
return any(rules)
|
||||||
|
|
||||||
|
|
||||||
|
class IsWineryReviewer(IsStandardUser):
|
||||||
|
|
||||||
|
def has_permission(self, request, view):
|
||||||
|
rules = [
|
||||||
|
super().has_permission(request, view)
|
||||||
|
]
|
||||||
|
|
||||||
|
if 'type_id' in request.data and 'address_id' in request.data and request.user:
|
||||||
|
countries = Address.objects.filter(id=request.data['address_id'])
|
||||||
|
|
||||||
|
est = EstablishmentSubType.objects.filter(establishment_type_id=request.data['type_id'])
|
||||||
|
if est.exists():
|
||||||
|
role = Role.objects.filter(establishment_subtype_id__in=[type.id for type in est],
|
||||||
|
role=Role.WINERY_REVIEWER,
|
||||||
|
country_id__in=[country.id for country in countries]) \
|
||||||
|
.first()
|
||||||
|
|
||||||
|
rules.append(
|
||||||
|
UserRole.objects.filter(user=request.user, role=role).exists()
|
||||||
|
)
|
||||||
|
|
||||||
|
return any(rules)
|
||||||
|
|
||||||
|
def has_object_permission(self, request, view, obj):
|
||||||
|
rules = [
|
||||||
|
super().has_object_permission(request, view, obj)
|
||||||
|
]
|
||||||
|
|
||||||
|
if hasattr(obj, 'type_id') or hasattr(obj, 'establishment_type_id'):
|
||||||
|
type_id: int
|
||||||
|
if hasattr(obj, 'type_id'):
|
||||||
|
type_id = obj.type_id
|
||||||
|
else:
|
||||||
|
type_id = obj.establishment_type_id
|
||||||
|
|
||||||
|
est = EstablishmentSubType.objects.filter(establishment_type_id=type_id)
|
||||||
|
role = Role.objects.filter(role=Role.WINERY_REVIEWER,
|
||||||
|
establishment_subtype_id__in=[id for type.id in est],
|
||||||
|
country_id=obj.country_id).first()
|
||||||
|
|
||||||
|
object_id: int
|
||||||
|
if hasattr(obj, 'object_id'):
|
||||||
|
object_id = obj.object_id
|
||||||
|
else:
|
||||||
|
object_id = obj.establishment_id
|
||||||
|
|
||||||
|
rules = [
|
||||||
|
UserRole.objects.filter(user=request.user, role=role,
|
||||||
|
establishment_id=object_id
|
||||||
|
).exists(),
|
||||||
|
super().has_object_permission(request, view, obj)
|
||||||
|
]
|
||||||
|
return any(rules)
|
||||||
|
|
@ -56,17 +56,18 @@ class TranslateFieldTests(BaseTestCase):
|
||||||
start=datetime.now(pytz.utc) + timedelta(hours=-13),
|
start=datetime.now(pytz.utc) + timedelta(hours=-13),
|
||||||
end=datetime.now(pytz.utc) + timedelta(hours=13),
|
end=datetime.now(pytz.utc) + timedelta(hours=13),
|
||||||
news_type=self.news_type,
|
news_type=self.news_type,
|
||||||
slug='test',
|
slugs={'en-GB': 'test'},
|
||||||
state=News.PUBLISHED,
|
state=News.PUBLISHED,
|
||||||
country=self.country_ru,
|
country=self.country_ru,
|
||||||
)
|
)
|
||||||
|
self.slug = next(iter(self.news_item.slugs.values()))
|
||||||
self.news_item.save()
|
self.news_item.save()
|
||||||
|
|
||||||
def test_model_field(self):
|
def test_model_field(self):
|
||||||
self.assertTrue(hasattr(self.news_item, "title_translated"))
|
self.assertTrue(hasattr(self.news_item, "title_translated"))
|
||||||
|
|
||||||
def test_read_locale(self):
|
def test_read_locale(self):
|
||||||
response = self.client.get(f"/api/web/news/slug/{self.news_item.slug}/", format='json')
|
response = self.client.get(f"/api/web/news/slug/{self.slug}/", format='json')
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
news_data = response.json()
|
news_data = response.json()
|
||||||
self.assertIn("title_translated", news_data)
|
self.assertIn("title_translated", news_data)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ from rest_framework.response import Response
|
||||||
|
|
||||||
from gallery.tasks import delete_image
|
from gallery.tasks import delete_image
|
||||||
from search_indexes.documents import es_update
|
from search_indexes.documents import es_update
|
||||||
|
from news.models import News
|
||||||
|
|
||||||
|
|
||||||
# JWT
|
# JWT
|
||||||
|
|
@ -124,6 +125,8 @@ class BaseCreateDestroyMixinView(generics.CreateAPIView, generics.DestroyAPIView
|
||||||
lookup_field = 'slug'
|
lookup_field = 'slug'
|
||||||
|
|
||||||
def get_base_object(self):
|
def get_base_object(self):
|
||||||
|
if 'slugs' in [f.name for f in self._model._meta.get_fields()]: # slugs instead of `slug`
|
||||||
|
return get_object_or_404(self._model, slugs__values__contains=[self.kwargs['slug']])
|
||||||
return get_object_or_404(self._model, slug=self.kwargs['slug'])
|
return get_object_or_404(self._model, slug=self.kwargs['slug'])
|
||||||
|
|
||||||
def es_update_base_object(self):
|
def es_update_base_object(self):
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ services:
|
||||||
- "5436:5432"
|
- "5436:5432"
|
||||||
volumes:
|
volumes:
|
||||||
- gm-db:/var/lib/postgresql/data/
|
- gm-db:/var/lib/postgresql/data/
|
||||||
|
- .:/code
|
||||||
|
|
||||||
|
|
||||||
elasticsearch:
|
elasticsearch:
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,15 @@
|
||||||
./manage.py transfer --fill_city_gallery
|
./manage.py transfer --fill_city_gallery
|
||||||
./manage.py transfer -l
|
./manage.py transfer -l
|
||||||
./manage.py transfer --product
|
./manage.py transfer --product
|
||||||
|
# Утеряна четкая связь между последовательностью миграций для импорта тегов продуктов,
|
||||||
|
# что может привести к удалению уже импортированных тегов командой выше.
|
||||||
./manage.py transfer --souvenir
|
./manage.py transfer --souvenir
|
||||||
./manage.py transfer --establishment_note
|
./manage.py transfer --establishment_note
|
||||||
./manage.py transfer --product_note
|
./manage.py transfer --product_note
|
||||||
|
./manage.py transfer --check_serial_number
|
||||||
./manage.py transfer --wine_characteristics
|
./manage.py transfer --wine_characteristics
|
||||||
./manage.py transfer --inquiries
|
./manage.py transfer --inquiries
|
||||||
./manage.py transfer --assemblage
|
./manage.py transfer --assemblage
|
||||||
./manage.py transfer --purchased_plaques
|
./manage.py transfer --purchased_plaques
|
||||||
./manage.py rm_empty_images
|
./manage.py rm_empty_images
|
||||||
|
./manage.py add_artisan_subtype # добавляет подтипы для заведений артизанов
|
||||||
|
|
@ -48,6 +48,7 @@ CONTRIB_APPS = [
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'django.contrib.gis',
|
'django.contrib.gis',
|
||||||
|
'django.contrib.postgres',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user