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) return Response(status=status.HTTP_200_OK)
class ConfirmEmailView(JWTGenericViewMixin): class ConfirmEmailView(JWTGenericViewMixin, generics.GenericAPIView):
"""View for confirm changing email""" """View for confirm changing email"""
permission_classes = (permissions.AllowAny,) permission_classes = (permissions.AllowAny,)

View File

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

View File

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

View File

@ -71,7 +71,7 @@ class OAuth2ViewMixin(CsrfExemptMixin, OAuthLibMixin, BaseOAuth2ViewMixin):
# Sign in via Facebook # 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 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) return Response(status=status.HTTP_201_CREATED)
class ConfirmationEmailView(JWTGenericViewMixin): class ConfirmationEmailView(JWTGenericViewMixin, generics.GenericAPIView):
"""View for confirmation email""" """View for confirmation email"""
permission_classes = (permissions.AllowAny, ) permission_classes = (permissions.AllowAny, )
@ -174,7 +174,7 @@ class ConfirmationEmailView(JWTGenericViewMixin):
# Login by username|email + password # Login by username|email + password
class LoginByUsernameOrEmailView(JWTGenericViewMixin): class LoginByUsernameOrEmailView(JWTGenericViewMixin, generics.GenericAPIView):
"""Login by email and password""" """Login by email and password"""
permission_classes = (permissions.AllowAny,) permission_classes = (permissions.AllowAny,)
serializer_class = serializers.LoginByUsernameOrEmailSerializer serializer_class = serializers.LoginByUsernameOrEmailSerializer
@ -197,7 +197,7 @@ class LoginByUsernameOrEmailView(JWTGenericViewMixin):
# Logout # Logout
class LogoutView(JWTGenericViewMixin): class LogoutView(JWTGenericViewMixin, generics.GenericAPIView):
"""Logout user""" """Logout user"""
permission_classes = (IsAuthenticatedAndTokenIsValid, ) permission_classes = (IsAuthenticatedAndTokenIsValid, )
@ -215,7 +215,7 @@ class LogoutView(JWTGenericViewMixin):
# Refresh token # Refresh token
class RefreshTokenView(JWTGenericViewMixin): class RefreshTokenView(JWTGenericViewMixin, generics.GenericAPIView):
"""Refresh access_token""" """Refresh access_token"""
permission_classes = (permissions.AllowAny, ) permission_classes = (permissions.AllowAny, )
serializer_class = serializers.RefreshTokenSerializer serializer_class = serializers.RefreshTokenSerializer

View File

@ -1,8 +1,14 @@
from rest_framework import serializers 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.models import Country
from location.serializers import CountrySimpleSerializer from location.serializers import CountrySimpleSerializer
from collection.serializers.common import CollectionBaseSerializer from product.models import Product
from collection import models from utils.exceptions import (
BindingObjectNotFound, RemovedBindingObjectNotFound, ObjectAlreadyAdded
)
class CollectionBackOfficeSerializer(CollectionBaseSerializer): class CollectionBackOfficeSerializer(CollectionBaseSerializer):
@ -31,3 +37,54 @@ class CollectionBackOfficeSerializer(CollectionBaseSerializer):
'start', 'start',
'end', '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.""" """Collection common urlpaths."""
from django.urls import path from rest_framework.routers import SimpleRouter
from collection.views import back as views from collection.views import back as views
app_name = 'collection' app_name = 'collection'
router = SimpleRouter()
router.register(r'', views.CollectionBackOfficeViewSet)
urlpatterns = [ urlpatterns = router.urls
path('', views.CollectionListCreateView.as_view(), name='list-create'),
path('<int:pk>/', views.CollectionRUDView.as_view(), name='rud-collection'),
]

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 import models
from collection.serializers import back from collection.serializers import back as serializers
from utils.views import BindObjectMixin
class CollectionListCreateView(generics.ListCreateAPIView): class CollectionViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
"""Collection list-create view.""" """ViewSet for Collection model."""
pagination_class = None
permission_classes = (permissions.AllowAny,)
queryset = models.Collection.objects.all() queryset = models.Collection.objects.all()
serializer_class = back.CollectionBackOfficeSerializer serializer_class = serializers.CollectionBackOfficeSerializer
# todo: conf. permissions by TT
permission_classes = (permissions.IsAuthenticated, )
class CollectionRUDView(generics.RetrieveUpdateDestroyAPIView): class CollectionBackOfficeViewSet(mixins.CreateModelMixin,
"""Collection list-create view.""" mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.RetrieveModelMixin,
BindObjectMixin,
CollectionViewSet):
"""ViewSet for Collection model for BackOffice users."""
permission_classes = (permissions.IsAuthenticated,)
queryset = models.Collection.objects.all() queryset = models.Collection.objects.all()
serializer_class = back.CollectionBackOfficeSerializer serializer_class = serializers.CollectionBackOfficeSerializer
# todo: conf. permissions by TT bind_object_serializer_class = serializers.CollectionBindObjectSerializer
permission_classes = (permissions.IsAuthenticated, )
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 utils.admin import BaseModelAdminMixin
from establishment import models from establishment import models
from main.models import Award from main.models import Award
from product.models import Product from product.models import Product, PurchasedProduct
from review import models as review_models from review import models as review_models
@ -69,13 +69,19 @@ class EstablishmentNote(admin.TabularInline):
extra = 0 extra = 0
class PurchasedProduct(admin.TabularInline):
model = PurchasedProduct
extra = 0
@admin.register(models.Establishment) @admin.register(models.Establishment)
class EstablishmentAdmin(BaseModelAdminMixin, admin.ModelAdmin): class EstablishmentAdmin(BaseModelAdminMixin, admin.ModelAdmin):
"""Establishment admin.""" """Establishment admin."""
list_display = ['id', '__str__', 'image_tag', ] list_display = ['id', '__str__', 'image_tag', ]
search_fields = ['id', 'name', 'index_name', 'slug'] search_fields = ['id', 'name', 'index_name', 'slug']
list_filter = ['public_mark', 'toque_number'] list_filter = ['public_mark', 'toque_number']
inlines = [GalleryImageInline, CompanyInline, EstablishmentNote] inlines = [GalleryImageInline, CompanyInline, EstablishmentNote,
PurchasedProduct]
# inlines = [ # inlines = [
# AwardInline, ContactPhoneInline, ContactEmailInline, # 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, currency = models.ForeignKey(Currency, blank=True, null=True, default=None,
on_delete=models.PROTECT, on_delete=models.PROTECT,
verbose_name=_('currency')) 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() objects = EstablishmentQuerySet.as_manager()
@ -411,6 +418,12 @@ class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin,
def __str__(self): def __str__(self):
return f'id:{self.id}-{self.name}' 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): def delete(self, using=None, keep_parents=False):
"""Overridden delete method""" """Overridden delete method"""
# Delete all related companies # Delete all related companies

View File

@ -4,7 +4,9 @@ from django.db.models import Q, F
from establishment.models import Establishment from establishment.models import Establishment
from location.models import Address 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, \ from transfer.serializers.establishment import EstablishmentSerializer, \
EstablishmentNoteSerializer EstablishmentNoteSerializer
from transfer.serializers.plate import PlateSerializer from transfer.serializers.plate import PlateSerializer
@ -140,6 +142,43 @@ def transfer_establishment_note():
pprint(f"transfer_establishment_note errors: {errors}") 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 = { data_types = {
"establishment": [ "establishment": [
transfer_establishment, transfer_establishment,
@ -149,4 +188,7 @@ data_types = {
transfer_establishment_addresses transfer_establishment_addresses
], ],
"menu": [transfer_menu], "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 timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer
from utils.permissions import IsCountryAdmin, IsEstablishmentManager from utils.permissions import IsCountryAdmin, IsEstablishmentManager
from utils.views import CreateDestroyGalleryViewMixin from utils.views import CreateDestroyGalleryViewMixin
from timetable.models import Timetable
from rest_framework import status from rest_framework import status
from rest_framework.response import Response from rest_framework.response import Response
@ -36,13 +37,14 @@ class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView):
class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView): class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView):
"""Establishment schedule RUD view""" """Establishment schedule RUD view"""
serializer_class = ScheduleRUDSerializer serializer_class = ScheduleRUDSerializer
permission_classes = [IsEstablishmentManager]
def get_object(self): def get_object(self):
""" """
Returns the object the view is displaying. Returns the object the view is displaying.
""" """
establishment_pk = self.kwargs['pk'] establishment_pk = self.kwargs.get('pk')
schedule_id = self.kwargs['schedule_id'] schedule_id = self.kwargs.get('schedule_id')
establishment = get_object_or_404(klass=models.Establishment.objects.all(), establishment = get_object_or_404(klass=models.Establishment.objects.all(),
pk=establishment_pk) pk=establishment_pk)
@ -59,6 +61,8 @@ class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView):
class EstablishmentScheduleCreateView(generics.CreateAPIView): class EstablishmentScheduleCreateView(generics.CreateAPIView):
"""Establishment schedule Create view""" """Establishment schedule Create view"""
serializer_class = ScheduleCreateSerializer serializer_class = ScheduleCreateSerializer
queryset = Timetable.objects.all()
permission_classes = [IsEstablishmentManager]
class MenuListCreateView(generics.ListCreateAPIView): class MenuListCreateView(generics.ListCreateAPIView):
@ -200,8 +204,9 @@ class EstablishmentGalleryCreateDestroyView(EstablishmentMixinViews,
""" """
establishment_qs = self.filter_queryset(self.get_queryset()) establishment_qs = self.filter_queryset(self.get_queryset())
establishment = get_object_or_404(establishment_qs, pk=self.kwargs['pk']) establishment = get_object_or_404(establishment_qs, pk=self.kwargs.get('pk'))
gallery = get_object_or_404(establishment.establishment_gallery, image_id=self.kwargs['image_id']) gallery = get_object_or_404(establishment.establishment_gallery,
image_id=self.kwargs.get('image_id'))
# May raise a permission denied # May raise a permission denied
self.check_object_permissions(self.request, gallery) self.check_object_permissions(self.request, gallery)
@ -217,7 +222,7 @@ class EstablishmentGalleryListView(EstablishmentMixinViews,
def get_object(self): def get_object(self):
"""Override get_object method.""" """Override get_object method."""
qs = super(EstablishmentGalleryListView, self).get_queryset() 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 # May raise a permission denied
self.check_object_permissions(self.request, establishment) self.check_object_permissions(self.request, establishment)
@ -240,7 +245,7 @@ class EstablishmentCompanyListCreateView(EstablishmentMixinViews,
establishment_qs = models.Establishment.objects.all() establishment_qs = models.Establishment.objects.all()
filtered_ad_qs = self.filter_queryset(establishment_qs) 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 # May raise a permission denied
self.check_object_permissions(self.request, establishment) self.check_object_permissions(self.request, establishment)
@ -263,8 +268,8 @@ class EstablishmentCompanyRUDView(EstablishmentMixinViews,
establishment_qs = models.Establishment.objects.all() establishment_qs = models.Establishment.objects.all()
filtered_ad_qs = self.filter_queryset(establishment_qs) 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'))
company = get_object_or_404(establishment.companies.all(), pk=self.kwargs['company_pk']) company = get_object_or_404(establishment.companies.all(), pk=self.kwargs.get('company_pk'))
# May raise a permission denied # May raise a permission denied
self.check_object_permissions(self.request, company) self.check_object_permissions(self.request, company)
@ -273,7 +278,7 @@ class EstablishmentCompanyRUDView(EstablishmentMixinViews,
class EstablishmentNoteListCreateView(EstablishmentMixinViews, class EstablishmentNoteListCreateView(EstablishmentMixinViews,
generics.ListCreateAPIView): generics.ListCreateAPIView):
"""Retrieve|Update|Destroy establishment note view.""" """Retrieve|Update|Destroy establishment note view."""
serializer_class = serializers.EstablishmentNoteListCreateSerializer serializer_class = serializers.EstablishmentNoteListCreateSerializer
@ -283,7 +288,7 @@ class EstablishmentNoteListCreateView(EstablishmentMixinViews,
establishment_qs = models.Establishment.objects.all() establishment_qs = models.Establishment.objects.all()
filtered_establishment_qs = self.filter_queryset(establishment_qs) 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 # May raise a permission denied
self.check_object_permissions(self.request, establishment) self.check_object_permissions(self.request, establishment)
@ -306,7 +311,7 @@ class EstablishmentNoteRUDView(EstablishmentMixinViews,
establishment_qs = models.Establishment.objects.all() establishment_qs = models.Establishment.objects.all()
filtered_establishment_qs = self.filter_queryset(establishment_qs) 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']) note = get_object_or_404(establishment.notes.all(), pk=self.kwargs['note_pk'])
# May raise a permission denied # May raise a permission denied

View File

@ -1,8 +1,8 @@
from transfer.serializers import location as location_serializers from transfer.serializers import location as location_serializers
from transfer import models as transfer_models 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 pprint import pprint
from requests import get from requests import get
@ -179,6 +179,42 @@ def update_flags():
query.save() 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 = { data_types = {
"dictionaries": [ "dictionaries": [
transfer_countries, transfer_countries,
@ -192,4 +228,5 @@ data_types = {
"update_country_flag": [ "update_country_flag": [
update_flags 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_qs = self.filter_queryset(self.get_queryset())
city = get_object_or_404(city_qs, pk=self.kwargs['pk']) city = get_object_or_404(city_qs, pk=self.kwargs.get('pk'))
gallery = get_object_or_404(city.city_gallery, image_id=self.kwargs['image_id']) gallery = get_object_or_404(city.city_gallery, image_id=self.kwargs.get('image_id'))
# May raise a permission denied # May raise a permission denied
self.check_object_permissions(self.request, gallery) 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 PUBLISHED = 1
STATE_CHOICES = ( STATE_CHOICES = (
(WAITING,'waiting'), (WAITING, 'waiting'),
(PUBLISHED, 'published') (PUBLISHED, 'published')
) )

View File

@ -38,7 +38,7 @@ class SiteFeatureSerializer(serializers.ModelSerializer):
'route', 'route',
'source', 'source',
'nested', 'nested',
) )
class CurrencySerializer(ProjectModelSerializer): class CurrencySerializer(ProjectModelSerializer):
@ -145,6 +145,19 @@ class AwardSerializer(AwardBaseSerializer):
fields = AwardBaseSerializer.Meta.fields + ['award_type', ] 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): class CarouselListSerializer(serializers.ModelSerializer):
"""Serializer for retrieving list of carousel items.""" """Serializer for retrieving list of carousel items."""

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): def get_queryset(self):
country_code = self.request.country_code country_code = self.request.country_code
if hasattr(settings, 'CAROUSEL_ITEMS') and country_code in ['www', 'main']: if hasattr(settings, 'CAROUSEL_ITEMS') and country_code in settings.INTERNATIONAL_COUNTRY_CODES:
qs = models.Carousel.objects.filter(id__in=settings.CAROUSEL_ITEMS) qs = models.Carousel.objects.filter(id__in=settings.CAROUSEL_ITEMS)
return qs return qs
qs = models.Carousel.objects.is_parsed().active() 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') tag_value__in = filters.CharFilter(method='in_tags')
type = filters.CharFilter(method='by_type') 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: class Meta:
"""Meta class""" """Meta class"""
model = models.News model = models.News
@ -29,6 +39,8 @@ class NewsListFilterSet(filters.FilterSet):
'tag_group', 'tag_group',
'tag_value__exclude', 'tag_value__exclude',
'tag_value__in', 'tag_value__in',
'state',
'sort_by',
) )
def in_tags(self, queryset, name, value): def in_tags(self, queryset, name, value):
@ -58,3 +70,6 @@ class NewsListFilterSet(filters.FilterSet):
return queryset.filter(news_type__name=value) return queryset.filter(news_type__name=value)
else: else:
return queryset 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): class NewsBackOfficeBaseSerializer(NewsBaseSerializer):
"""News back office base serializer.""" """News back office base serializer."""
is_published = serializers.BooleanField(source='is_publish', read_only=True)
class Meta(NewsBaseSerializer.Meta): class Meta(NewsBaseSerializer.Meta):
"""Meta class.""" """Meta class."""
@ -169,6 +170,7 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer):
fields = NewsBaseSerializer.Meta.fields + ( fields = NewsBaseSerializer.Meta.fields + (
'title', 'title',
'subtitle', 'subtitle',
'is_published',
) )

View File

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

View File

@ -6,3 +6,4 @@ from partner import models
@admin.register(models.Partner) @admin.register(models.Partner)
class PartnerModelAdmin(admin.ModelAdmin): class PartnerModelAdmin(admin.ModelAdmin):
"""Model admin for Partner model.""" """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.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from establishment.models import Establishment
from utils.models import ImageMixin, ProjectBaseMixin from utils.models import ImageMixin, ProjectBaseMixin
class Partner(ProjectBaseMixin): class Partner(ProjectBaseMixin):
"""Partner model.""" """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')) url = models.URLField(verbose_name=_('Partner URL'))
image = models.URLField(verbose_name=_('Partner image URL'), null=True) 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: class Meta:
verbose_name = _('partner') verbose_name = _('partner')

View File

@ -1,19 +1,37 @@
from django.db.models import Value, IntegerField, F
from pprint import pprint from pprint import pprint
from establishment.models import Establishment
from partner.models import Partner
from transfer.models import EstablishmentBacklinks from transfer.models import EstablishmentBacklinks
from transfer.serializers.partner import PartnerSerializer from transfer.serializers.partner import PartnerSerializer
def transfer_partner(): 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), many=True)
serialized_data = PartnerSerializer(data=list(queryset.values()), many=True)
if serialized_data.is_valid(): if serialized_data.is_valid():
Partner.objects.all().delete() # TODO: закоментить, если требуется сохранить старые записи
serialized_data.save() serialized_data.save()
else: else:
pprint(f"News serializer errors: {serialized_data.errors}") pprint(f"Partner serializer errors: {serialized_data.errors}")
data_types = { data_types = {

View File

@ -40,7 +40,6 @@ class Command(BaseCommand):
TagCategory.objects.bulk_create(objects) TagCategory.objects.bulk_create(objects)
self.stdout.write(self.style.WARNING(f'Add or get tag category objects.')) self.stdout.write(self.style.WARNING(f'Add or get tag category objects.'))
def product_type_category_sql(self): def product_type_category_sql(self):
with connections['legacy'].cursor() as cursor: with connections['legacy'].cursor() as cursor:
cursor.execute(''' cursor.execute('''
@ -56,9 +55,9 @@ class Command(BaseCommand):
def add_type_product_category(self): def add_type_product_category(self):
for c in tqdm(self.product_type_category_sql(), desc='Add type product category'): 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) 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) type.tag_categories.add(category)
self.stdout.write(self.style.WARNING(f'Add type product category objects.')) self.stdout.write(self.style.WARNING(f'Add type product category objects.'))
@ -116,7 +115,7 @@ class Command(BaseCommand):
select select
DISTINCT DISTINCT
m.product_id, 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 trim(CONVERT(v.key_name USING utf8)) as tag_category
FROM product_metadata m FROM product_metadata m
JOIN product_key_value_metadata v on v.id = m.product_key_value_metadatum_id 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) product = Product.objects.get(old_id=t.product_id)
for tag in tags: for tag in tags:
if product not in tag.products.all(): if tag not in product.tags.all():
product.tags.add(tag) product.tags.add(tag)
self.stdout.write(self.style.WARNING(f'Add or get tag objects.')) 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): def grape_variety(self):
return self.tags.filter(category__index_name='grape-variety') 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 @property
def related_tags(self): def related_tags(self):
return super().visible_tags.exclude(category__index_name__in=[ return super().visible_tags.exclude(category__index_name__in=[
'sugar-content', 'wine-color', 'bottles-produced', 'sugar-content', 'wine-color', 'bottles-produced',
'serial-number', 'grape-variety'] 'serial-number', 'grape-variety', 'serial_number',
) 'alcohol_percentage', 'bottle_size',
])
@property @property
def display_name(self): def display_name(self):
@ -316,6 +327,26 @@ class OnlineProduct(Product):
verbose_name_plural = _('Online products') 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): class Unit(models.Model):
"""Product unit model.""" """Product unit model."""
name = models.CharField(max_length=255, name = models.CharField(max_length=255,

View File

@ -128,6 +128,8 @@ class ProductDetailSerializer(ProductBaseSerializer):
bottles_produced = TagBaseSerializer(many=True, read_only=True) bottles_produced = TagBaseSerializer(many=True, read_only=True)
sugar_contents = TagBaseSerializer(many=True, read_only=True) sugar_contents = TagBaseSerializer(many=True, read_only=True)
grape_variety = 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, image_url = serializers.URLField(allow_null=True,
read_only=True) read_only=True)
new_image = ImageBaseSerializer(source='crop_main_image', 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', 'new_image',
'grape_variety', 'grape_variety',
'average_price', '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 import models as transfer_models
from transfer.serializers import product as product_serializers 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(): def transfer_wine_color():
@ -50,8 +39,8 @@ def transfer_wine_bottles_produced():
) )
queryset = [vars(query) for query in raw_queryset] queryset = [vars(query) for query in raw_queryset]
serialized_data = product_serializers.WineBottlesProducedSerializer( serialized_data = product_serializers.WineBottlesProducedSerializer(
data=queryset, data=queryset,
many=True) many=True)
if serialized_data.is_valid(): if serialized_data.is_valid():
serialized_data.save() serialized_data.save()
else: else:
@ -69,8 +58,8 @@ def transfer_wine_classification_type():
) )
queryset = [vars(query) for query in raw_queryset] queryset = [vars(query) for query in raw_queryset]
serialized_data = product_serializers.WineClassificationTypeSerializer( serialized_data = product_serializers.WineClassificationTypeSerializer(
data=queryset, data=queryset,
many=True) many=True)
if serialized_data.is_valid(): if serialized_data.is_valid():
serialized_data.save() serialized_data.save()
else: else:
@ -79,10 +68,10 @@ def transfer_wine_classification_type():
def transfer_wine_standard(): def transfer_wine_standard():
queryset = transfer_models.ProductClassification.objects.filter(parent_id__isnull=True) \ queryset = transfer_models.ProductClassification.objects.filter(parent_id__isnull=True) \
.exclude(type='Classification') .exclude(type='Classification')
serialized_data = product_serializers.ProductStandardSerializer( serialized_data = product_serializers.ProductStandardSerializer(
data=list(queryset.values()), data=list(queryset.values()),
many=True) many=True)
if serialized_data.is_valid(): if serialized_data.is_valid():
serialized_data.save() serialized_data.save()
else: else:
@ -92,8 +81,8 @@ def transfer_wine_standard():
def transfer_wine_classifications(): def transfer_wine_classifications():
queryset = transfer_models.ProductClassification.objects.filter(type='Classification') queryset = transfer_models.ProductClassification.objects.filter(type='Classification')
serialized_data = product_serializers.ProductClassificationSerializer( serialized_data = product_serializers.ProductClassificationSerializer(
data=list(queryset.values()), data=list(queryset.values()),
many=True) many=True)
if serialized_data.is_valid(): if serialized_data.is_valid():
serialized_data.save() serialized_data.save()
else: else:
@ -104,8 +93,8 @@ def transfer_product():
errors = [] errors = []
queryset = transfer_models.Products.objects.all() queryset = transfer_models.Products.objects.all()
serialized_data = product_serializers.ProductSerializer( serialized_data = product_serializers.ProductSerializer(
data=list(queryset.values()), data=list(queryset.values()),
many=True) many=True)
if serialized_data.is_valid(): if serialized_data.is_valid():
serialized_data.save() serialized_data.save()
else: else:
@ -117,8 +106,8 @@ def transfer_product_note():
errors = [] errors = []
queryset = transfer_models.ProductNotes.objects.exclude(text='') queryset = transfer_models.ProductNotes.objects.exclude(text='')
serialized_data = product_serializers.ProductNoteSerializer( serialized_data = product_serializers.ProductNoteSerializer(
data=list(queryset.values()), data=list(queryset.values()),
many=True) many=True)
if serialized_data.is_valid(): if serialized_data.is_valid():
serialized_data.save() serialized_data.save()
else: else:
@ -130,8 +119,8 @@ def transfer_plate():
errors = [] errors = []
queryset = transfer_models.Merchandise.objects.all() queryset = transfer_models.Merchandise.objects.all()
serialized_data = product_serializers.PlateSerializer( serialized_data = product_serializers.PlateSerializer(
data=list(queryset.values()), data=list(queryset.values()),
many=True) many=True)
if serialized_data.is_valid(): if serialized_data.is_valid():
serialized_data.save() serialized_data.save()
else: else:
@ -143,8 +132,8 @@ def transfer_plate_image():
errors = [] errors = []
queryset = transfer_models.Merchandise.objects.all() queryset = transfer_models.Merchandise.objects.all()
serialized_data = product_serializers.PlateImageSerializer( serialized_data = product_serializers.PlateImageSerializer(
data=list(queryset.values()), data=list(queryset.values()),
many=True) many=True)
if serialized_data.is_valid(): if serialized_data.is_valid():
serialized_data.save() serialized_data.save()
else: else:
@ -153,7 +142,6 @@ def transfer_plate_image():
data_types = { data_types = {
"partner": [transfer_partner],
"wine_characteristics": [ "wine_characteristics": [
transfer_wine_sugar_content, transfer_wine_sugar_content,
transfer_wine_color, transfer_wine_color,
@ -161,12 +149,12 @@ data_types = {
transfer_wine_classification_type, transfer_wine_classification_type,
transfer_wine_standard, transfer_wine_standard,
transfer_wine_classifications, transfer_wine_classifications,
], ],
"product": [ "product": [
transfer_product, transfer_product,
], ],
"product_note": [ "product_note": [
transfer_product_note, transfer_product_note,
], ],
"souvenir": [ "souvenir": [
transfer_plate, transfer_plate,

View File

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

View File

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

View File

@ -19,6 +19,7 @@ class BaseTestCase(APITestCase):
username=self.username, username=self.username,
email=self.email, email=self.email,
password=self.password, password=self.password,
is_staff=True,
) )
tokens = User.create_jwt_tokens(self.user) 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): class InquiriesTestCase(BaseTestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()

View File

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

View File

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

View File

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

View File

@ -8,6 +8,9 @@ from timetable.models import Timetable
class ScheduleRUDSerializer(serializers.ModelSerializer): class ScheduleRUDSerializer(serializers.ModelSerializer):
"""Serializer for Establishment model.""" """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', weekday_display = serializers.CharField(source='get_weekday_display',
read_only=True) read_only=True)
@ -18,9 +21,6 @@ class ScheduleRUDSerializer(serializers.ModelSerializer):
opening_at = serializers.TimeField(required=False) opening_at = serializers.TimeField(required=False)
closed_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: class Meta:
"""Meta class.""" """Meta class."""
model = Timetable model = Timetable

View File

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

View File

@ -217,10 +217,10 @@ class CityNames(MigrateMixin):
class CityPhotos(MigrateMixin): class CityPhotos(MigrateMixin):
using = 'legacy' using = 'legacy'
# city_id = models.IntegerField(blank=True, null=True)
city = models.ForeignKey(Cities, models.DO_NOTHING, 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_file_name = models.CharField(max_length=255, blank=True, null=True)
attachment_content_type = 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) geometries = models.CharField(max_length=1024, blank=True, null=True)
attachment_file_size = models.IntegerField(blank=True, null=True) attachment_file_size = models.IntegerField(blank=True, null=True)
attachment_updated_at = models.DateTimeField(blank=True, null=True) attachment_updated_at = models.DateTimeField(blank=True, null=True)
@ -581,22 +581,19 @@ class EstablishmentInfos(MigrateMixin):
db_table = 'establishment_infos' db_table = 'establishment_infos'
# class EstablishmentMerchandises(MigrateMixin): class EstablishmentMerchandises(MigrateMixin):
# using = 'legacy' using = 'legacy'
#
# establishment = models.ForeignKey('Establishments', models.DO_NOTHING, blank=True, null=True)
# 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) class Meta:
# gifted = models.IntegerField(blank=True, null=True) managed = False
# quantity = models.IntegerField(blank=True, null=True) db_table = 'establishment_merchandises'
# created_at = models.DateTimeField()
# updated_at = models.DateTimeField()
#
# class Meta:
# managed = False
# db_table = 'establishment_merchandises'
class Menus(MigrateMixin): class Menus(MigrateMixin):

View File

@ -1,28 +1,53 @@
from rest_framework import serializers from rest_framework import serializers
from establishment.models import Establishment
from partner.models import Partner from partner.models import Partner
class PartnerSerializer(serializers.ModelSerializer): class PartnerSerializer(serializers.Serializer):
backlink_url = serializers.CharField(source="url") id = serializers.IntegerField()
partnership_icon = serializers.CharField() establishment_id = serializers.IntegerField()
partnership_name = serializers.CharField() partnership_name = serializers.CharField(allow_null=True)
partnership_icon = serializers.CharField(allow_null=True)
class Meta: backlink_url = serializers.CharField(allow_null=True)
model = Partner created_at = serializers.DateTimeField(format='%m-%d-%Y %H:%M:%S')
fields = ( type = serializers.CharField(allow_null=True)
"backlink_url", starting_date = serializers.DateField(allow_null=True)
"partnership_icon", expiry_date = serializers.DateField(allow_null=True)
"partnership_name" price_per_month = serializers.DecimalField(max_digits=10, decimal_places=2, allow_null=True)
)
def validate(self, data): def validate(self, data):
data["image"] = partnership_to_image_url.get(data["partnership_name"]).get(data["partnership_icon"]) data.update({
data.pop("partnership_name") 'old_id': data.pop('id'),
data.pop("partnership_icon") '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 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): 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 = { partnership_to_image_url = {

View File

@ -265,7 +265,7 @@ class ProductSerializer(TransferSerializerMixin):
state = serializers.CharField() state = serializers.CharField()
bottles_produced = serializers.CharField(allow_null=True, allow_blank=True) bottles_produced = serializers.CharField(allow_null=True, allow_blank=True)
unique_key = serializers.CharField(allow_null=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: class Meta:
model = models.Product model = models.Product

View File

@ -4,6 +4,7 @@ from django.conf import settings
from django.db.transaction import on_commit from django.db.transaction import on_commit
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from rest_framework import generics, status from rest_framework import generics, status
from rest_framework.decorators import action
from rest_framework.response import Response from rest_framework.response import Response
from gallery.tasks import delete_image from gallery.tasks import delete_image
@ -12,7 +13,7 @@ from search_indexes.documents import es_update
# JWT # JWT
# Login base view mixins # Login base view mixins
class JWTGenericViewMixin(generics.GenericAPIView): class JWTGenericViewMixin:
"""JWT view mixin""" """JWT view mixin"""
ACCESS_TOKEN_HTTP_ONLY = False ACCESS_TOKEN_HTTP_ONLY = False
@ -39,30 +40,31 @@ class JWTGenericViewMixin(generics.GenericAPIView):
""" """
COOKIES = [] COOKIES = []
if hasattr(self.request, 'locale'): if hasattr(self, 'request'):
COOKIES.append(self.COOKIE(key='locale', if hasattr(self.request, 'locale'):
value=self.request.locale, COOKIES.append(self.COOKIE(key='locale',
http_only=self.ACCESS_TOKEN_HTTP_ONLY, value=self.request.locale,
secure=self.LOCALE_SECURE, http_only=self.ACCESS_TOKEN_HTTP_ONLY,
max_age=settings.COOKIES_MAX_AGE if permanent else None)) secure=self.LOCALE_SECURE,
if hasattr(self.request, 'country_code'): max_age=settings.COOKIES_MAX_AGE if permanent else None))
COOKIES.append(self.COOKIE(key='country_code', if hasattr(self.request, 'country_code'):
value=self.request.country_code, COOKIES.append(self.COOKIE(key='country_code',
http_only=self.COUNTRY_CODE_HTTP_ONLY, value=self.request.country_code,
secure=self.COUNTRY_CODE_SECURE, http_only=self.COUNTRY_CODE_HTTP_ONLY,
max_age=settings.COOKIES_MAX_AGE if permanent else None)) secure=self.COUNTRY_CODE_SECURE,
if access_token: max_age=settings.COOKIES_MAX_AGE if permanent else None))
COOKIES.append(self.COOKIE(key='access_token', if access_token:
value=access_token, COOKIES.append(self.COOKIE(key='access_token',
http_only=self.ACCESS_TOKEN_HTTP_ONLY, value=access_token,
secure=self.ACCESS_TOKEN_SECURE, http_only=self.ACCESS_TOKEN_HTTP_ONLY,
max_age=settings.COOKIES_MAX_AGE if permanent else None)) secure=self.ACCESS_TOKEN_SECURE,
if refresh_token: max_age=settings.COOKIES_MAX_AGE if permanent else None))
COOKIES.append(self.COOKIE(key='refresh_token', if refresh_token:
value=refresh_token, COOKIES.append(self.COOKIE(key='refresh_token',
http_only=self.REFRESH_TOKEN_HTTP_ONLY, value=refresh_token,
secure=self.REFRESH_TOKEN_SECURE, http_only=self.REFRESH_TOKEN_HTTP_ONLY,
max_age=settings.COOKIES_MAX_AGE if permanent else None)) secure=self.REFRESH_TOKEN_SECURE,
max_age=settings.COOKIES_MAX_AGE if permanent else None))
return COOKIES return COOKIES
def _put_cookies_in_response(self, cookies: list, response: Response): def _put_cookies_in_response(self, cookies: list, response: Response):
@ -156,3 +158,29 @@ class FavoritesCreateDestroyMixinView(generics.CreateAPIView,
instance.delete() instance.delete()
self.es_update_base_object() 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 # TMP TODO remove it later
# Временный хардкод для демонстрации > 15 ноября, потом удалить! # Временный хардкод для демонстрации > 15 ноября, потом удалить!
CAROUSEL_ITEMS = [230, 231, 232] CAROUSEL_ITEMS = [465]
ESTABLISHMENT_CHOSEN_TAGS = ['gastronomic', 'en_vogue', 'terrace', 'streetfood', 'business', 'bar_cocktail', 'brunch', 'pop'] ESTABLISHMENT_CHOSEN_TAGS = ['gastronomic', 'en_vogue', 'terrace', 'streetfood', 'business', 'bar_cocktail', 'brunch', 'pop']
NEWS_CHOSEN_TAGS = ['eat', 'drink', 'cook', 'style', 'international', 'event', 'partnership'] NEWS_CHOSEN_TAGS = ['eat', 'drink', 'cook', 'style', 'international', 'event', 'partnership']
INTERNATIONAL_COUNTRY_CODES = ['www', 'main', 'next'] 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', 'search_indexes.documents.product': 'development_product',
} }
# ELASTICSEARCH_DSL_AUTOSYNC = False
sentry_sdk.init( sentry_sdk.init(
dsn="https://35d9bb789677410ab84a822831c6314f@sentry.io/1729093", dsn="https://35d9bb789677410ab84a822831c6314f@sentry.io/1729093",

View File

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