gm-148: refactored

This commit is contained in:
Anatoly 2019-10-04 11:50:29 +03:00
parent 4796fce784
commit 4890e00b95
14 changed files with 146 additions and 113 deletions

View File

@ -18,7 +18,7 @@ def send_reset_password_email(user_id, country_code):
user.send_email(subject=_('Password resetting'),
message=user.reset_password_template(country_code))
except:
logger.error(f'METHOD_NAME: {send_reset_password_email.__name__}\n'
logger.error(f'TASK_NAME: {send_reset_password_email.__name__}\n'
f'DETAIL: Exception occurred for reset password: '
f'{user_id}')
@ -31,7 +31,7 @@ def confirm_new_email_address(user_id, country_code):
user.send_email(subject=_('Validate new email address'),
message=user.confirm_email_template(country_code))
except:
logger.error(f'METHOD_NAME: {confirm_new_email_address.__name__}\n'
logger.error(f'TASK_NAME: {confirm_new_email_address.__name__}\n'
f'DETAIL: Exception occurred for user: {user_id}')
@ -43,5 +43,5 @@ def change_email_address(user_id, country_code):
user.send_email(subject=_('Validate new email address'),
message=user.change_email_template(country_code))
except:
logger.error(f'METHOD_NAME: {change_email_address.__name__}\n'
logger.error(f'TASK_NAME: {change_email_address.__name__}\n'
f'DETAIL: Exception occurred for user: {user_id}')

View File

@ -1,7 +1,8 @@
"""Authorization app celery tasks."""
import logging
from django.utils.translation import gettext_lazy as _
from celery import shared_task
from django.utils.translation import gettext_lazy as _
from account import models as account_models
@ -17,5 +18,5 @@ def send_confirm_email(user_id, country_code):
obj.send_email(subject=_('Email confirmation'),
message=obj.confirm_email_template(country_code))
except:
logger.error(f'METHOD_NAME: {send_confirm_email.__name__}\n'
logger.error(f'TASK_NAME: {send_confirm_email.__name__}\n'
f'DETAIL: Exception occurred for user: {user_id}')

View File

@ -1,7 +1,6 @@
# Generated by Django 2.2.4 on 2019-10-02 14:56
# Generated by Django 2.2.4 on 2019-10-03 12:28
from django.db import migrations, models
import django.db.models.deletion
from django.db import migrations
import sorl.thumbnail.fields
import utils.methods
@ -13,14 +12,13 @@ class Migration(migrations.Migration):
]
operations = [
migrations.RemoveField(
model_name='image',
name='parent',
),
migrations.AlterField(
model_name='image',
name='image',
field=sorl.thumbnail.fields.ImageField(upload_to=utils.methods.image_path, verbose_name='image file'),
),
migrations.AlterField(
model_name='image',
name='parent',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='children', to='gallery.Image', verbose_name='parent image'),
),
]

View File

@ -1,20 +1,15 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from sorl.thumbnail import delete
from sorl.thumbnail.fields import ImageField as SORLImageField
from utils.methods import image_path
from django.conf import settings
from . import tasks
from utils.models import ProjectBaseMixin, SORLImageMixin, PlatformMixin
class ImageQuerySet(models.QuerySet):
"""QuerySet for model Image."""
def original_images(self):
"""Return QuerySet with original images."""
return self.filter(parent__isnull=True)
class Image(ProjectBaseMixin, SORLImageMixin, PlatformMixin):
"""Image model."""
@ -28,11 +23,6 @@ class Image(ProjectBaseMixin, SORLImageMixin, PlatformMixin):
image = SORLImageField(upload_to=image_path,
verbose_name=_('image file'))
parent = models.ForeignKey('self',
blank=True, null=True, default=None,
related_name='children',
on_delete=models.SET_DEFAULT,
verbose_name=_('parent image'))
orientation = models.PositiveSmallIntegerField(choices=ORIENTATIONS,
blank=True, null=True, default=None,
verbose_name=_('image orientation'))
@ -49,9 +39,20 @@ class Image(ProjectBaseMixin, SORLImageMixin, PlatformMixin):
"""String representation"""
return str(self.id)
def delete_from_remote_storage(self, delete_original: bool = True):
"""Delete from remote storage"""
if settings.USE_CELERY:
tasks.delete_image_from_remote_storage.delay(self.id, delete_original)
else:
tasks.delete_image_from_remote_storage(self.id, delete_original)
def delete_image(self, completely: bool = True):
"""
Deletes an instance and crops of instance from media storage.
:param completely: if set to False then removed only crop neither original image.
"""
try:
# Delete from remote storage
delete(file_=self.image.file, delete_file=completely)
except FileNotFoundError:
pass
finally:
if completely:
# Delete an instance of image
super().delete()

View File

@ -22,7 +22,6 @@ class ImageSerializer(serializers.ModelSerializer):
'id',
'file',
'url',
'parent',
'orientation',
'orientation_display',
'title',

View File

@ -2,7 +2,6 @@
import logging
from celery import shared_task
from sorl.thumbnail import delete
from . import models
@ -11,17 +10,14 @@ logger = logging.getLogger(__name__)
@shared_task
def delete_image_from_remote_storage(image_id, delete_original=True):
def delete_image(image_id: int, completely: bool = True):
"""Delete an image from remote storage."""
image_qs = models.Image.objects.filter(id=image_id)
if image_qs.exists():
try:
image = image_qs.first()
# Delete from remote storage
delete(file_=image.image.file, delete_file=delete_original)
# Delete an instance of image
image.delete()
image.delete_image(completely=completely)
except:
logger.error(f'METHOD_NAME: delete_image_from_remote_storage\n'
logger.error(f'TASK_NAME: delete_image\n'
f'DETAIL: Exception occurred while deleting an image '
f'from remote storage: image_id - {image_id}')
f'and related crops from remote storage: image_id - {image_id}')

View File

@ -6,5 +6,6 @@ from . import views
app_name = 'gallery'
urlpatterns = [
path('upload/', views.ImageBaseView.as_view(), name='upload-image'),
path('', views.ImageListCreateView.as_view(), name='list-create-image'),
path('<int:pk>/', views.ImageRetrieveDestroyView.as_view(), name='retrieve-destroy-image'),
]

View File

@ -1,19 +1,30 @@
from rest_framework import generics
from django.conf import settings
from django.db.transaction import on_commit
from rest_framework import generics, status
from rest_framework.response import Response
from . import models, serializers
from . import tasks, models, serializers
class ImageBaseView(generics.CreateAPIView):
"""Upload image to gallery."""
class ImageBaseView(generics.GenericAPIView):
"""Base Image view."""
model = models.Image
queryset = models.Image.objects.all()
serializer_class = serializers.ImageSerializer
class NewsImageListView(ImageBaseView, generics.ListAPIView):
"""Return list of uploaded images for news object."""
class ImageListCreateView(ImageBaseView, generics.ListCreateAPIView):
"""List/Create Image view."""
def get_queryset(self):
"""Override get_queryset method."""
qs = super(NewsImageListView, self).get_queryset()
return qs.filter(news_gallery__news=self.kwargs.get('news_id'))
class ImageRetrieveDestroyView(ImageBaseView, generics.RetrieveDestroyAPIView):
"""Destroy view for model Image"""
def delete(self, request, *args, **kwargs):
"""Override destroy view"""
instance = self.get_object()
if settings.USE_CELERY:
on_commit(lambda: tasks.delete_image.delay(image_id=instance.id))
else:
on_commit(lambda: tasks.delete_image(image_id=instance.id))
return Response(status=status.HTTP_204_NO_CONTENT)

View File

@ -1,11 +1,11 @@
"""News app models."""
from django.db import models
from django.contrib.contenttypes import fields as generic
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from rest_framework.reverse import reverse
from utils.models import BaseAttributes, TJSONField, TranslatedFieldsMixin
from random import sample as random_sample
class NewsType(models.Model):
@ -149,10 +149,6 @@ class News(BaseAttributes, TranslatedFieldsMixin):
def web_url(self):
return reverse('web:news:rud', kwargs={'slug': self.slug})
@property
def original_images(self):
return self.gallery.original_images()
class NewsGalleryQuerySet(models.QuerySet):
"""QuerySet for model News"""
@ -169,8 +165,6 @@ class NewsGallery(models.Model):
on_delete=models.SET_NULL,
verbose_name=_('gallery'))
objects = NewsGalleryQuerySet.as_manager()
class Meta:
"""NewsGallery meta class."""
verbose_name = _('news gallery')

View File

@ -4,7 +4,6 @@ from rest_framework import serializers
from account.serializers.common import UserBaseSerializer
from gallery.models import Image
from gallery.serializers import ImageSerializer
from location import models as location_models
from location.serializers import CountrySimpleSerializer
from main.serializers import MetaDataContentSerializer
@ -12,12 +11,61 @@ from news import models
from utils.serializers import TranslatedField, ProjectModelSerializer
class NewsCropImageSerializer(ImageSerializer):
class CropImageSerializer(serializers.Serializer):
"""Serializer for crop images for News object."""
preview_url = serializers.SerializerMethodField()
promo_horizontal_web_url = serializers.SerializerMethodField()
promo_horizontal_mobile_url = serializers.SerializerMethodField()
tile_horizontal_web_url = serializers.SerializerMethodField()
tile_horizontal_mobile_url = serializers.SerializerMethodField()
tile_vertical_web_url = serializers.SerializerMethodField()
highlight_vertical_web_url = serializers.SerializerMethodField()
editor_web_url = serializers.SerializerMethodField()
editor_mobile_url = serializers.SerializerMethodField()
def get_preview_url(self, obj):
"""Get crop preview."""
return obj.instance.get_image_url('news_preview')
def get_promo_horizontal_web_url(self, obj):
"""Get crop promo_horizontal_web."""
return obj.instance.get_image_url('news_promo_horizontal_web')
def get_promo_horizontal_mobile_url(self, obj):
"""Get crop promo_horizontal_mobile."""
return obj.instance.get_image_url('news_promo_horizontal_mobile')
def get_tile_horizontal_web_url(self, obj):
"""Get crop tile_horizontal_web."""
return obj.instance.get_image_url('news_tile_horizontal_web')
def get_tile_horizontal_mobile_url(self, obj):
"""Get crop tile_horizontal_mobile."""
return obj.instance.get_image_url('news_tile_horizontal_mobile')
def get_tile_vertical_web_url(self, obj):
"""Get crop tile_vertical_web."""
return obj.instance.get_image_url('news_tile_vertical_web')
def get_highlight_vertical_web_url(self, obj):
"""Get crop highlight_vertical_web."""
return obj.instance.get_image_url('news_highlight_vertical_web')
def get_editor_web_url(self, obj):
"""Get crop editor_web."""
return obj.instance.get_image_url('news_editor_web')
def get_editor_mobile_url(self, obj):
"""Get crop editor_mobile."""
return obj.instance.get_image_url('news_editor_mobile')
class NewsImageSerializer(serializers.ModelSerializer):
"""Serializer for returning crop images of news image."""
orientation_display = serializers.CharField(source='get_orientation_display',
read_only=True)
web_url = serializers.SerializerMethodField()
mobile_url = serializers.SerializerMethodField()
original_url = serializers.URLField(source='image.url')
auto_crop_images = CropImageSerializer(source='image', allow_null=True)
class Meta:
model = Image
@ -25,34 +73,8 @@ class NewsCropImageSerializer(ImageSerializer):
'id',
'title',
'orientation_display',
'web_url',
'mobile_url',
]
extra_kwargs = {
'orientation': {'write_only': True}
}
def get_web_url(self, obj):
"""Return URL of cropped image by thumbnail."""
return obj.get_image_url('news_promo_horizontal_web')
def get_mobile_url(self, obj):
"""Return URL of cropped image by thumbnail."""
return obj.get_image_url('news_promo_horizontal_mobile')
class NewsImageSerializer(ImageSerializer):
"""News images"""
url = serializers.URLField(source='image.url', read_only=True)
crops = NewsCropImageSerializer(source='children', allow_null=True, many=True)
class Meta:
model = Image
fields = [
'id',
'title',
'url',
'crops',
'original_url',
'auto_crop_images',
]
extra_kwargs = {
'orientation': {'write_only': True}
@ -79,7 +101,7 @@ class NewsBaseSerializer(ProjectModelSerializer):
# related fields
news_type = NewsTypeSerializer(read_only=True)
tags = MetaDataContentSerializer(read_only=True, many=True)
gallery = NewsImageSerializer(source='original_images', read_only=True, many=True)
gallery = NewsImageSerializer(read_only=True, many=True)
class Meta:
"""Meta class."""

View File

@ -1,10 +1,12 @@
"""News app views."""
from django.conf import settings
from django.db.transaction import on_commit
from django.shortcuts import get_object_or_404
from rest_framework import generics, permissions, status
from rest_framework.response import Response
from gallery.tasks import delete_image
from news import filters, models, serializers
from gallery.serializers import ImageSerializer
class NewsMixinView:
@ -102,13 +104,14 @@ class NewsBackOfficeGalleryCreateDestroyView(NewsBackOfficeMixinView,
def destroy(self, request, *args, **kwargs):
"""Override destroy method."""
gallery_obj = self.get_object()
image_obj = gallery_obj.image
if settings.USE_CELERY:
on_commit(lambda: delete_image.delay(image_id=gallery_obj.image.id,
completely=False))
else:
on_commit(lambda: delete_image(image_id=gallery_obj.image.id,
completely=False))
# Delete an instances of NewsGallery model
gallery_obj.delete()
# Delete an instance of Image model
image_obj.delete_from_remote_storage()
return Response(status=status.HTTP_204_NO_CONTENT)
@ -128,7 +131,7 @@ class NewsBackOfficeGalleryListView(NewsBackOfficeMixinView, generics.ListAPIVie
def get_queryset(self):
"""Override get_queryset method."""
return self.get_object().gallery.original_images()
return self.get_object().gallery.all()
class NewsBackOfficeRUDView(NewsBackOfficeMixinView,

View File

@ -1,6 +1,8 @@
"""Utils app models."""
import logging
from os.path import exists
from django.conf import settings
from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.contrib.gis.db import models
from django.contrib.postgres.fields import JSONField
@ -9,12 +11,11 @@ from django.utils import timezone
from django.utils.html import mark_safe
from django.utils.translation import ugettext_lazy as _, get_language
from easy_thumbnails.fields import ThumbnailerImageField
from sorl.thumbnail import get_thumbnail
from sorl.thumbnail.fields import ImageField as SORLImageField
from utils.methods import image_path, svg_image_path
from utils.validators import svg_image_validator
from sorl.thumbnail.fields import ImageField as SORLImageField
from sorl.thumbnail import get_thumbnail
from django.conf import settings
from utils.methods import image_url_valid
logger = logging.getLogger(__name__)
@ -123,7 +124,7 @@ class OAuthProjectMixin:
def get_source(self):
"""Method to get of platform"""
return NotImplemented
return NotImplementedError
class BaseAttributes(ProjectBaseMixin):
@ -193,15 +194,18 @@ class SORLImageMixin(models.Model):
"""Meta class."""
abstract = True
def get_image(self, thumbnail_key=None):
def get_image(self, thumbnail_key: str):
"""Get thumbnail image file."""
if thumbnail_key in settings.SORL_THUMBNAIL_ALIASES:
return get_thumbnail(file_=self.image,
**settings.SORL_THUMBNAIL_ALIASES[thumbnail_key])
return get_thumbnail(
file_=self.image,
**settings.SORL_THUMBNAIL_ALIASES[thumbnail_key])
def get_image_url(self, thumbnail_key=None):
def get_image_url(self, thumbnail_key: str):
"""Get image thumbnail url."""
return self.get_image(thumbnail_key).url
crop_image = self.get_image(thumbnail_key)
if hasattr(crop_image, 'url'):
return self.get_image(thumbnail_key).url
def image_tag(self):
"""Admin preview tag."""

View File

@ -1,8 +1,10 @@
"""Utils QuerySet Mixins"""
from django.db import models
from django.db.models import Q, Sum, F
from functools import reduce
from operator import add
from django.db import models
from django.db.models import Q, F
from utils.methods import get_contenttype
@ -50,7 +52,7 @@ class RelatedObjectsCountMixin(models.QuerySet):
def filter_all_related_gt(self, count):
"""Queryset filter by all related objects count"""
exp =reduce(add, [F(f"{related_object}_count") for related_object in self._get_related_objects_names()])
exp = reduce(add, [F(f"{related_object}_count") for related_object in self._get_related_objects_names()])
return self._annotate_related_objects_count()\
.annotate(all_related_count=exp)\
.filter(all_related_count__gt=count)

View File

@ -1,6 +1,7 @@
"""Local settings."""
from .base import *
import sys
from .amazon_s3 import *
ALLOWED_HOSTS = ['*', ]
@ -23,8 +24,8 @@ CELERY_BROKER_URL = BROKER_URL
# MEDIA
MEDIA_URL = f'{SCHEMA_URI}://{DOMAIN_URI}/{MEDIA_LOCATION}/'
MEDIA_ROOT = os.path.join(PUBLIC_ROOT, MEDIA_LOCATION)
# MEDIA_URL = f'{SCHEMA_URI}://{DOMAIN_URI}/{MEDIA_LOCATION}/'
# MEDIA_ROOT = os.path.join(PUBLIC_ROOT, MEDIA_LOCATION)
# LOGGING