Merge branch 'develop' of ssh://gl.id-east.ru:222/gm/gm-backend into develop
This commit is contained in:
commit
12b56da53a
|
|
@ -27,7 +27,7 @@ class Migration(migrations.Migration):
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'establishment gallery',
|
'verbose_name': 'establishment gallery',
|
||||||
'verbose_name_plural': 'establishment galleries',
|
'verbose_name_plural': 'establishment galleries',
|
||||||
'unique_together': {('establishment', 'is_main'), ('establishment', 'image')},
|
'unique_together': {('establishment', 'image')},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
|
|
|
||||||
|
|
@ -963,7 +963,7 @@ class EstablishmentGallery(IntermediateGalleryModelMixin):
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
verbose_name = _('establishment gallery')
|
verbose_name = _('establishment gallery')
|
||||||
verbose_name_plural = _('establishment galleries')
|
verbose_name_plural = _('establishment galleries')
|
||||||
unique_together = (('establishment', 'is_main'), ('establishment', 'image'))
|
unique_together = ('establishment', 'image')
|
||||||
|
|
||||||
|
|
||||||
class PositionQuerySet(models.QuerySet):
|
class PositionQuerySet(models.QuerySet):
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ from location.serializers import (
|
||||||
AddressBaseSerializer, AddressDetailSerializer, CityBaseSerializer,
|
AddressBaseSerializer, AddressDetailSerializer, CityBaseSerializer,
|
||||||
CityShortSerializer, EstablishmentWineOriginBaseSerializer,
|
CityShortSerializer, EstablishmentWineOriginBaseSerializer,
|
||||||
EstablishmentWineRegionBaseSerializer,
|
EstablishmentWineRegionBaseSerializer,
|
||||||
)
|
AddressMobileDetailSerializer)
|
||||||
from main.serializers import AwardSerializer, CurrencySerializer
|
from main.serializers import AwardSerializer, CurrencySerializer
|
||||||
from review.serializers import ReviewShortSerializer, ReviewBaseSerializer
|
from review.serializers import ReviewShortSerializer, ReviewBaseSerializer
|
||||||
from tag.serializers import TagBaseSerializer
|
from tag.serializers import TagBaseSerializer
|
||||||
|
|
@ -501,6 +501,7 @@ class MobileEstablishmentDetailSerializer(EstablishmentDetailSerializer):
|
||||||
"""Serializer for Establishment model for mobiles."""
|
"""Serializer for Establishment model for mobiles."""
|
||||||
|
|
||||||
last_comment = comment_serializers.CommentBaseSerializer(allow_null=True)
|
last_comment = comment_serializers.CommentBaseSerializer(allow_null=True)
|
||||||
|
address = AddressMobileDetailSerializer(read_only=True)
|
||||||
|
|
||||||
class Meta(EstablishmentDetailSerializer.Meta):
|
class Meta(EstablishmentDetailSerializer.Meta):
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
|
||||||
53
apps/gallery/migrations/0009_auto_20200206_1749.py
Normal file
53
apps/gallery/migrations/0009_auto_20200206_1749.py
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
# Generated by Django 2.2.7 on 2020-02-06 17:49
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import sorl.thumbnail.fields
|
||||||
|
import utils.methods
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('gallery', '0008_merge_20191212_0752'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='image',
|
||||||
|
name='created_by',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='image_records_created', to=settings.AUTH_USER_MODEL, verbose_name='created by'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='image',
|
||||||
|
name='is_public',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='Is media source public'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='image',
|
||||||
|
name='link',
|
||||||
|
field=models.URLField(blank=True, default=None, null=True, verbose_name='mp4 or youtube video link'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='image',
|
||||||
|
name='modified_by',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='image_records_modified', to=settings.AUTH_USER_MODEL, verbose_name='modified by'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='image',
|
||||||
|
name='order',
|
||||||
|
field=models.PositiveIntegerField(default=0, verbose_name='Sorting order'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='image',
|
||||||
|
name='preview',
|
||||||
|
field=sorl.thumbnail.fields.ImageField(default=None, max_length=255, null=True, upload_to=utils.methods.image_path, verbose_name='image preview'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='image',
|
||||||
|
name='image',
|
||||||
|
field=sorl.thumbnail.fields.ImageField(default=None, max_length=255, null=True, upload_to=utils.methods.image_path, verbose_name='image file'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -1,33 +1,48 @@
|
||||||
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 botocore.exceptions import ClientError
|
||||||
|
from django.conf import settings
|
||||||
|
from project.storage_backends import PublicMediaStorage
|
||||||
|
import boto3
|
||||||
from sorl import thumbnail
|
from sorl import thumbnail
|
||||||
from sorl.thumbnail.fields import ImageField as SORLImageField
|
from sorl.thumbnail.fields import ImageField as SORLImageField
|
||||||
|
|
||||||
from utils.methods import image_path
|
from utils.methods import image_path
|
||||||
from utils.models import ProjectBaseMixin, SORLImageMixin, PlatformMixin
|
from utils.models import ProjectBaseMixin, SORLImageMixin, PlatformMixin, BaseAttributes
|
||||||
|
|
||||||
|
|
||||||
class ImageQuerySet(models.QuerySet):
|
class ImageQuerySet(models.QuerySet):
|
||||||
"""QuerySet for model Image."""
|
"""QuerySet for model Image."""
|
||||||
|
|
||||||
|
|
||||||
class Image(ProjectBaseMixin, SORLImageMixin, PlatformMixin):
|
class Image(BaseAttributes, SORLImageMixin, PlatformMixin):
|
||||||
"""Image model."""
|
"""Image model."""
|
||||||
HORIZONTAL = 0
|
HORIZONTAL = 0
|
||||||
VERTICAL = 1
|
VERTICAL = 1
|
||||||
|
|
||||||
|
MEDIA_TYPES = (
|
||||||
|
_('photo'),
|
||||||
|
_('video'),
|
||||||
|
_('youtube'),
|
||||||
|
)
|
||||||
|
|
||||||
ORIENTATIONS = (
|
ORIENTATIONS = (
|
||||||
(HORIZONTAL, _('Horizontal')),
|
(HORIZONTAL, _('Horizontal')),
|
||||||
(VERTICAL, _('Vertical')),
|
(VERTICAL, _('Vertical')),
|
||||||
)
|
)
|
||||||
|
|
||||||
image = SORLImageField(max_length=255, upload_to=image_path,
|
image = SORLImageField(max_length=255, upload_to=image_path,
|
||||||
verbose_name=_('image file'))
|
verbose_name=_('image file'), default=None, null=True)
|
||||||
orientation = models.PositiveSmallIntegerField(choices=ORIENTATIONS,
|
orientation = models.PositiveSmallIntegerField(choices=ORIENTATIONS,
|
||||||
blank=True, null=True, default=None,
|
blank=True, null=True, default=None,
|
||||||
verbose_name=_('image orientation'))
|
verbose_name=_('image orientation'))
|
||||||
title = models.CharField(_('title'), max_length=255, default='')
|
title = models.CharField(_('title'), max_length=255, default='')
|
||||||
|
is_public = models.BooleanField(default=True, verbose_name=_('Is media source public'))
|
||||||
|
|
||||||
|
preview = SORLImageField(max_length=255, upload_to=image_path, verbose_name=_('image preview'), null=True,
|
||||||
|
default=None)
|
||||||
|
link = models.URLField(blank=True, null=True, default=None, verbose_name=_('mp4 or youtube video link'))
|
||||||
|
order = models.PositiveIntegerField(default=0, verbose_name=_('Sorting order'))
|
||||||
objects = ImageQuerySet.as_manager()
|
objects = ImageQuerySet.as_manager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -40,6 +55,35 @@ class Image(ProjectBaseMixin, SORLImageMixin, PlatformMixin):
|
||||||
"""String representation"""
|
"""String representation"""
|
||||||
return f'{self.id}'
|
return f'{self.id}'
|
||||||
|
|
||||||
|
def set_pubic(self, is_public=True):
|
||||||
|
if not settings.AWS_STORAGE_BUCKET_NAME:
|
||||||
|
"""Backend doesn't use aws s3"""
|
||||||
|
return
|
||||||
|
s3 = boto3.resource('s3', region_name=settings.AWS_S3_REGION_NAME, aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
|
||||||
|
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY)
|
||||||
|
bucket = s3.Bucket(settings.AWS_STORAGE_BUCKET_NAME)
|
||||||
|
if self.image:
|
||||||
|
file_object = bucket.Object(f'{PublicMediaStorage.location}/{str(self.image.file)}')
|
||||||
|
if is_public:
|
||||||
|
file_object.Acl().put(ACL='public-read')
|
||||||
|
else:
|
||||||
|
file_object.Acl().put(ACL='authenticated-read')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type(self) -> str:
|
||||||
|
if self.image:
|
||||||
|
return self.MEDIA_TYPES[0]
|
||||||
|
if self.link is not None and self.link.endswith('.mp4'):
|
||||||
|
return self.MEDIA_TYPES[1]
|
||||||
|
return self.MEDIA_TYPES[2]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def image_size_in_KB(self):
|
||||||
|
try:
|
||||||
|
return self.image.size / 1000 if self.image else None
|
||||||
|
except (FileNotFoundError, ClientError):
|
||||||
|
return None
|
||||||
|
|
||||||
def delete_image(self, completely: bool = True):
|
def delete_image(self, completely: bool = True):
|
||||||
"""
|
"""
|
||||||
Deletes an instance and crops of instance from media storage.
|
Deletes an instance and crops of instance from media storage.
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@ from sorl.thumbnail import get_thumbnail
|
||||||
from sorl.thumbnail.parsers import parse_crop, ThumbnailParseError
|
from sorl.thumbnail.parsers import parse_crop, ThumbnailParseError
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from establishment.models import Establishment
|
||||||
|
from account.serializers.common import UserBaseSerializer
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -45,6 +48,54 @@ class ImageSerializer(serializers.ModelSerializer):
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentGallerySerializer(serializers.ModelSerializer):
|
||||||
|
"""Serializer for creating and retrieving establishment media"""
|
||||||
|
type = serializers.ChoiceField(read_only=True, choices=models.Image.MEDIA_TYPES)
|
||||||
|
created_by = UserBaseSerializer(read_only=True, allow_null=True)
|
||||||
|
image_size_in_KB = serializers.DecimalField(read_only=True, decimal_places=2, max_digits=20)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.Image
|
||||||
|
fields = (
|
||||||
|
'id',
|
||||||
|
'image',
|
||||||
|
'type',
|
||||||
|
'link',
|
||||||
|
'order',
|
||||||
|
'preview',
|
||||||
|
'is_public',
|
||||||
|
'title',
|
||||||
|
'created_by',
|
||||||
|
'image_size_in_KB',
|
||||||
|
)
|
||||||
|
extra_kwargs = {
|
||||||
|
'created': {'read_only': True},
|
||||||
|
}
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
"""Overridden validate method."""
|
||||||
|
image = attrs.get('image')
|
||||||
|
|
||||||
|
if image and image.size >= settings.FILE_UPLOAD_MAX_MEMORY_SIZE:
|
||||||
|
raise serializers.ValidationError({'detail': _('File size too large: %s bytes') % image.size})
|
||||||
|
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
establishment = get_object_or_404(klass=Establishment, pk=self.context['view'].kwargs['establishment_id'])
|
||||||
|
instance = super().create(validated_data)
|
||||||
|
instance.created_by = self.context['request'].user
|
||||||
|
instance.establishment_set.add(establishment)
|
||||||
|
instance.save()
|
||||||
|
return instance
|
||||||
|
|
||||||
|
def update(self, instance: models.Image, validated_data):
|
||||||
|
if instance.is_public != validated_data.get('is_public'):
|
||||||
|
instance.set_pubic(validated_data.get('is_public', True))
|
||||||
|
return super().update(instance, validated_data)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class CropImageSerializer(ImageSerializer):
|
class CropImageSerializer(ImageSerializer):
|
||||||
"""Serializers for image crops."""
|
"""Serializers for image crops."""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,9 @@ app_name = 'gallery'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.ImageListCreateView.as_view(), name='list-create'),
|
path('', views.ImageListCreateView.as_view(), name='list-create'),
|
||||||
|
path('for_establishment/<int:establishment_id>/', views.MediaForEstablishmentView.as_view(),
|
||||||
|
name='establishment-media'),
|
||||||
|
path('media/<int:pk>/', views.MediaUpdateView.as_view(), name='media-update'),
|
||||||
path('<int:pk>/', views.ImageRetrieveDestroyView.as_view(), name='retrieve-destroy'),
|
path('<int:pk>/', views.ImageRetrieveDestroyView.as_view(), name='retrieve-destroy'),
|
||||||
path('<int:pk>/crop/', views.CropImageCreateView.as_view(), name='create-crop'),
|
path('<int:pk>/crop/', views.CropImageCreateView.as_view(), name='create-crop'),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@ from rest_framework import generics, status
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from utils.methods import get_permission_classes
|
from utils.methods import get_permission_classes
|
||||||
from utils.permissions import IsContentPageManager
|
from utils.permissions import IsContentPageManager, IsCountryAdmin, IsEstablishmentManager, \
|
||||||
|
IsProducerFoodInspector, IsEstablishmentAdministrator
|
||||||
from . import tasks, models, serializers
|
from . import tasks, models, serializers
|
||||||
|
|
||||||
|
|
||||||
class ImageBaseView(generics.GenericAPIView):
|
class ImageBaseView(generics.GenericAPIView):
|
||||||
"""Base Image view."""
|
"""Base Image view."""
|
||||||
model = models.Image
|
model = models.Image
|
||||||
|
|
@ -22,6 +22,23 @@ class ImageListCreateView(ImageBaseView, generics.ListCreateAPIView):
|
||||||
"""List/Create Image view."""
|
"""List/Create Image view."""
|
||||||
|
|
||||||
|
|
||||||
|
class MediaForEstablishmentView(ImageBaseView, generics.ListCreateAPIView):
|
||||||
|
"""View for creating and retrieving certain establishment media."""
|
||||||
|
pagination_class = None
|
||||||
|
permission_classes = (IsCountryAdmin, IsEstablishmentAdministrator, IsEstablishmentManager, IsProducerFoodInspector)
|
||||||
|
serializer_class = serializers.EstablishmentGallerySerializer
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return super().get_queryset().filter(establishment__pk=self.kwargs['establishment_id'])\
|
||||||
|
.order_by('-order').prefetch_related('created_by')
|
||||||
|
|
||||||
|
|
||||||
|
class MediaUpdateView(ImageBaseView, generics.UpdateAPIView):
|
||||||
|
"""View for updating media data"""
|
||||||
|
serializer_class = serializers.EstablishmentGallerySerializer
|
||||||
|
permission_classes = ()
|
||||||
|
|
||||||
|
|
||||||
class ImageRetrieveDestroyView(ImageBaseView, generics.RetrieveDestroyAPIView):
|
class ImageRetrieveDestroyView(ImageBaseView, generics.RetrieveDestroyAPIView):
|
||||||
"""Destroy view for model Image"""
|
"""Destroy view for model Image"""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,19 @@ class CityBaseSerializer(serializers.ModelSerializer):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CityMobileSerializer(CityBaseSerializer):
|
||||||
|
name = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
class Meta(CityBaseSerializer.Meta):
|
||||||
|
fields = CityBaseSerializer.Meta.fields + [
|
||||||
|
'name'
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_name(self, obj: models.City) -> str:
|
||||||
|
if hasattr(obj, 'name_translated'):
|
||||||
|
return obj.name_translated
|
||||||
|
|
||||||
|
|
||||||
class CityDetailSerializer(CityBaseSerializer):
|
class CityDetailSerializer(CityBaseSerializer):
|
||||||
"""Serializer for detail view."""
|
"""Serializer for detail view."""
|
||||||
image = ImageBaseSerializer(source='crop_image', read_only=True)
|
image = ImageBaseSerializer(source='crop_image', read_only=True)
|
||||||
|
|
@ -245,6 +258,10 @@ class AddressDetailSerializer(AddressBaseSerializer):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AddressMobileDetailSerializer(AddressDetailSerializer):
|
||||||
|
city = CityMobileSerializer(read_only=True)
|
||||||
|
|
||||||
|
|
||||||
class WineRegionBaseSerializer(serializers.ModelSerializer):
|
class WineRegionBaseSerializer(serializers.ModelSerializer):
|
||||||
"""Wine region serializer."""
|
"""Wine region serializer."""
|
||||||
country = CountrySerializer()
|
country = CountrySerializer()
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import string
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
from operator import or_
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
@ -242,12 +243,12 @@ def get_image_meta_by_url(url) -> (int, int, int):
|
||||||
def get_permission_classes(*args) -> list:
|
def get_permission_classes(*args) -> list:
|
||||||
"""Return permission_class object with admin permissions."""
|
"""Return permission_class object with admin permissions."""
|
||||||
from rest_framework.permissions import IsAdminUser
|
from rest_framework.permissions import IsAdminUser
|
||||||
from utils.permissions import IsCountryAdmin
|
from utils.permissions import IsCountryAdmin, IsReadOnly
|
||||||
|
|
||||||
admin_permission_classes = [IsCountryAdmin, IsAdminUser]
|
admin_permission_classes = [IsCountryAdmin, IsAdminUser, IsReadOnly]
|
||||||
permission_classes = [
|
permission_classes = [
|
||||||
reduce(
|
reduce(
|
||||||
lambda a, b: a | b, admin_permission_classes + list(args)
|
or_, admin_permission_classes + list(args)
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
return permission_classes
|
return permission_classes
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ class IsRefreshTokenValid(permissions.BasePermission):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class IsGuest(permissions.IsAuthenticatedOrReadOnly):
|
class IsGuest(permissions.BasePermission):
|
||||||
"""
|
"""
|
||||||
Object-level permission to only allow owners of an object to edit it.
|
Object-level permission to only allow owners of an object to edit it.
|
||||||
"""
|
"""
|
||||||
|
|
@ -66,6 +66,15 @@ class IsGuest(permissions.IsAuthenticatedOrReadOnly):
|
||||||
return all(rules)
|
return all(rules)
|
||||||
|
|
||||||
|
|
||||||
|
class IsReadOnly(permissions.BasePermission):
|
||||||
|
"""
|
||||||
|
Allows getting access to resource only if request method in SAFE_METHODs.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def has_permission(self, request, view):
|
||||||
|
return request.method in SAFE_HTTP_METHODS
|
||||||
|
|
||||||
|
|
||||||
class IsApprovedUser(IsAuthenticatedAndTokenIsValid):
|
class IsApprovedUser(IsAuthenticatedAndTokenIsValid):
|
||||||
"""
|
"""
|
||||||
Object-level permission to only allow owners of an object to edit it.
|
Object-level permission to only allow owners of an object to edit it.
|
||||||
|
|
@ -200,7 +209,7 @@ class IsEstablishmentAdministrator(IsApprovedUser):
|
||||||
).only('id')
|
).only('id')
|
||||||
has_permission = True if user_role.exists() else has_permission
|
has_permission = True if user_role.exists() else has_permission
|
||||||
rules.append(has_permission)
|
rules.append(has_permission)
|
||||||
return all(rules)
|
return bool(request.method in SAFE_HTTP_METHODS or all(rules))
|
||||||
|
|
||||||
def has_object_permission(self, request, view, obj):
|
def has_object_permission(self, request, view, obj):
|
||||||
rules = [
|
rules = [
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user