diff --git a/apps/location/migrations/0024_city_gallery.py b/apps/location/migrations/0024_city_gallery.py new file mode 100644 index 00000000..22774103 --- /dev/null +++ b/apps/location/migrations/0024_city_gallery.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.4 on 2019-11-07 08:46 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gallery', '0006_merge_20191027_1758'), + ('location', '0023_auto_20191107_0742'), + ] + + operations = [ + migrations.AddField( + model_name='city', + name='gallery', + field=models.ManyToManyField(through='location.CityGallery', to='gallery.Image'), + ), + ] diff --git a/apps/location/migrations/0025_auto_20191107_2005.py b/apps/location/migrations/0025_auto_20191107_2005.py new file mode 100644 index 00000000..04bc0f59 --- /dev/null +++ b/apps/location/migrations/0025_auto_20191107_2005.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-11-07 20:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('location', '0024_city_gallery'), + ] + + operations = [ + migrations.AlterField( + model_name='city', + name='gallery', + field=models.ManyToManyField(blank=True, through='location.CityGallery', to='gallery.Image'), + ), + ] diff --git a/apps/location/migrations/0026_auto_20191107_2010.py b/apps/location/migrations/0026_auto_20191107_2010.py new file mode 100644 index 00000000..6bd9660a --- /dev/null +++ b/apps/location/migrations/0026_auto_20191107_2010.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-11-07 20:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('location', '0025_auto_20191107_2005'), + ] + + operations = [ + migrations.AlterField( + model_name='city', + name='gallery', + field=models.ManyToManyField(blank=True, null=True, through='location.CityGallery', to='gallery.Image'), + ), + ] diff --git a/apps/location/models.py b/apps/location/models.py index b89761c6..67385dc2 100644 --- a/apps/location/models.py +++ b/apps/location/models.py @@ -109,6 +109,8 @@ class City(models.Model): map_ref = models.CharField(max_length=255, blank=True, null=True) situation = models.CharField(max_length=255, blank=True, null=True) + gallery = models.ManyToManyField('gallery.Image', through='location.CityGallery', blank=True) + objects = CityQuerySet.as_manager() class Meta: diff --git a/apps/location/serializers/common.py b/apps/location/serializers/common.py index 554a306b..36163e12 100644 --- a/apps/location/serializers/common.py +++ b/apps/location/serializers/common.py @@ -4,6 +4,7 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers from location import models from utils.serializers import TranslatedField +from gallery.models import Image class CountrySerializer(serializers.ModelSerializer): @@ -56,6 +57,116 @@ class RegionSerializer(serializers.ModelSerializer): ] +class CropImageSerializer(serializers.Serializer): + """Serializer for crop images for City 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 CityImageSerializer(serializers.ModelSerializer): + """Serializer for returning crop images of news image.""" + + orientation_display = serializers.CharField(source='get_orientation_display', + read_only=True) + original_url = serializers.URLField(source='image.url') + auto_crop_images = CropImageSerializer(source='image', allow_null=True) + + class Meta: + model = Image + fields = [ + 'id', + 'title', + 'orientation_display', + 'original_url', + 'auto_crop_images', + ] + extra_kwargs = { + 'orientation': {'write_only': True} + } + + +class CityGallerySerializer(serializers.ModelSerializer): + """Serializer class for model NewsGallery.""" + + class Meta: + """Meta class""" + + model = models.CityGallery + fields = [ + 'id', + 'is_main', + ] + + def get_request_kwargs(self): + """Get url kwargs from request.""" + return self.context.get('request').parser_context.get('kwargs') + + def validate(self, attrs): + """Override validate method.""" + news_pk = self.get_request_kwargs().get('pk') + image_id = self.get_request_kwargs().get('image_id') + + news_qs = models.City.objects.filter(pk=news_pk) + image_qs = Image.objects.filter(id=image_id) + + if not news_qs.exists(): + raise serializers.ValidationError({'detail': _('News not found')}) + if not image_qs.exists(): + raise serializers.ValidationError({'detail': _('Image not found')}) + + news = news_qs.first() + image = image_qs.first() + + attrs['news'] = news + attrs['image'] = image + + return attrs + + class CitySerializer(serializers.ModelSerializer): """City serializer.""" region = RegionSerializer(read_only=True) @@ -69,7 +180,8 @@ class CitySerializer(serializers.ModelSerializer): queryset=models.Country.objects.all(), write_only=True ) - country = CountrySerializer() + city_gallery = CityGallerySerializer(many=True, read_only=True) + country = CountrySerializer(read_only=True) class Meta: model = models.City @@ -80,10 +192,10 @@ class CitySerializer(serializers.ModelSerializer): 'region', 'region_id', 'country_id', - 'country', 'postal_code', 'is_island', - 'city_gallery' + 'city_gallery', + 'country' ] diff --git a/apps/location/tests.py b/apps/location/tests.py index 3eaefd85..ec2d1437 100644 --- a/apps/location/tests.py +++ b/apps/location/tests.py @@ -162,6 +162,7 @@ class CityTests(BaseTestCase): response = self.client.post('/api/back/location/cities/', data=data, format='json') response_data = response.json() + print(response_data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) response = self.client.get(f'/api/back/location/cities/{response_data["id"]}/', format='json') diff --git a/apps/location/urls/common.py b/apps/location/urls/common.py index edff6122..e2840590 100644 --- a/apps/location/urls/common.py +++ b/apps/location/urls/common.py @@ -12,6 +12,11 @@ urlpatterns = [ path('cities/', views.CityListCreateView.as_view(), name='city-list'), path('cities//', views.CityRUDView.as_view(), name='city-detail'), + path('cities//gallery/', views.CityGalleryListView.as_view(), + name='gallery-list'), + path('cities//gallery//', views.CityGalleryCreateDestroyView.as_view(), + name='gallery-create-destroy'), + path('countries/', views.CountryListView.as_view(), name='country-list'), path('countries//', views.CountryRetrieveView.as_view(), name='country-retrieve'), diff --git a/apps/location/views/common.py b/apps/location/views/common.py index f3e3be7e..5b17d31d 100644 --- a/apps/location/views/common.py +++ b/apps/location/views/common.py @@ -1,6 +1,10 @@ """Location app views.""" -from rest_framework import generics -from rest_framework import permissions +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 location import models, serializers @@ -103,6 +107,70 @@ class CityUpdateView(CityViewMixin, generics.UpdateAPIView): serializer_class = serializers.CitySerializer +class CityGalleryListView(generics.ListAPIView): + """Resource for returning gallery for news for back-office users.""" + serializer_class = serializers.CityImageSerializer + + permission_classes = (permissions.IsAuthenticated,) + queryset = models.City.objects + + def get_object(self): + """Override get_object method.""" + qs = super(CityGalleryListView, self).get_queryset() + city = get_object_or_404(qs, pk=self.kwargs['pk']) + + # May raise a permission denied + self.check_object_permissions(self.request, city) + + return city + + def get_queryset(self): + """Override get_queryset method.""" + return self.get_object().gallery.all() + + +class CityGalleryCreateDestroyView(generics.CreateAPIView, + generics.DestroyAPIView): + + permission_classes = (permissions.IsAuthenticated,) + queryset = models.City.objects + + """Resource for a create gallery for news for back-office users.""" + serializer_class = serializers.CityGallerySerializer + + def get_object(self): + """ + Returns the object the view is displaying. + """ + city_qs = self.filter_queryset(self.get_queryset()) + + city = get_object_or_404(city_qs, pk=self.kwargs['pk']) + gallery = get_object_or_404(city.news_gallery, image_id=self.kwargs['image_id']) + + # May raise a permission denied + self.check_object_permissions(self.request, gallery) + + return gallery + + def create(self, request, *args, **kwargs): + """Overridden create method""" + super().create(request, *args, **kwargs) + return Response(status=status.HTTP_201_CREATED) + + def destroy(self, request, *args, **kwargs): + """Override destroy method.""" + gallery_obj = self.get_object() + 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() + return Response(status=status.HTTP_204_NO_CONTENT) + + # Address class AddressCreateView(AddressViewMixin, generics.CreateAPIView): """Create view for model Address"""