diff --git a/apps/account/migrations/0024_role_establishment_subtype.py b/apps/account/migrations/0024_role_establishment_subtype.py new file mode 100644 index 00000000..3b9062c5 --- /dev/null +++ b/apps/account/migrations/0024_role_establishment_subtype.py @@ -0,0 +1,20 @@ +# Generated by Django 2.2.7 on 2019-12-06 06:55 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('establishment', '0067_auto_20191122_1244'), + ('account', '0023_auto_20191204_0916'), + ] + + operations = [ + migrations.AddField( + model_name='role', + name='establishment_subtype', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='establishment.EstablishmentSubType', verbose_name='Establishment subtype'), + ), + ] diff --git a/apps/account/models.py b/apps/account/models.py index 205171d0..8f6c6233 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -1,5 +1,6 @@ """Account models""" from datetime import datetime +from tabnanny import verbose from django.conf import settings from django.contrib.auth.models import AbstractUser, UserManager as BaseUserManager @@ -15,7 +16,7 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework.authtoken.models import Token from authorization.models import Application -from establishment.models import Establishment +from establishment.models import Establishment, EstablishmentSubType from location.models import Country from main.models import SiteSettings from utils.models import GMTokenGenerator @@ -33,7 +34,7 @@ class Role(ProjectBaseMixin): REVIEWER_MANGER = 6 RESTAURANT_REVIEWER = 7 SALES_MAN = 8 - WINERY_REVIEWER = 9 + WINERY_REVIEWER = 9 # Establishments subtype "winery" SELLER = 10 ROLE_CHOICES = ( @@ -54,6 +55,9 @@ class Role(ProjectBaseMixin): null=True, blank=True, on_delete=models.SET_NULL) site = models.ForeignKey(SiteSettings, verbose_name=_('Site settings'), null=True, blank=True, on_delete=models.SET_NULL) + establishment_subtype = models.ForeignKey(EstablishmentSubType, + verbose_name=_('Establishment subtype'), + null=True, blank=True, on_delete=models.SET_NULL) class UserManager(BaseUserManager): diff --git a/apps/account/serializers/back.py b/apps/account/serializers/back.py index 699210e7..15e0684e 100644 --- a/apps/account/serializers/back.py +++ b/apps/account/serializers/back.py @@ -33,6 +33,7 @@ class BackUserSerializer(serializers.ModelSerializer): 'email_confirmed', 'newsletter', 'roles', + 'password' ) extra_kwargs = { 'password': {'write_only': True} diff --git a/apps/account/urls/back.py b/apps/account/urls/back.py index 630a4cb9..30f21573 100644 --- a/apps/account/urls/back.py +++ b/apps/account/urls/back.py @@ -8,6 +8,6 @@ app_name = 'account' urlpatterns = [ path('role/', views.RoleLstView.as_view(), name='role-list-create'), path('user-role/', views.UserRoleLstView.as_view(), name='user-role-list-create'), - path('user/', views.UserLstView.as_view(), name='user-list-create'), + path('user/', views.UserLstView.as_view(), name='user-create-list'), path('user//', views.UserRUDView.as_view(), name='user-rud'), ] diff --git a/apps/advertisement/models.py b/apps/advertisement/models.py index 920e3389..66cd0024 100644 --- a/apps/advertisement/models.py +++ b/apps/advertisement/models.py @@ -22,6 +22,10 @@ class AdvertisementQuerySet(models.QuerySet): """Filter Advertisement by page type.""" return self.filter(page_type__name=page_type) + def by_country(self, code: str): + """Filter Advertisement by country code.""" + return self.filter(sites__country__code=code) + def by_locale(self, locale): """Filter by locale.""" return self.filter(target_languages__locale=locale) diff --git a/apps/advertisement/serializers/common.py b/apps/advertisement/serializers/common.py index 0adb74f9..9caee0c2 100644 --- a/apps/advertisement/serializers/common.py +++ b/apps/advertisement/serializers/common.py @@ -11,14 +11,11 @@ from main.models import SiteSettings class AdvertisementBaseSerializer(serializers.ModelSerializer): """Base serializer for model Advertisement.""" - languages = LanguageSerializer(many=True, read_only=True, - source='target_languages') target_languages = serializers.PrimaryKeyRelatedField( queryset=Language.objects.all(), many=True, write_only=True ) - sites = SiteShortSerializer(many=True, read_only=True) target_sites = serializers.PrimaryKeyRelatedField( queryset=SiteSettings.objects.all(), many=True, @@ -33,9 +30,7 @@ class AdvertisementBaseSerializer(serializers.ModelSerializer): 'uuid', 'url', 'block_level', - 'languages', 'target_languages', - 'sites', 'target_sites', 'start', 'end', diff --git a/apps/advertisement/views/common.py b/apps/advertisement/views/common.py index 43c6e965..02c61873 100644 --- a/apps/advertisement/views/common.py +++ b/apps/advertisement/views/common.py @@ -28,5 +28,8 @@ class AdvertisementPageTypeListView(AdvertisementBaseView, generics.ListAPIView) product_type = self.kwargs.get('page_type') qs = super(AdvertisementPageTypeListView, self).get_queryset() if product_type: - return qs.by_page_type(product_type) + return qs.by_page_type(product_type) \ + .by_country(self.request.country_code) \ + .by_locale(self.request.locale) \ + .distinct('id') return qs.none() diff --git a/apps/advertisement/views/web.py b/apps/advertisement/views/web.py index db1cfde8..a9e527a0 100644 --- a/apps/advertisement/views/web.py +++ b/apps/advertisement/views/web.py @@ -7,3 +7,4 @@ class AdvertisementPageTypeWebListView(AdvertisementPageTypeListView): """Advertisement mobile list view.""" serializer_class = AdvertisementPageTypeWebListSerializer + diff --git a/apps/establishment/models.py b/apps/establishment/models.py index fb401ddd..46a45294 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -830,25 +830,6 @@ class ContactEmail(models.Model): return f'{self.email}' -# -# class Wine(TranslatedFieldsMixin, models.Model): -# """Wine model.""" -# establishment = models.ForeignKey( -# 'establishment.Establishment', verbose_name=_('establishment'), -# on_delete=models.CASCADE) -# bottles = models.IntegerField(_('bottles')) -# price_min = models.DecimalField( -# _('price min'), max_digits=14, decimal_places=2) -# price_max = models.DecimalField( -# _('price max'), max_digits=14, decimal_places=2) -# by_glass = models.BooleanField(_('by glass')) -# price_glass_min = models.DecimalField( -# _('price min'), max_digits=14, decimal_places=2) -# price_glass_max = models.DecimalField( -# _('price max'), max_digits=14, decimal_places=2) -# - - class Plate(TranslatedFieldsMixin, models.Model): """Plate model.""" STR_FIELD_NAME = 'name' diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 52d7ae69..19b4b764 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -450,14 +450,18 @@ class EstablishmentSimilarSerializer(EstablishmentBaseSerializer): address = AddressDetailSerializer(read_only=True) schedule = ScheduleRUDSerializer(many=True, allow_null=True) - establishment_type = EstablishmentTypeGeoSerializer() - artisan_category = TagBaseSerializer(many=True, allow_null=True) + type = EstablishmentTypeGeoSerializer(source='establishment_type') + artisan_category = TagBaseSerializer(many=True, allow_null=True, read_only=True) + restaurant_category = TagBaseSerializer(many=True, allow_null=True, read_only=True) + restaurant_cuisine = TagBaseSerializer(many=True, allow_null=True, read_only=True) class Meta(EstablishmentBaseSerializer.Meta): fields = EstablishmentBaseSerializer.Meta.fields + [ 'schedule', - 'establishment_type', + 'type', 'artisan_category', + 'restaurant_category', + 'restaurant_cuisine', ] diff --git a/apps/establishment/tests.py b/apps/establishment/tests.py index f46cccc3..f0c26abe 100644 --- a/apps/establishment/tests.py +++ b/apps/establishment/tests.py @@ -4,7 +4,8 @@ from account.models import User from rest_framework import status from http.cookies import SimpleCookie from main.models import Currency -from establishment.models import Establishment, EstablishmentType, Menu, SocialChoice, SocialNetwork +from establishment.models import Establishment, EstablishmentType, EstablishmentSubType,\ + Menu, SocialChoice, SocialNetwork # Create your tests here. from translation.models import Language from account.models import Role, UserRole @@ -87,7 +88,7 @@ class BaseTestCase(APITestCase): ) -class EstablishmentBTests(BaseTestCase): +class EstablishmentBackTests(BaseTestCase): def test_establishment_CRUD(self): params = {'page': 1, 'page_size': 1, } response = self.client.get('/api/back/establishments/', params, format='json') diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index fb5e39e3..6e2c953a 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -3,10 +3,9 @@ from django.http import Http404, HttpResponse from django.shortcuts import get_object_or_404 from rest_framework import generics, permissions, status -from utils.permissions import IsCountryAdmin, IsEstablishmentManager from establishment import filters, models, serializers from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer -from utils.permissions import IsCountryAdmin, IsEstablishmentManager +from utils.permissions import IsCountryAdmin, IsEstablishmentManager, IsWineryReviewer from utils.views import CreateDestroyGalleryViewMixin from timetable.models import Timetable from rest_framework import status @@ -25,7 +24,8 @@ class EstablishmentListCreateView(EstablishmentMixinViews, generics.ListCreateAP """Establishment list/create view.""" filter_class = filters.EstablishmentFilter - permission_classes = [IsCountryAdmin | IsEstablishmentManager] + + permission_classes = [IsWineryReviewer | IsCountryAdmin | IsEstablishmentManager] queryset = models.Establishment.objects.all() serializer_class = serializers.EstablishmentListCreateSerializer @@ -34,14 +34,14 @@ class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView): lookup_field = 'slug' queryset = models.Establishment.objects.all() serializer_class = serializers.EstablishmentRUDSerializer - permission_classes = [IsCountryAdmin | IsEstablishmentManager] + permission_classes = [IsWineryReviewer | IsCountryAdmin | IsEstablishmentManager] class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView): """Establishment schedule RUD view""" lookup_field = 'slug' serializer_class = ScheduleRUDSerializer - permission_classes = [IsEstablishmentManager] + permission_classes = [IsWineryReviewer |IsEstablishmentManager] def get_object(self): """ @@ -67,21 +67,21 @@ class EstablishmentScheduleCreateView(generics.CreateAPIView): lookup_field = 'slug' serializer_class = ScheduleCreateSerializer queryset = Timetable.objects.all() - permission_classes = [IsEstablishmentManager] + permission_classes = [IsWineryReviewer | IsEstablishmentManager] class MenuListCreateView(generics.ListCreateAPIView): """Menu list create view.""" serializer_class = serializers.MenuSerializers queryset = models.Menu.objects.all() - permission_classes = [IsEstablishmentManager] + permission_classes = [IsWineryReviewer | IsEstablishmentManager] class MenuRUDView(generics.RetrieveUpdateDestroyAPIView): """Menu RUD view.""" serializer_class = serializers.MenuRUDSerializers queryset = models.Menu.objects.all() - permission_classes = [IsEstablishmentManager] + permission_classes = [IsWineryReviewer | IsEstablishmentManager] class SocialChoiceListCreateView(generics.ListCreateAPIView): @@ -119,14 +119,14 @@ class PlateListCreateView(generics.ListCreateAPIView): serializer_class = serializers.PlatesSerializers queryset = models.Plate.objects.all() pagination_class = None - permission_classes = [IsEstablishmentManager] + permission_classes = [IsWineryReviewer | IsEstablishmentManager] class PlateRUDView(generics.RetrieveUpdateDestroyAPIView): """Plate RUD view.""" serializer_class = serializers.PlatesSerializers queryset = models.Plate.objects.all() - permission_classes = [IsEstablishmentManager] + permission_classes = [IsWineryReviewer | IsEstablishmentManager] class PhonesListCreateView(generics.ListCreateAPIView): @@ -134,14 +134,14 @@ class PhonesListCreateView(generics.ListCreateAPIView): serializer_class = serializers.ContactPhoneBackSerializers queryset = models.ContactPhone.objects.all() pagination_class = None - permission_classes = [IsEstablishmentManager] + permission_classes = [IsWineryReviewer | IsEstablishmentManager] class PhonesRUDView(generics.RetrieveUpdateDestroyAPIView): """Phones RUD view.""" serializer_class = serializers.ContactPhoneBackSerializers queryset = models.ContactPhone.objects.all() - permission_classes = [IsEstablishmentManager] + permission_classes = [IsWineryReviewer | IsEstablishmentManager] class EmailListCreateView(generics.ListCreateAPIView): @@ -149,14 +149,14 @@ class EmailListCreateView(generics.ListCreateAPIView): serializer_class = serializers.ContactEmailBackSerializers queryset = models.ContactEmail.objects.all() pagination_class = None - permission_classes = [IsEstablishmentManager] + permission_classes = [IsWineryReviewer | IsEstablishmentManager] class EmailRUDView(generics.RetrieveUpdateDestroyAPIView): """Email RUD view.""" serializer_class = serializers.ContactEmailBackSerializers queryset = models.ContactEmail.objects.all() - permission_classes = [IsEstablishmentManager] + permission_classes = [IsWineryReviewer | IsEstablishmentManager] class EmployeeListCreateView(generics.ListCreateAPIView): diff --git a/apps/favorites/views.py b/apps/favorites/views.py index bee25ced..4faa3e07 100644 --- a/apps/favorites/views.py +++ b/apps/favorites/views.py @@ -30,6 +30,8 @@ class FavoritesEstablishmentListView(generics.ListAPIView): """Override get_queryset method""" return Establishment.objects.filter(favorites__user=self.request.user) \ .order_by('-favorites').with_base_related() \ + .with_certain_tag_category_related('category', 'restaurant_category') \ + .with_certain_tag_category_related('cuisine', 'restaurant_cuisine') \ .with_certain_tag_category_related('shop_category', 'artisan_category') diff --git a/apps/product/serializers/common.py b/apps/product/serializers/common.py index d794ca93..51ff8e1e 100644 --- a/apps/product/serializers/common.py +++ b/apps/product/serializers/common.py @@ -6,7 +6,8 @@ from comment.models import Comment from comment.serializers import CommentSerializer from establishment.serializers import EstablishmentProductShortSerializer from establishment.serializers.common import _EstablishmentAddressShortSerializer -from location.serializers import WineOriginRegionBaseSerializer, WineOriginBaseSerializer +from location.serializers import WineOriginRegionBaseSerializer,\ + WineOriginBaseSerializer, EstablishmentWineOriginBaseSerializer from main.serializers import AwardSerializer from product import models from review.serializers import ReviewShortSerializer @@ -95,6 +96,7 @@ class ProductBaseSerializer(serializers.ModelSerializer): preview_image_url = serializers.URLField(allow_null=True, read_only=True) in_favorites = serializers.BooleanField(allow_null=True) + wine_origins = EstablishmentWineOriginBaseSerializer(many=True, read_only=True) class Meta: """Meta class.""" diff --git a/apps/search_indexes/documents/tag_category.py b/apps/search_indexes/documents/tag_category.py index ed69c8b7..cd2c8a90 100644 --- a/apps/search_indexes/documents/tag_category.py +++ b/apps/search_indexes/documents/tag_category.py @@ -2,6 +2,7 @@ from django.conf import settings from django_elasticsearch_dsl import Document, Index, fields from tag import models +from news.models import News TagCategoryIndex = Index(settings.ELASTICSEARCH_INDEX_NAMES.get(__name__, 'tag_category')) TagCategoryIndex.settings(number_of_shards=2, number_of_replicas=2) @@ -26,8 +27,16 @@ class TagCategoryDocument(Document): 'public', 'value_type' ) - related_models = [models.Tag] + related_models = [models.Tag, News] def get_queryset(self): return super().get_queryset().with_base_related() + + def get_instances_from_related(self, related_instance): + """If related_models is set, define how to retrieve the Car instance(s) from the related model. + The related_models option should be used with caution because it can lead in the index + to the updating of a lot of items. + """ + if isinstance(related_instance, News): + return related_instance.tags \ No newline at end of file diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index a43ebaf7..3b5561fa 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -243,8 +243,8 @@ class WineOriginSerializer(serializers.Serializer): class EstablishmentDocumentSerializer(InFavoritesMixin, DocumentSerializer): """Establishment document serializer.""" - establishment_type = EstablishmentTypeSerializer() - establishment_subtypes = EstablishmentTypeSerializer(many=True) + type = EstablishmentTypeSerializer(source='establishment_type') + subtypes = EstablishmentTypeSerializer(many=True, source='establishment_subtypes') address = AddressDocumentSerializer(allow_null=True) tags = TagsDocumentSerializer(many=True, source='visible_tags') restaurant_category = TagsDocumentSerializer(many=True, allow_null=True) @@ -280,8 +280,8 @@ class EstablishmentDocumentSerializer(InFavoritesMixin, DocumentSerializer): 'wine_origins', # 'works_now', # 'collections', - # 'establishment_type', - # 'establishment_subtypes', + 'type', + 'subtypes', ) diff --git a/apps/transfer/serializers/guide.py b/apps/transfer/serializers/guide.py index f0cddd02..8a083e55 100644 --- a/apps/transfer/serializers/guide.py +++ b/apps/transfer/serializers/guide.py @@ -68,7 +68,7 @@ class GuideSerializer(TransferSerializerMixin): class GuideFilterSerializer(TransferSerializerMixin): id = serializers.IntegerField() year = serializers.CharField(allow_null=True) - establishment_type = serializers.CharField(allow_null=True) + type = serializers.CharField(allow_null=True, source='establishment_type') countries = serializers.CharField(allow_null=True) regions = serializers.CharField(allow_null=True) subregions = serializers.CharField(allow_null=True) @@ -86,7 +86,7 @@ class GuideFilterSerializer(TransferSerializerMixin): fields = ( 'id', 'year', - 'establishment_type', + 'type', 'countries', 'regions', 'subregions', diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 30055c44..498f5932 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -7,7 +7,8 @@ from rest_framework_simplejwt.tokens import AccessToken from account.models import UserRole, Role from authorization.models import JWTRefreshToken from utils.tokens import GMRefreshToken - +from establishment.models import EstablishmentSubType +from location.models import Address class IsAuthenticatedAndTokenIsValid(permissions.BasePermission): """ @@ -56,8 +57,9 @@ class IsGuest(permissions.IsAuthenticatedOrReadOnly): """ Object-level permission to only allow owners of an object to edit it. """ - + SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS') def has_permission(self, request, view): + rules = [ request.user.is_superuser, request.method in permissions.SAFE_METHODS @@ -306,7 +308,6 @@ class IsEstablishmentManager(IsStandardUser): rules = [ # special! super().has_permission(request, view) - # super().has_object_permission(request, view, obj) ] role = Role.objects.filter(role=Role.ESTABLISHMENT_MANAGER) \ @@ -319,7 +320,6 @@ class IsEstablishmentManager(IsStandardUser): ).exists(), # special! super().has_permission(request, view) - # super().has_object_permission(request, view, obj) ] return any(rules) @@ -368,7 +368,7 @@ class IsRestaurantReviewer(IsStandardUser): # and request.user.email_confirmed, if hasattr(request.data, 'user') and hasattr(request.data, 'object_id'): role = Role.objects.filter(role=Role.RESTAURANT_REVIEWER) \ - .first() # 'Comments moderator' + .first() rules = [ UserRole.objects.filter(user=request.user, role=role, @@ -394,3 +394,58 @@ class IsRestaurantReviewer(IsStandardUser): ] return any(rules) + + +class IsWineryReviewer(IsStandardUser): + + def has_permission(self, request, view): + rules = [ + super().has_permission(request, view) + ] + + if 'type_id' in request.data and 'address_id' in request.data and request.user: + countries = Address.objects.filter(id=request.data['address_id']) + + est = EstablishmentSubType.objects.filter(establishment_type_id=request.data['type_id']) + if est.exists(): + role = Role.objects.filter(establishment_subtype_id__in=[type.id for type in est], + role=Role.WINERY_REVIEWER, + country_id__in=[country.id for country in countries]) \ + .first() + + rules.append( + UserRole.objects.filter(user=request.user, role=role).exists() + ) + + return any(rules) + + def has_object_permission(self, request, view, obj): + rules = [ + super().has_object_permission(request, view, obj) + ] + + if hasattr(obj, 'type_id') or hasattr(obj, 'establishment_type_id'): + type_id: int + if hasattr(obj, 'type_id'): + type_id = obj.type_id + else: + type_id = obj.establishment_type_id + + est = EstablishmentSubType.objects.filter(establishment_type_id=type_id) + role = Role.objects.filter(role=Role.WINERY_REVIEWER, + establishment_subtype_id__in=[id for type.id in est], + country_id=obj.country_id).first() + + object_id: int + if hasattr(obj, 'object_id'): + object_id = obj.object_id + else: + object_id = obj.establishment_id + + rules = [ + UserRole.objects.filter(user=request.user, role=role, + establishment_id=object_id + ).exists(), + super().has_object_permission(request, view, obj) + ] + return any(rules) \ No newline at end of file