Merge branch 'develop' into feature/in_favorites

This commit is contained in:
evgeniy-st 2019-11-21 18:17:35 +03:00
commit 04d6412f39
50 changed files with 815 additions and 181 deletions

View File

@ -63,7 +63,7 @@ class SendConfirmationEmailView(generics.GenericAPIView):
return Response(status=status.HTTP_200_OK)
class ConfirmEmailView(JWTGenericViewMixin):
class ConfirmEmailView(JWTGenericViewMixin, generics.GenericAPIView):
"""View for confirm changing email"""
permission_classes = (permissions.AllowAny,)

View File

@ -33,7 +33,7 @@ class PasswordResetView(generics.GenericAPIView):
return Response(status=status.HTTP_200_OK)
class PasswordResetConfirmView(JWTGenericViewMixin):
class PasswordResetConfirmView(JWTGenericViewMixin, generics.GenericAPIView):
"""View for confirmation new password"""
serializer_class = serializers.PasswordResetConfirmSerializer
permission_classes = (permissions.AllowAny,)

View File

@ -45,7 +45,7 @@ class AdvertisementPageListCreateView(AdvertisementBackOfficeViewMixin,
ad_qs = Advertisement.objects.all()
filtered_ad_qs = self.filter_queryset(ad_qs)
ad = get_object_or_404(filtered_ad_qs, pk=self.kwargs['pk'])
ad = get_object_or_404(filtered_ad_qs, pk=self.kwargs.get('pk'))
# May raise a permission denied
self.check_object_permissions(self.request, ad)
@ -68,8 +68,8 @@ class AdvertisementPageRUDView(AdvertisementBackOfficeViewMixin,
ad_qs = Advertisement.objects.all()
filtered_ad_qs = self.filter_queryset(ad_qs)
ad = get_object_or_404(filtered_ad_qs, pk=self.kwargs['ad_pk'])
page = get_object_or_404(ad.pages.all(), pk=self.kwargs['page_pk'])
ad = get_object_or_404(filtered_ad_qs, pk=self.kwargs.get('ad_pk'))
page = get_object_or_404(ad.pages.all(), pk=self.kwargs.get('page_pk'))
# May raise a permission denied
self.check_object_permissions(self.request, page)

View File

@ -71,7 +71,7 @@ class OAuth2ViewMixin(CsrfExemptMixin, OAuthLibMixin, BaseOAuth2ViewMixin):
# Sign in via Facebook
class OAuth2SignUpView(OAuth2ViewMixin, JWTGenericViewMixin):
class OAuth2SignUpView(OAuth2ViewMixin, JWTGenericViewMixin, generics.GenericAPIView):
"""
Implements an endpoint to convert a provider token to an access token
@ -142,7 +142,7 @@ class SignUpView(generics.GenericAPIView):
return Response(status=status.HTTP_201_CREATED)
class ConfirmationEmailView(JWTGenericViewMixin):
class ConfirmationEmailView(JWTGenericViewMixin, generics.GenericAPIView):
"""View for confirmation email"""
permission_classes = (permissions.AllowAny, )
@ -174,7 +174,7 @@ class ConfirmationEmailView(JWTGenericViewMixin):
# Login by username|email + password
class LoginByUsernameOrEmailView(JWTGenericViewMixin):
class LoginByUsernameOrEmailView(JWTGenericViewMixin, generics.GenericAPIView):
"""Login by email and password"""
permission_classes = (permissions.AllowAny,)
serializer_class = serializers.LoginByUsernameOrEmailSerializer
@ -197,7 +197,7 @@ class LoginByUsernameOrEmailView(JWTGenericViewMixin):
# Logout
class LogoutView(JWTGenericViewMixin):
class LogoutView(JWTGenericViewMixin, generics.GenericAPIView):
"""Logout user"""
permission_classes = (IsAuthenticatedAndTokenIsValid, )
@ -215,7 +215,7 @@ class LogoutView(JWTGenericViewMixin):
# Refresh token
class RefreshTokenView(JWTGenericViewMixin):
class RefreshTokenView(JWTGenericViewMixin, generics.GenericAPIView):
"""Refresh access_token"""
permission_classes = (permissions.AllowAny, )
serializer_class = serializers.RefreshTokenSerializer

View File

@ -1,8 +1,14 @@
from rest_framework import serializers
from collection import models
from collection.serializers.common import CollectionBaseSerializer
from establishment.models import Establishment
from location.models import Country
from location.serializers import CountrySimpleSerializer
from collection.serializers.common import CollectionBaseSerializer
from collection import models
from product.models import Product
from utils.exceptions import (
BindingObjectNotFound, RemovedBindingObjectNotFound, ObjectAlreadyAdded
)
class CollectionBackOfficeSerializer(CollectionBaseSerializer):
@ -31,3 +37,54 @@ class CollectionBackOfficeSerializer(CollectionBaseSerializer):
'start',
'end',
]
class CollectionBindObjectSerializer(serializers.Serializer):
"""Serializer for binding collection and objects"""
ESTABLISHMENT = 'establishment'
PRODUCT = 'product'
TYPE_CHOICES = (
(ESTABLISHMENT, 'Establishment'),
(PRODUCT, 'Product'),
)
type = serializers.ChoiceField(TYPE_CHOICES)
object_id = serializers.IntegerField()
def validate(self, attrs):
view = self.context.get('view')
request = self.context.get('request')
obj_type = attrs.get('type')
obj_id = attrs.get('object_id')
collection = view.get_object()
attrs['collection'] = collection
if obj_type == self.ESTABLISHMENT:
establishment = Establishment.objects.filter(pk=obj_id).\
first()
if not establishment:
raise BindingObjectNotFound()
if request.method == 'POST' and collection.establishments.\
filter(pk=establishment.pk).exists():
raise ObjectAlreadyAdded()
if request.method == 'DELETE' and not collection.\
establishments.filter(pk=establishment.pk).\
exists():
raise RemovedBindingObjectNotFound()
attrs['related_object'] = establishment
elif obj_type == self.PRODUCT:
product = Product.objects.filter(pk=obj_id).first()
if not product:
raise BindingObjectNotFound()
if request.method == 'POST' and collection.products.\
filter(pk=product.pk).exists():
raise ObjectAlreadyAdded()
if request.method == 'DELETE' and not collection.products.\
filter(pk=product.pk).exists():
raise RemovedBindingObjectNotFound()
attrs['related_object'] = product
return attrs

View File

@ -1,11 +1,10 @@
"""Collection common urlpaths."""
from django.urls import path
from rest_framework.routers import SimpleRouter
from collection.views import back as views
app_name = 'collection'
router = SimpleRouter()
router.register(r'', views.CollectionBackOfficeViewSet)
urlpatterns = [
path('', views.CollectionListCreateView.as_view(), name='list-create'),
path('<int:pk>/', views.CollectionRUDView.as_view(), name='rud-collection'),
]
urlpatterns = router.urls

View File

@ -1,19 +1,49 @@
from rest_framework import generics, permissions
from rest_framework import permissions
from rest_framework import viewsets, mixins
from collection import models
from collection.serializers import back
from collection.serializers import back as serializers
from utils.views import BindObjectMixin
class CollectionListCreateView(generics.ListCreateAPIView):
"""Collection list-create view."""
class CollectionViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
"""ViewSet for Collection model."""
pagination_class = None
permission_classes = (permissions.AllowAny,)
queryset = models.Collection.objects.all()
serializer_class = back.CollectionBackOfficeSerializer
# todo: conf. permissions by TT
permission_classes = (permissions.IsAuthenticated, )
serializer_class = serializers.CollectionBackOfficeSerializer
class CollectionRUDView(generics.RetrieveUpdateDestroyAPIView):
"""Collection list-create view."""
class CollectionBackOfficeViewSet(mixins.CreateModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.RetrieveModelMixin,
BindObjectMixin,
CollectionViewSet):
"""ViewSet for Collection model for BackOffice users."""
permission_classes = (permissions.IsAuthenticated,)
queryset = models.Collection.objects.all()
serializer_class = back.CollectionBackOfficeSerializer
# todo: conf. permissions by TT
permission_classes = (permissions.IsAuthenticated, )
serializer_class = serializers.CollectionBackOfficeSerializer
bind_object_serializer_class = serializers.CollectionBindObjectSerializer
def perform_binding(self, serializer):
data = serializer.validated_data
collection = data.pop('collection')
obj_type = data.get('type')
related_object = data.get('related_object')
if obj_type == self.bind_object_serializer_class.ESTABLISHMENT:
collection.establishments.add(related_object)
elif obj_type == self.bind_object_serializer_class.PRODUCT:
collection.products.add(related_object)
def perform_unbinding(self, serializer):
data = serializer.validated_data
collection = data.pop('collection')
obj_type = data.get('type')
related_object = data.get('related_object')
if obj_type == self.bind_object_serializer_class.ESTABLISHMENT:
collection.establishments.remove(related_object)
elif obj_type == self.bind_object_serializer_class.PRODUCT:
collection.products.remove(related_object)

View File

@ -7,7 +7,7 @@ from comment.models import Comment
from utils.admin import BaseModelAdminMixin
from establishment import models
from main.models import Award
from product.models import Product
from product.models import Product, PurchasedProduct
from review import models as review_models
@ -69,13 +69,19 @@ class EstablishmentNote(admin.TabularInline):
extra = 0
class PurchasedProduct(admin.TabularInline):
model = PurchasedProduct
extra = 0
@admin.register(models.Establishment)
class EstablishmentAdmin(BaseModelAdminMixin, admin.ModelAdmin):
"""Establishment admin."""
list_display = ['id', '__str__', 'image_tag', ]
search_fields = ['id', 'name', 'index_name', 'slug']
list_filter = ['public_mark', 'toque_number']
inlines = [GalleryImageInline, CompanyInline, EstablishmentNote]
inlines = [GalleryImageInline, CompanyInline, EstablishmentNote,
PurchasedProduct]
# inlines = [
# AwardInline, ContactPhoneInline, ContactEmailInline,

View File

@ -0,0 +1,19 @@
# Generated by Django 2.2.7 on 2019-11-20 12:49
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('product', '0018_purchasedproduct'),
('establishment', '0064_auto_20191119_1546'),
]
operations = [
migrations.AddField(
model_name='establishment',
name='purchased_products',
field=models.ManyToManyField(blank=True, help_text='Attribute from legacy db.\nMust be deleted after the implementation of the market.', related_name='establishments', through='product.PurchasedProduct', to='product.Product', verbose_name='purchased plaques'),
),
]

View File

@ -399,6 +399,13 @@ class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin,
currency = models.ForeignKey(Currency, blank=True, null=True, default=None,
on_delete=models.PROTECT,
verbose_name=_('currency'))
purchased_products = models.ManyToManyField('product.Product', blank=True,
through='product.PurchasedProduct',
related_name='establishments',
verbose_name=_('purchased plaques'),
help_text=_('Attribute from legacy db.\n'
'Must be deleted after the '
'implementation of the market.'))
objects = EstablishmentQuerySet.as_manager()
@ -411,6 +418,12 @@ class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin,
def __str__(self):
return f'id:{self.id}-{self.name}'
def clean_fields(self, exclude=None):
super().clean_fields(exclude)
if self.purchased_products.filter(product_type__index_name='souvenir').exists():
raise ValidationError(
_('Only souvenirs.'))
def delete(self, using=None, keep_parents=False):
"""Overridden delete method"""
# Delete all related companies

View File

@ -4,7 +4,9 @@ from django.db.models import Q, F
from establishment.models import Establishment
from location.models import Address
from transfer.models import Establishments, Dishes, EstablishmentNotes
from product.models import PurchasedProduct, Product
from transfer.models import Establishments, Dishes, EstablishmentNotes, \
EstablishmentMerchandises
from transfer.serializers.establishment import EstablishmentSerializer, \
EstablishmentNoteSerializer
from transfer.serializers.plate import PlateSerializer
@ -140,6 +142,43 @@ def transfer_establishment_note():
pprint(f"transfer_establishment_note errors: {errors}")
def transfer_purchased_plaques():
update_products_counter = 0
already_updated_counter = 0
not_existed_establishment_counter = 0
purchased = EstablishmentMerchandises.objects.values_list(
'establishment_id',
'merchandise__vintage',
'gifted',
'quantity'
)
for old_est_id, vintage, gifted, quantity in purchased:
establishment_qs = Establishment.objects.filter(old_id=old_est_id)
product_qs = Product.objects.filter(name='Plaque restaurants',
vintage=vintage)
if establishment_qs.exists() and product_qs.exists():
product = product_qs.first()
establishment = establishment_qs.first()
purchases, created = PurchasedProduct.objects.get_or_create(
establishment=establishment,
product=product,
is_gifted=gifted,
quantity=quantity
)
if created:
update_products_counter += 1
else:
already_updated_counter += 1
else:
not_existed_establishment_counter += 1
print(f'Updated products: {update_products_counter}\n'
f'Already updated: {already_updated_counter}\n'
f'Not existed establishment: {not_existed_establishment_counter}')
data_types = {
"establishment": [
transfer_establishment,
@ -149,4 +188,7 @@ data_types = {
transfer_establishment_addresses
],
"menu": [transfer_menu],
"purchased_plaques": [
transfer_purchased_plaques
],
}

View File

@ -6,6 +6,7 @@ from establishment import filters, models, serializers
from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer
from utils.permissions import IsCountryAdmin, IsEstablishmentManager
from utils.views import CreateDestroyGalleryViewMixin
from timetable.models import Timetable
from rest_framework import status
from rest_framework.response import Response
@ -36,13 +37,14 @@ class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView):
class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView):
"""Establishment schedule RUD view"""
serializer_class = ScheduleRUDSerializer
permission_classes = [IsEstablishmentManager]
def get_object(self):
"""
Returns the object the view is displaying.
"""
establishment_pk = self.kwargs['pk']
schedule_id = self.kwargs['schedule_id']
establishment_pk = self.kwargs.get('pk')
schedule_id = self.kwargs.get('schedule_id')
establishment = get_object_or_404(klass=models.Establishment.objects.all(),
pk=establishment_pk)
@ -59,6 +61,8 @@ class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView):
class EstablishmentScheduleCreateView(generics.CreateAPIView):
"""Establishment schedule Create view"""
serializer_class = ScheduleCreateSerializer
queryset = Timetable.objects.all()
permission_classes = [IsEstablishmentManager]
class MenuListCreateView(generics.ListCreateAPIView):
@ -200,8 +204,9 @@ class EstablishmentGalleryCreateDestroyView(EstablishmentMixinViews,
"""
establishment_qs = self.filter_queryset(self.get_queryset())
establishment = get_object_or_404(establishment_qs, pk=self.kwargs['pk'])
gallery = get_object_or_404(establishment.establishment_gallery, image_id=self.kwargs['image_id'])
establishment = get_object_or_404(establishment_qs, pk=self.kwargs.get('pk'))
gallery = get_object_or_404(establishment.establishment_gallery,
image_id=self.kwargs.get('image_id'))
# May raise a permission denied
self.check_object_permissions(self.request, gallery)
@ -217,7 +222,7 @@ class EstablishmentGalleryListView(EstablishmentMixinViews,
def get_object(self):
"""Override get_object method."""
qs = super(EstablishmentGalleryListView, self).get_queryset()
establishment = get_object_or_404(qs, pk=self.kwargs['pk'])
establishment = get_object_or_404(qs, pk=self.kwargs.get('pk'))
# May raise a permission denied
self.check_object_permissions(self.request, establishment)
@ -240,7 +245,7 @@ class EstablishmentCompanyListCreateView(EstablishmentMixinViews,
establishment_qs = models.Establishment.objects.all()
filtered_ad_qs = self.filter_queryset(establishment_qs)
establishment = get_object_or_404(filtered_ad_qs, pk=self.kwargs['pk'])
establishment = get_object_or_404(filtered_ad_qs, pk=self.kwargs.get('pk'))
# May raise a permission denied
self.check_object_permissions(self.request, establishment)
@ -263,8 +268,8 @@ class EstablishmentCompanyRUDView(EstablishmentMixinViews,
establishment_qs = models.Establishment.objects.all()
filtered_ad_qs = self.filter_queryset(establishment_qs)
establishment = get_object_or_404(filtered_ad_qs, pk=self.kwargs['pk'])
company = get_object_or_404(establishment.companies.all(), pk=self.kwargs['company_pk'])
establishment = get_object_or_404(filtered_ad_qs, pk=self.kwargs.get('pk'))
company = get_object_or_404(establishment.companies.all(), pk=self.kwargs.get('company_pk'))
# May raise a permission denied
self.check_object_permissions(self.request, company)
@ -273,7 +278,7 @@ class EstablishmentCompanyRUDView(EstablishmentMixinViews,
class EstablishmentNoteListCreateView(EstablishmentMixinViews,
generics.ListCreateAPIView):
generics.ListCreateAPIView):
"""Retrieve|Update|Destroy establishment note view."""
serializer_class = serializers.EstablishmentNoteListCreateSerializer
@ -283,7 +288,7 @@ class EstablishmentNoteListCreateView(EstablishmentMixinViews,
establishment_qs = models.Establishment.objects.all()
filtered_establishment_qs = self.filter_queryset(establishment_qs)
establishment = get_object_or_404(filtered_establishment_qs, pk=self.kwargs['pk'])
establishment = get_object_or_404(filtered_establishment_qs, pk=self.kwargs.get('pk'))
# May raise a permission denied
self.check_object_permissions(self.request, establishment)
@ -306,7 +311,7 @@ class EstablishmentNoteRUDView(EstablishmentMixinViews,
establishment_qs = models.Establishment.objects.all()
filtered_establishment_qs = self.filter_queryset(establishment_qs)
establishment = get_object_or_404(filtered_establishment_qs, pk=self.kwargs['pk'])
establishment = get_object_or_404(filtered_establishment_qs, pk=self.kwargs.get('pk'))
note = get_object_or_404(establishment.notes.all(), pk=self.kwargs['note_pk'])
# May raise a permission denied

View File

@ -1,8 +1,8 @@
from transfer.serializers import location as location_serializers
from transfer import models as transfer_models
from location.models import Country
from location.models import Country, CityGallery, City
from gallery.models import Image
from pprint import pprint
from requests import get
@ -179,6 +179,42 @@ def update_flags():
query.save()
def transfer_city_gallery():
created_counter = 0
cities_not_exists = {}
gallery_obj_exists_counter = 0
city_gallery = transfer_models.CityPhotos.objects.exclude(city__isnull=True) \
.exclude(city__country_code_2__isnull=True) \
.exclude(city__country_code_2__iexact='') \
.exclude(city__region_code__isnull=True) \
.exclude(city__region_code__iexact='') \
.values_list('city_id', 'attachment_suffix_url')
for old_city_id, image_suffix_url in city_gallery:
city = City.objects.filter(old_id=old_city_id)
if city.exists():
city = city.first()
image, _ = Image.objects.get_or_create(image=image_suffix_url,
defaults={
'image': image_suffix_url,
'orientation': Image.HORIZONTAL,
'title': f'{city.name} - {image_suffix_url}',
})
city_gallery, created = CityGallery.objects.get_or_create(image=image,
city=city,
is_main=True)
if created:
created_counter += 1
else:
gallery_obj_exists_counter += 1
else:
cities_not_exists.update({'city_old_id': old_city_id})
print(f'Created: {created_counter}\n'
f'City not exists: {cities_not_exists}\n'
f'Already added: {gallery_obj_exists_counter}')
data_types = {
"dictionaries": [
transfer_countries,
@ -192,4 +228,5 @@ data_types = {
"update_country_flag": [
update_flags
],
"fill_city_gallery": [transfer_city_gallery]
}

View File

@ -51,8 +51,8 @@ class CityGalleryCreateDestroyView(common.CityViewMixin,
"""
city_qs = self.filter_queryset(self.get_queryset())
city = get_object_or_404(city_qs, pk=self.kwargs['pk'])
gallery = get_object_or_404(city.city_gallery, image_id=self.kwargs['image_id'])
city = get_object_or_404(city_qs, pk=self.kwargs.get('pk'))
gallery = get_object_or_404(city.city_gallery, image_id=self.kwargs.get('image_id'))
# May raise a permission denied
self.check_object_permissions(self.request, gallery)

40
apps/main/filters.py Normal file
View File

@ -0,0 +1,40 @@
from django.core.validators import EMPTY_VALUES
from django_filters import rest_framework as filters
from main import models
class AwardFilter(filters.FilterSet):
"""Award filter set."""
establishment_id = filters.NumberFilter(field_name='object_id', )
product_id = filters.NumberFilter(field_name='object_id', )
employee_id = filters.NumberFilter(field_name='object_id', )
class Meta:
"""Meta class."""
model = models.Award
fields = (
'establishment_id',
'product_id',
'employee_id',
'state',
'award_type',
'vintage_year',
)
def by_establishment_id(self, queryset, name, value):
if value not in EMPTY_VALUES:
return queryset.by_establishment_id(value, content_type='establishment')
return queryset
def by_product_id(self, queryset, name, value):
if value not in EMPTY_VALUES:
return queryset.by_product_id(value, content_type='product')
return queryset
def by_employee_id(self, queryset, name, value):
if value not in EMPTY_VALUES:
return queryset.by_employee_id(value, content_type='establishmentemployee')
return queryset

View File

@ -153,7 +153,7 @@ class Award(TranslatedFieldsMixin, URLImageMixin, models.Model):
PUBLISHED = 1
STATE_CHOICES = (
(WAITING,'waiting'),
(WAITING, 'waiting'),
(PUBLISHED, 'published')
)

View File

@ -38,7 +38,7 @@ class SiteFeatureSerializer(serializers.ModelSerializer):
'route',
'source',
'nested',
)
)
class CurrencySerializer(ProjectModelSerializer):
@ -145,6 +145,19 @@ class AwardSerializer(AwardBaseSerializer):
fields = AwardBaseSerializer.Meta.fields + ['award_type', ]
class BackAwardSerializer(AwardBaseSerializer):
"""Award serializer."""
class Meta:
model = models.Award
fields = AwardBaseSerializer.Meta.fields + [
'award_type',
'state',
'content_type',
'object_id',
]
class CarouselListSerializer(serializers.ModelSerializer):
"""Serializer for retrieving list of carousel items."""
@ -202,4 +215,4 @@ class PageTypeBaseSerializer(serializers.ModelSerializer):
fields = [
'id',
'name',
]
]

View File

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

View File

@ -0,0 +1,73 @@
from http.cookies import SimpleCookie
from django.contrib.contenttypes.models import ContentType
from rest_framework import status
from rest_framework.test import APITestCase
from account.models import User
from location.models import Country
from main.models import Award, AwardType
class AwardTestCase(APITestCase):
def setUp(self):
self.user = User.objects.create_user(
username='alex',
email='alex@mail.com',
password='alex_password',
is_staff=True,
)
# get tokens
tokens = User.create_jwt_tokens(self.user)
self.client.cookies = SimpleCookie(
{'access_token': tokens.get('access_token'),
'refresh_token': tokens.get('refresh_token')})
self.country_ru = Country.objects.create(
name={'en-GB': 'Russian'},
code='RU',
)
self.content_type = ContentType.objects.get(app_label="establishment", model="establishment")
self.award_type = AwardType.objects.create(
country=self.country_ru,
name="Test award type",
)
self.award = Award.objects.create(
award_type=self.award_type,
vintage_year='2017',
state=Award.PUBLISHED,
object_id=1,
content_type_id=1,
)
def test_award_CRUD(self):
response = self.client.get('/api/back/main/awards/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
data = {
'award_type': self.award_type.pk,
'state': 1,
'object_id': 1,
'content_type': 1,
}
response = self.client.post('/api/back/main/awards/', data=data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
response = self.client.get(f'/api/back/main/awards/{self.award.id}/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
update_data = {
'vintage_year': '2019'
}
response = self.client.patch(f'/api/back/main/awards/{self.award.id}/', data=update_data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
response = self.client.delete(f'/api/back/main/awards/{self.award.id}/')
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)

11
apps/main/urls/back.py Normal file
View File

@ -0,0 +1,11 @@
"""Back main URLs"""
from django.urls import path
from main.views import back as views
app_name = 'main'
urlpatterns = [
path('awards/', views.AwardLstView.as_view(), name='awards-list-create'),
path('awards/<int:id>/', views.AwardRUDView.as_view(), name='awards-rud'),
]

21
apps/main/views/back.py Normal file
View File

@ -0,0 +1,21 @@
from rest_framework import generics, permissions
from main import serializers
from main.filters import AwardFilter
from main.models import Award
class AwardLstView(generics.ListCreateAPIView):
"""Award list create view."""
queryset = Award.objects.all()
serializer_class = serializers.BackAwardSerializer
permission_classes = (permissions.IsAdminUser,)
filterset_class = AwardFilter
class AwardRUDView(generics.RetrieveUpdateDestroyAPIView):
"""Award RUD view."""
queryset = Award.objects.all()
serializer_class = serializers.BackAwardSerializer
permission_classes = (permissions.IsAdminUser,)
lookup_field = 'id'

View File

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

View File

@ -20,6 +20,16 @@ class NewsListFilterSet(filters.FilterSet):
tag_value__in = filters.CharFilter(method='in_tags')
type = filters.CharFilter(method='by_type')
state = filters.NumberFilter()
SORT_BY_CREATED_CHOICE = "created"
SORT_BY_START_CHOICE = "start"
SORT_BY_CHOICES = (
(SORT_BY_CREATED_CHOICE, "created"),
(SORT_BY_START_CHOICE, "start"),
)
sort_by = filters.ChoiceFilter(method='sort_by_field', choices=SORT_BY_CHOICES)
class Meta:
"""Meta class"""
model = models.News
@ -29,6 +39,8 @@ class NewsListFilterSet(filters.FilterSet):
'tag_group',
'tag_value__exclude',
'tag_value__in',
'state',
'sort_by',
)
def in_tags(self, queryset, name, value):
@ -58,3 +70,6 @@ class NewsListFilterSet(filters.FilterSet):
return queryset.filter(news_type__name=value)
else:
return queryset
def sort_by_field(self, queryset, name, value):
return queryset.order_by(f'-{value}')

View File

@ -162,6 +162,7 @@ class NewsDetailWebSerializer(NewsDetailSerializer):
class NewsBackOfficeBaseSerializer(NewsBaseSerializer):
"""News back office base serializer."""
is_published = serializers.BooleanField(source='is_publish', read_only=True)
class Meta(NewsBaseSerializer.Meta):
"""Meta class."""
@ -169,6 +170,7 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer):
fields = NewsBaseSerializer.Meta.fields + (
'title',
'subtitle',
'is_published',
)

View File

@ -108,8 +108,8 @@ class NewsBackOfficeGalleryCreateDestroyView(NewsBackOfficeMixinView,
"""
news_qs = self.filter_queryset(self.get_queryset())
news = get_object_or_404(news_qs, pk=self.kwargs['pk'])
gallery = get_object_or_404(news.news_gallery, image_id=self.kwargs['image_id'])
news = get_object_or_404(news_qs, pk=self.kwargs.get('pk'))
gallery = get_object_or_404(news.news_gallery, image_id=self.kwargs.get('image_id'))
# May raise a permission denied
self.check_object_permissions(self.request, gallery)
@ -125,7 +125,7 @@ class NewsBackOfficeGalleryListView(NewsBackOfficeMixinView,
def get_object(self):
"""Override get_object method."""
qs = super(NewsBackOfficeGalleryListView, self).get_queryset()
news = get_object_or_404(qs, pk=self.kwargs['pk'])
news = get_object_or_404(qs, pk=self.kwargs.get('pk'))
# May raise a permission denied
self.check_object_permissions(self.request, news)

View File

@ -6,3 +6,4 @@ from partner import models
@admin.register(models.Partner)
class PartnerModelAdmin(admin.ModelAdmin):
"""Model admin for Partner model."""
raw_id_fields = ('establishment',)

View File

@ -0,0 +1,50 @@
# Generated by Django 2.2.7 on 2019-11-21 10:59
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('establishment', '0065_establishment_purchased_products'),
('partner', '0002_auto_20191101_0939'),
]
operations = [
migrations.AddField(
model_name='partner',
name='establishment',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='partners', to='establishment.Establishment', verbose_name='Establishment'),
),
migrations.AddField(
model_name='partner',
name='expiry_date',
field=models.DateField(blank=True, null=True, verbose_name='expiry date'),
),
migrations.AddField(
model_name='partner',
name='name',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='name'),
),
migrations.AddField(
model_name='partner',
name='old_id',
field=models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='old id'),
),
migrations.AddField(
model_name='partner',
name='price_per_month',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='price per month'),
),
migrations.AddField(
model_name='partner',
name='starting_date',
field=models.DateField(blank=True, null=True, verbose_name='starting date'),
),
migrations.AddField(
model_name='partner',
name='type',
field=models.PositiveSmallIntegerField(choices=[(0, 'Partner'), (1, 'Sponsor')], default=0),
),
]

View File

@ -1,13 +1,36 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from establishment.models import Establishment
from utils.models import ImageMixin, ProjectBaseMixin
class Partner(ProjectBaseMixin):
"""Partner model."""
PARTNER = 0
SPONSOR = 1
MODEL_TYPES = (
(PARTNER, _('Partner')),
(SPONSOR, _('Sponsor')),
)
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
name = models.CharField(_('name'), max_length=255, blank=True, null=True)
url = models.URLField(verbose_name=_('Partner URL'))
image = models.URLField(verbose_name=_('Partner image URL'), null=True)
establishment = models.ForeignKey(
Establishment,
verbose_name=_('Establishment'),
related_name='partners',
on_delete=models.CASCADE,
blank=True,
null=True,
)
type = models.PositiveSmallIntegerField(choices=MODEL_TYPES, default=PARTNER)
starting_date = models.DateField(_('starting date'), blank=True, null=True)
expiry_date = models.DateField(_('expiry date'), blank=True, null=True)
price_per_month = models.DecimalField(_('price per month'), max_digits=10, decimal_places=2, blank=True, null=True)
class Meta:
verbose_name = _('partner')

View File

@ -1,19 +1,37 @@
from django.db.models import Value, IntegerField, F
from pprint import pprint
from establishment.models import Establishment
from partner.models import Partner
from transfer.models import EstablishmentBacklinks
from transfer.serializers.partner import PartnerSerializer
def transfer_partner():
queryset = EstablishmentBacklinks.objects.filter(type="Partner")
"""
Transfer data to Partner model only after transfer Establishment
"""
establishments = Establishment.objects.filter(old_id__isnull=False).values_list('old_id', flat=True)
queryset = EstablishmentBacklinks.objects.filter(
establishment_id__in=list(establishments),
).values(
'id',
'establishment_id',
'partnership_name',
'partnership_icon',
'backlink_url',
'created_at',
'type',
'starting_date',
'expiry_date',
'price_per_month',
)
# queryset = EstablishmentBacklinks.objects.all() # Partner and Sponsor
serialized_data = PartnerSerializer(data=list(queryset.values()), many=True)
serialized_data = PartnerSerializer(data=list(queryset), many=True)
if serialized_data.is_valid():
Partner.objects.all().delete() # TODO: закоментить, если требуется сохранить старые записи
serialized_data.save()
else:
pprint(f"News serializer errors: {serialized_data.errors}")
pprint(f"Partner serializer errors: {serialized_data.errors}")
data_types = {

View File

@ -40,7 +40,6 @@ class Command(BaseCommand):
TagCategory.objects.bulk_create(objects)
self.stdout.write(self.style.WARNING(f'Add or get tag category objects.'))
def product_type_category_sql(self):
with connections['legacy'].cursor() as cursor:
cursor.execute('''
@ -56,9 +55,9 @@ class Command(BaseCommand):
def add_type_product_category(self):
for c in tqdm(self.product_type_category_sql(), desc='Add type product category'):
type = ProductType.objects.get(index_name='wine')
type = ProductType.objects.get(index_name=ProductType.WINE)
category = TagCategory.objects.get(index_name=c.tag_category)
if type and category not in type.tag_categories.all():
if category not in type.tag_categories.all():
type.tag_categories.add(category)
self.stdout.write(self.style.WARNING(f'Add type product category objects.'))
@ -116,7 +115,7 @@ class Command(BaseCommand):
select
DISTINCT
m.product_id,
lower(trim(CONVERT(m.value USING utf8))) as tag_value,
trim(CONVERT(m.value USING utf8)) as tag_value,
trim(CONVERT(v.key_name USING utf8)) as tag_category
FROM product_metadata m
JOIN product_key_value_metadata v on v.id = m.product_key_value_metadatum_id
@ -133,7 +132,7 @@ class Command(BaseCommand):
)
product = Product.objects.get(old_id=t.product_id)
for tag in tags:
if product not in tag.products.all():
if tag not in product.tags.all():
product.tags.add(tag)
self.stdout.write(self.style.WARNING(f'Add or get tag objects.'))

View File

@ -0,0 +1,30 @@
# Generated by Django 2.2.7 on 2019-11-20 12:49
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('establishment', '0064_auto_20191119_1546'),
('product', '0017_auto_20191119_1546'),
]
operations = [
migrations.CreateModel(
name='PurchasedProduct',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('is_gifted', models.NullBooleanField(default=None, verbose_name='is gifted')),
('quantity', models.PositiveSmallIntegerField(verbose_name='quantity')),
('establishment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='purchased_plaques', to='establishment.Establishment', verbose_name='establishment')),
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='purchased_by_establishments', to='product.Product', verbose_name='plaque')),
],
options={
'verbose_name': 'purchased plaque',
'verbose_name_plural': 'purchased plaques',
'unique_together': {('establishment', 'product')},
},
),
]

View File

@ -264,12 +264,23 @@ class Product(GalleryModelMixin, TranslatedFieldsMixin, BaseAttributes,
def grape_variety(self):
return self.tags.filter(category__index_name='grape-variety')
@property
def bottle_sizes(self):
return self.tags.filter(category__index_name='bottle_size')
@property
def alcohol_percentage(self):
qs = self.tags.filter(category__index_name='alcohol_percentage')
if qs.exists():
return qs.first()
@property
def related_tags(self):
return super().visible_tags.exclude(category__index_name__in=[
'sugar-content', 'wine-color', 'bottles-produced',
'serial-number', 'grape-variety']
)
'serial-number', 'grape-variety', 'serial_number',
'alcohol_percentage', 'bottle_size',
])
@property
def display_name(self):
@ -316,6 +327,26 @@ class OnlineProduct(Product):
verbose_name_plural = _('Online products')
class PurchasedProduct(models.Model):
"""Model for storing establishment purchased plaques."""
establishment = models.ForeignKey('establishment.Establishment', on_delete=models.CASCADE,
related_name='purchased_plaques',
verbose_name=_('establishment'))
product = models.ForeignKey('product.Product', on_delete=models.CASCADE,
related_name='purchased_by_establishments',
verbose_name=_('plaque'))
is_gifted = models.NullBooleanField(default=None,
verbose_name=_('is gifted'))
quantity = models.PositiveSmallIntegerField(verbose_name=_('quantity'))
class Meta:
"""Meta class."""
verbose_name = _('purchased plaque')
verbose_name_plural = _('purchased plaques')
unique_together = ('establishment', 'product')
class Unit(models.Model):
"""Product unit model."""
name = models.CharField(max_length=255,

View File

@ -128,6 +128,8 @@ class ProductDetailSerializer(ProductBaseSerializer):
bottles_produced = TagBaseSerializer(many=True, read_only=True)
sugar_contents = TagBaseSerializer(many=True, read_only=True)
grape_variety = TagBaseSerializer(many=True, read_only=True)
bottle_sizes = TagBaseSerializer(many=True, read_only=True)
alcohol_percentage = TagBaseSerializer(read_only=True)
image_url = serializers.URLField(allow_null=True,
read_only=True)
new_image = ImageBaseSerializer(source='crop_main_image', allow_null=True, read_only=True)
@ -146,6 +148,8 @@ class ProductDetailSerializer(ProductBaseSerializer):
'new_image',
'grape_variety',
'average_price',
'bottle_sizes',
'alcohol_percentage',
]

View File

@ -2,17 +2,6 @@ from pprint import pprint
from transfer import models as transfer_models
from transfer.serializers import product as product_serializers
from transfer.serializers.partner import PartnerSerializer
def transfer_partner():
queryset = transfer_models.EstablishmentBacklinks.objects.filter(type="Partner")
serialized_data = PartnerSerializer(data=list(queryset.values()), many=True)
if serialized_data.is_valid():
serialized_data.save()
else:
pprint(f"News serializer errors: {serialized_data.errors}")
def transfer_wine_color():
@ -50,8 +39,8 @@ def transfer_wine_bottles_produced():
)
queryset = [vars(query) for query in raw_queryset]
serialized_data = product_serializers.WineBottlesProducedSerializer(
data=queryset,
many=True)
data=queryset,
many=True)
if serialized_data.is_valid():
serialized_data.save()
else:
@ -69,8 +58,8 @@ def transfer_wine_classification_type():
)
queryset = [vars(query) for query in raw_queryset]
serialized_data = product_serializers.WineClassificationTypeSerializer(
data=queryset,
many=True)
data=queryset,
many=True)
if serialized_data.is_valid():
serialized_data.save()
else:
@ -79,10 +68,10 @@ def transfer_wine_classification_type():
def transfer_wine_standard():
queryset = transfer_models.ProductClassification.objects.filter(parent_id__isnull=True) \
.exclude(type='Classification')
.exclude(type='Classification')
serialized_data = product_serializers.ProductStandardSerializer(
data=list(queryset.values()),
many=True)
data=list(queryset.values()),
many=True)
if serialized_data.is_valid():
serialized_data.save()
else:
@ -92,8 +81,8 @@ def transfer_wine_standard():
def transfer_wine_classifications():
queryset = transfer_models.ProductClassification.objects.filter(type='Classification')
serialized_data = product_serializers.ProductClassificationSerializer(
data=list(queryset.values()),
many=True)
data=list(queryset.values()),
many=True)
if serialized_data.is_valid():
serialized_data.save()
else:
@ -104,8 +93,8 @@ def transfer_product():
errors = []
queryset = transfer_models.Products.objects.all()
serialized_data = product_serializers.ProductSerializer(
data=list(queryset.values()),
many=True)
data=list(queryset.values()),
many=True)
if serialized_data.is_valid():
serialized_data.save()
else:
@ -117,8 +106,8 @@ def transfer_product_note():
errors = []
queryset = transfer_models.ProductNotes.objects.exclude(text='')
serialized_data = product_serializers.ProductNoteSerializer(
data=list(queryset.values()),
many=True)
data=list(queryset.values()),
many=True)
if serialized_data.is_valid():
serialized_data.save()
else:
@ -130,8 +119,8 @@ def transfer_plate():
errors = []
queryset = transfer_models.Merchandise.objects.all()
serialized_data = product_serializers.PlateSerializer(
data=list(queryset.values()),
many=True)
data=list(queryset.values()),
many=True)
if serialized_data.is_valid():
serialized_data.save()
else:
@ -143,8 +132,8 @@ def transfer_plate_image():
errors = []
queryset = transfer_models.Merchandise.objects.all()
serialized_data = product_serializers.PlateImageSerializer(
data=list(queryset.values()),
many=True)
data=list(queryset.values()),
many=True)
if serialized_data.is_valid():
serialized_data.save()
else:
@ -153,7 +142,6 @@ def transfer_plate_image():
data_types = {
"partner": [transfer_partner],
"wine_characteristics": [
transfer_wine_sugar_content,
transfer_wine_color,
@ -161,12 +149,12 @@ data_types = {
transfer_wine_classification_type,
transfer_wine_standard,
transfer_wine_classifications,
],
],
"product": [
transfer_product,
],
"product_note": [
transfer_product_note,
transfer_product_note,
],
"souvenir": [
transfer_plate,

View File

@ -57,8 +57,8 @@ class ProductBackOfficeGalleryCreateDestroyView(ProductBackOfficeMixinView,
"""
product_qs = self.filter_queryset(self.get_queryset())
product = get_object_or_404(product_qs, pk=self.kwargs['pk'])
gallery = get_object_or_404(product.product_gallery, image_id=self.kwargs['image_id'])
product = get_object_or_404(product_qs, pk=self.kwargs.get('pk'))
gallery = get_object_or_404(product.product_gallery, image_id=self.kwargs.get('image_id'))
# May raise a permission denied
self.check_object_permissions(self.request, gallery)
@ -75,7 +75,7 @@ class ProductBackOfficeGalleryListView(ProductBackOfficeMixinView,
def get_object(self):
"""Override get_object method."""
qs = super(ProductBackOfficeGalleryListView, self).get_queryset()
product = get_object_or_404(qs, pk=self.kwargs['pk'])
product = get_object_or_404(qs, pk=self.kwargs.get('pk'))
# May raise a permission denied
self.check_object_permissions(self.request, product)
@ -149,7 +149,7 @@ class ProductNoteListCreateView(ProductBackOfficeMixinView,
product_qs = models.Product.objects.all()
filtered_product_qs = self.filter_queryset(product_qs)
product = get_object_or_404(filtered_product_qs, pk=self.kwargs['pk'])
product = get_object_or_404(filtered_product_qs, pk=self.kwargs.get('pk'))
# May raise a permission denied
self.check_object_permissions(self.request, product)
@ -173,8 +173,8 @@ class ProductNoteRUDView(ProductBackOfficeMixinView,
product_qs = models.Product.objects.all()
filtered_product_qs = self.filter_queryset(product_qs)
product = get_object_or_404(filtered_product_qs, pk=self.kwargs['pk'])
note = get_object_or_404(product.notes.all(), pk=self.kwargs['note_pk'])
product = get_object_or_404(filtered_product_qs, pk=self.kwargs.get('pk'))
note = get_object_or_404(product.notes.all(), pk=self.kwargs.get('note_pk'))
# May raise a permission denied
self.check_object_permissions(self.request, note)

View File

@ -14,7 +14,9 @@ class ReviewBaseSerializer(serializers.ModelSerializer):
'child',
'published_at',
'vintage',
'country'
'country',
'content_type',
'object_id',
)

View File

@ -19,6 +19,7 @@ class BaseTestCase(APITestCase):
username=self.username,
email=self.email,
password=self.password,
is_staff=True,
)
tokens = User.create_jwt_tokens(self.user)
@ -61,6 +62,49 @@ class BaseTestCase(APITestCase):
)
class ReviewTestCase(BaseTestCase):
def setUp(self):
super().setUp()
def test_review_list(self):
response = self.client.get('/api/back/review/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_review_post(self):
test_review = {
'reviewer': self.user.id,
'status': Review.READY,
'vintage': 2019,
'country': self.country_ru.id,
'object_id': 1,
'content_type': 1,
}
response = self.client.post('/api/back/review/', data=test_review)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
def test_review_detail(self):
response = self.client.get(f'/api/back/review/{self.test_review.id}/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_review_detail_put(self):
data = {
'id': self.test_review.id,
'vintage': 2018,
'reviewer': self.user.id,
'status': Review.READY,
'country': self.country_ru.id,
'object_id': 1,
'content_type': 1,
}
response = self.client.put(f'/api/back/review/{self.test_review.id}/', data=data, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_review_delete(self):
response = self.client.delete(f'/api/back/review/{self.test_review.id}/')
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
class InquiriesTestCase(BaseTestCase):
def setUp(self):
super().setUp()

View File

@ -1,9 +1,8 @@
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import generics, permissions
from review import filters
from review import models
from review import serializers
from review import filters
from utils.permissions import IsReviewerManager, IsRestaurantReviewer
@ -12,7 +11,6 @@ class ReviewLstView(generics.ListCreateAPIView):
serializer_class = serializers.ReviewBaseSerializer
queryset = models.Review.objects.all()
permission_classes = [permissions.IsAuthenticatedOrReadOnly, ]
filter_backends = (DjangoFilterBackend,)
filterset_class = filters.ReviewFilter
@ -20,7 +18,7 @@ class ReviewRUDView(generics.RetrieveUpdateDestroyAPIView):
"""Comment RUD view."""
serializer_class = serializers.ReviewBaseSerializer
queryset = models.Review.objects.all()
permission_classes = [IsReviewerManager | IsRestaurantReviewer]
permission_classes = [permissions.IsAdminUser | IsReviewerManager | IsRestaurantReviewer]
lookup_field = 'id'

View File

@ -71,14 +71,21 @@ class CustomSearchFilterBackend(SearchFilterBackend):
Q("match", **{k: v})
)
__queries.append(
Q('wildcard', **{k: f'*{search_term.lower()}*'})
Q('wildcard',
**{k: {
'value': f'*{search_term.lower()}*',
'boost': v.get('boost', 1) + 30
}
}
)
)
else:
__queries.append(
Q("match", **field_kwargs)
)
__queries.append(
Q('wildcard', **{field: f'*{search_term.lower()}*'})
Q('wildcard', **{field: {'value': f'*{search_term.lower()}*',
'boost': field_kwargs[field].get('boost', 1) + 30}})
)
else:
for field in view.search_fields:
@ -99,13 +106,20 @@ class CustomSearchFilterBackend(SearchFilterBackend):
Q("match", **{k: v})
)
__queries.append(
Q('wildcard', **{k: f'*{search_term.lower()}*'})
Q('wildcard',
**{k: {
'value': f'*{search_term.lower()}*',
'boost': v.get('boost', 1) + 30
}
}
)
)
else:
__queries.append(
Q("match", **field_kwargs)
)
__queries.append(
Q('wildcard', **{field: f'*{search_term.lower()}*'})
Q('wildcard', **{field: {'value': f'*{search_term.lower()}*',
'boost': field_kwargs[field].get('boost', 1) + 30}})
)
return __queries

View File

@ -43,8 +43,8 @@ class TagCategoryFilterSet(TagsBaseFilterSet):
'product_type', )
def by_product_type(self, queryset, name, value):
if value == product_models.ProductType.WINE:
queryset = queryset.filter(index_name='wine-color').filter(tags__products__isnull=False)
# if value == product_models.ProductType.WINE:
# queryset = queryset.filter(index_name='wine-color').filter(tags__products__isnull=False)
queryset = queryset.by_product_type(value)
return queryset

View File

@ -8,6 +8,9 @@ from timetable.models import Timetable
class ScheduleRUDSerializer(serializers.ModelSerializer):
"""Serializer for Establishment model."""
NULLABLE_FIELDS = ['lunch_start', 'lunch_end', 'dinner_start',
'dinner_end', 'opening_at', 'closed_at']
weekday_display = serializers.CharField(source='get_weekday_display',
read_only=True)
@ -18,9 +21,6 @@ class ScheduleRUDSerializer(serializers.ModelSerializer):
opening_at = serializers.TimeField(required=False)
closed_at = serializers.TimeField(required=False)
NULLABLE_FIELDS = ['lunch_start', 'lunch_end', 'dinner_start',
'dinner_end', 'opening_at', 'closed_at']
class Meta:
"""Meta class."""
model = Timetable

View File

@ -39,6 +39,8 @@ class Command(BaseCommand):
'rating_count',
'product_review',
'newsletter_subscriber', # подписчики на рассылку - переносить после переноса пользователей №1
'purchased_plaques', # №6 - перенос купленных тарелок
'fill_city_gallery', # №3 - перенос галереи городов
]
def handle(self, *args, **options):

View File

@ -217,10 +217,10 @@ class CityNames(MigrateMixin):
class CityPhotos(MigrateMixin):
using = 'legacy'
# city_id = models.IntegerField(blank=True, null=True)
city = models.ForeignKey(Cities, models.DO_NOTHING, blank=True, null=True)
attachment_file_name = models.CharField(max_length=255, blank=True, null=True)
attachment_content_type = models.CharField(max_length=255, blank=True, null=True)
attachment_suffix_url = models.CharField(max_length=255)
geometries = models.CharField(max_length=1024, blank=True, null=True)
attachment_file_size = models.IntegerField(blank=True, null=True)
attachment_updated_at = models.DateTimeField(blank=True, null=True)
@ -581,22 +581,19 @@ class EstablishmentInfos(MigrateMixin):
db_table = 'establishment_infos'
# class EstablishmentMerchandises(MigrateMixin):
# using = 'legacy'
#
# establishment = models.ForeignKey('Establishments', models.DO_NOTHING, blank=True, null=True)
class EstablishmentMerchandises(MigrateMixin):
using = 'legacy'
# TODO: модели Merchandises нету в гугл таблице Check Migrations
establishment = models.ForeignKey('Establishments', models.DO_NOTHING, blank=True, null=True)
merchandise = models.ForeignKey('Merchandise', models.DO_NOTHING, blank=True, null=True)
gifted = models.NullBooleanField(blank=True, null=True)
quantity = models.IntegerField(blank=True, null=True)
created_at = models.DateTimeField()
updated_at = models.DateTimeField()
# merchandise = models.ForeignKey('Merchandises', models.DO_NOTHING, blank=True, null=True)
# gifted = models.IntegerField(blank=True, null=True)
# quantity = models.IntegerField(blank=True, null=True)
# created_at = models.DateTimeField()
# updated_at = models.DateTimeField()
#
# class Meta:
# managed = False
# db_table = 'establishment_merchandises'
class Meta:
managed = False
db_table = 'establishment_merchandises'
class Menus(MigrateMixin):

View File

@ -1,28 +1,53 @@
from rest_framework import serializers
from establishment.models import Establishment
from partner.models import Partner
class PartnerSerializer(serializers.ModelSerializer):
backlink_url = serializers.CharField(source="url")
partnership_icon = serializers.CharField()
partnership_name = serializers.CharField()
class Meta:
model = Partner
fields = (
"backlink_url",
"partnership_icon",
"partnership_name"
)
class PartnerSerializer(serializers.Serializer):
id = serializers.IntegerField()
establishment_id = serializers.IntegerField()
partnership_name = serializers.CharField(allow_null=True)
partnership_icon = serializers.CharField(allow_null=True)
backlink_url = serializers.CharField(allow_null=True)
created_at = serializers.DateTimeField(format='%m-%d-%Y %H:%M:%S')
type = serializers.CharField(allow_null=True)
starting_date = serializers.DateField(allow_null=True)
expiry_date = serializers.DateField(allow_null=True)
price_per_month = serializers.DecimalField(max_digits=10, decimal_places=2, allow_null=True)
def validate(self, data):
data["image"] = partnership_to_image_url.get(data["partnership_name"]).get(data["partnership_icon"])
data.pop("partnership_name")
data.pop("partnership_icon")
data.update({
'old_id': data.pop('id'),
'name': data['partnership_name'],
'url': data.pop('backlink_url'),
'image': self.get_image(data),
'establishment': self.get_establishment(data),
'type': Partner.PARTNER if data['type'] == 'Partner' else Partner.SPONSOR,
'created': data.pop('created_at'),
})
data.pop('partnership_icon')
data.pop('partnership_name')
data.pop('establishment_id')
return data
@staticmethod
def get_image(data):
return partnership_to_image_url.get(data['partnership_name']).get(data['partnership_icon'])
@staticmethod
def get_establishment(data):
establishment = Establishment.objects.filter(old_id=data['establishment_id']).first()
if not establishment:
raise ValueError(f"Establishment not found with old_id {data['establishment_id']}: ")
return establishment
def create(self, validated_data):
return Partner.objects.create(**validated_data)
obj, _ = Partner.objects.update_or_create(
old_id=validated_data['old_id'],
defaults=validated_data,
)
return obj
partnership_to_image_url = {

View File

@ -265,7 +265,7 @@ class ProductSerializer(TransferSerializerMixin):
state = serializers.CharField()
bottles_produced = serializers.CharField(allow_null=True, allow_blank=True)
unique_key = serializers.CharField(allow_null=True)
price = serializers.DecimalField(max_digits=14, decimal_places=2)
price = serializers.DecimalField(max_digits=14, decimal_places=2, allow_null=True)
class Meta:
model = models.Product

View File

@ -4,6 +4,7 @@ from django.conf import settings
from django.db.transaction import on_commit
from django.shortcuts import get_object_or_404
from rest_framework import generics, status
from rest_framework.decorators import action
from rest_framework.response import Response
from gallery.tasks import delete_image
@ -12,7 +13,7 @@ from search_indexes.documents import es_update
# JWT
# Login base view mixins
class JWTGenericViewMixin(generics.GenericAPIView):
class JWTGenericViewMixin:
"""JWT view mixin"""
ACCESS_TOKEN_HTTP_ONLY = False
@ -39,30 +40,31 @@ class JWTGenericViewMixin(generics.GenericAPIView):
"""
COOKIES = []
if hasattr(self.request, 'locale'):
COOKIES.append(self.COOKIE(key='locale',
value=self.request.locale,
http_only=self.ACCESS_TOKEN_HTTP_ONLY,
secure=self.LOCALE_SECURE,
max_age=settings.COOKIES_MAX_AGE if permanent else None))
if hasattr(self.request, 'country_code'):
COOKIES.append(self.COOKIE(key='country_code',
value=self.request.country_code,
http_only=self.COUNTRY_CODE_HTTP_ONLY,
secure=self.COUNTRY_CODE_SECURE,
max_age=settings.COOKIES_MAX_AGE if permanent else None))
if access_token:
COOKIES.append(self.COOKIE(key='access_token',
value=access_token,
http_only=self.ACCESS_TOKEN_HTTP_ONLY,
secure=self.ACCESS_TOKEN_SECURE,
max_age=settings.COOKIES_MAX_AGE if permanent else None))
if refresh_token:
COOKIES.append(self.COOKIE(key='refresh_token',
value=refresh_token,
http_only=self.REFRESH_TOKEN_HTTP_ONLY,
secure=self.REFRESH_TOKEN_SECURE,
max_age=settings.COOKIES_MAX_AGE if permanent else None))
if hasattr(self, 'request'):
if hasattr(self.request, 'locale'):
COOKIES.append(self.COOKIE(key='locale',
value=self.request.locale,
http_only=self.ACCESS_TOKEN_HTTP_ONLY,
secure=self.LOCALE_SECURE,
max_age=settings.COOKIES_MAX_AGE if permanent else None))
if hasattr(self.request, 'country_code'):
COOKIES.append(self.COOKIE(key='country_code',
value=self.request.country_code,
http_only=self.COUNTRY_CODE_HTTP_ONLY,
secure=self.COUNTRY_CODE_SECURE,
max_age=settings.COOKIES_MAX_AGE if permanent else None))
if access_token:
COOKIES.append(self.COOKIE(key='access_token',
value=access_token,
http_only=self.ACCESS_TOKEN_HTTP_ONLY,
secure=self.ACCESS_TOKEN_SECURE,
max_age=settings.COOKIES_MAX_AGE if permanent else None))
if refresh_token:
COOKIES.append(self.COOKIE(key='refresh_token',
value=refresh_token,
http_only=self.REFRESH_TOKEN_HTTP_ONLY,
secure=self.REFRESH_TOKEN_SECURE,
max_age=settings.COOKIES_MAX_AGE if permanent else None))
return COOKIES
def _put_cookies_in_response(self, cookies: list, response: Response):
@ -156,3 +158,29 @@ class FavoritesCreateDestroyMixinView(generics.CreateAPIView,
instance.delete()
self.es_update_base_object()
# BackOffice user`s views & viewsets
class BindObjectMixin:
"""Bind object mixin."""
def get_serializer_class(self):
if self.action == 'bind_object':
return self.bind_object_serializer_class
return self.serializer_class
def perform_binding(self, serializer):
raise NotImplemented
def perform_unbinding(self, serializer):
raise NotImplemented
@action(methods=['post', 'delete'], detail=True, url_path='bind-object')
def bind_object(self, request, pk=None):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
if request.method == 'POST':
self.perform_binding(serializer)
return Response(serializer.data, status=status.HTTP_201_CREATED)
elif request.method == 'DELETE':
self.perform_unbinding(serializer)
return Response(status=status.HTTP_204_NO_CONTENT)

View File

@ -509,9 +509,8 @@ FALLBACK_LOCALE = 'en-GB'
# TMP TODO remove it later
# Временный хардкод для демонстрации > 15 ноября, потом удалить!
CAROUSEL_ITEMS = [230, 231, 232]
CAROUSEL_ITEMS = [465]
ESTABLISHMENT_CHOSEN_TAGS = ['gastronomic', 'en_vogue', 'terrace', 'streetfood', 'business', 'bar_cocktail', 'brunch', 'pop']
NEWS_CHOSEN_TAGS = ['eat', 'drink', 'cook', 'style', 'international', 'event', 'partnership']
INTERNATIONAL_COUNTRY_CODES = ['www', 'main', 'next']
ELASTICSEARCH_DSL_AUTOSYNC = False

View File

@ -33,6 +33,7 @@ ELASTICSEARCH_INDEX_NAMES = {
'search_indexes.documents.product': 'development_product',
}
# ELASTICSEARCH_DSL_AUTOSYNC = False
sentry_sdk.init(
dsn="https://35d9bb789677410ab84a822831c6314f@sentry.io/1729093",

View File

@ -15,5 +15,5 @@ urlpatterns = [
path('products/', include(('product.urls.back', 'product'), namespace='product')),
path('re_blocks/', include(('advertisement.urls.back', 'advertisement'),
namespace='advertisement')),
path('main/', include('main.urls.back')),
]