diff --git a/apps/location/migrations/0030_auto_20191120_1010.py b/apps/location/migrations/0030_auto_20191120_1010.py new file mode 100644 index 00000000..26c1e5ef --- /dev/null +++ b/apps/location/migrations/0030_auto_20191120_1010.py @@ -0,0 +1,34 @@ +# Generated by Django 2.2.7 on 2019-11-20 10:10 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('gallery', '0006_merge_20191027_1758'), + ('location', '0029_merge_20191119_1438'), + ] + + operations = [ + migrations.CreateModel( + name='CityGallery', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('is_main', models.BooleanField(default=False, verbose_name='Is the main image')), + ('city', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='city_gallery', to='location.City', verbose_name='city')), + ('image', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='city_gallery', to='gallery.Image', verbose_name='image')), + ], + options={ + 'verbose_name': 'city gallery', + 'verbose_name_plural': 'city galleries', + 'unique_together': {('city', 'is_main'), ('city', 'image')}, + }, + ), + migrations.AddField( + model_name='city', + name='gallery', + field=models.ManyToManyField(through='location.CityGallery', to='gallery.Image'), + ), + ] diff --git a/apps/location/models.py b/apps/location/models.py index a1e0d60f..fdc2ef5d 100644 --- a/apps/location/models.py +++ b/apps/location/models.py @@ -8,7 +8,8 @@ from django.utils.translation import gettext_lazy as _ from translation.models import Language from utils.models import (ProjectBaseMixin, SVGImageMixin, TJSONField, - TranslatedFieldsMixin, get_current_locale) + TranslatedFieldsMixin, get_current_locale, + IntermediateGalleryModelMixin, GalleryModelMixin) class CountryQuerySet(models.QuerySet): @@ -96,9 +97,8 @@ class CityQuerySet(models.QuerySet): return self.filter(country__code=code) -class City(models.Model): +class City(GalleryModelMixin): """Region model.""" - name = models.CharField(_('name'), max_length=250) code = models.CharField(_('code'), max_length=250) region = models.ForeignKey( @@ -111,6 +111,8 @@ class City(models.Model): is_island = models.BooleanField(_('is island'), default=False) old_id = models.IntegerField(null=True, blank=True, default=None) + gallery = models.ManyToManyField('gallery.Image', through='CityGallery') + objects = CityQuerySet.as_manager() class Meta: @@ -121,6 +123,24 @@ class City(models.Model): return self.name +class CityGallery(IntermediateGalleryModelMixin): + """Gallery for model City.""" + city = models.ForeignKey(City, null=True, + related_name='city_gallery', + on_delete=models.CASCADE, + verbose_name=_('city')) + image = models.ForeignKey('gallery.Image', null=True, + related_name='city_gallery', + on_delete=models.CASCADE, + verbose_name=_('image')) + + class Meta: + """CityGallery meta class.""" + verbose_name = _('city gallery') + verbose_name_plural = _('city galleries') + unique_together = (('city', 'is_main'), ('city', 'image')) + + class Address(models.Model): """Address model.""" city = models.ForeignKey(City, verbose_name=_('city'), on_delete=models.CASCADE) diff --git a/apps/location/serializers/back.py b/apps/location/serializers/back.py index c178f7fd..9a263acd 100644 --- a/apps/location/serializers/back.py +++ b/apps/location/serializers/back.py @@ -1,5 +1,8 @@ from location import models from location.serializers import common +from rest_framework import serializers +from gallery.models import Image +from django.utils.translation import gettext_lazy as _ class AddressCreateSerializer(common.AddressDetailSerializer): @@ -18,3 +21,45 @@ class CountryBackSerializer(common.CountrySerializer): 'name', 'country_id' ] + + +class CityGallerySerializer(serializers.ModelSerializer): + """Serializer class for model CityGallery.""" + + 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.""" + city_pk = self.get_request_kwargs().get('pk') + image_id = self.get_request_kwargs().get('image_id') + + city_qs = models.City.objects.filter(pk=city_pk) + image_qs = Image.objects.filter(id=image_id) + + if not city_qs.exists(): + raise serializers.ValidationError({'detail': _('City not found')}) + + if not image_qs.exists(): + raise serializers.ValidationError({'detail': _('Image not found')}) + + city = city_qs.first() + image = image_qs.first() + + if image in city.gallery.all(): + raise serializers.ValidationError({'detail': _('Image is already added.')}) + + attrs['city'] = city + attrs['image'] = image + + return attrs diff --git a/apps/location/urls/back.py b/apps/location/urls/back.py index 8fd87dc9..c5ef027b 100644 --- a/apps/location/urls/back.py +++ b/apps/location/urls/back.py @@ -11,6 +11,11 @@ urlpatterns = [ path('cities/', views.CityListCreateView.as_view(), name='city-list-create'), path('cities//', views.CityRUDView.as_view(), name='city-retrieve'), + path('cities//gallery/', views.CityGalleryListView.as_view(), + name='gallery-list'), + path('cities//gallery//', + views.CityGalleryCreateDestroyView.as_view(), + name='gallery-create-destroy'), path('countries/', views.CountryListCreateView.as_view(), name='country-list-create'), path('countries//', views.CountryRUDView.as_view(), name='country-retrieve'), diff --git a/apps/location/views/back.py b/apps/location/views/back.py index a4eee929..b6677837 100644 --- a/apps/location/views/back.py +++ b/apps/location/views/back.py @@ -4,7 +4,11 @@ from rest_framework import generics from location import models, serializers from location.views import common from utils.permissions import IsCountryAdmin -from rest_framework.permissions import IsAuthenticatedOrReadOnly +from utils.views import CreateDestroyGalleryViewMixin +from rest_framework.permissions import IsAuthenticatedOrReadOnly +from django.shortcuts import get_object_or_404 +from utils.serializers import ImageBaseSerializer + # Address @@ -35,6 +39,48 @@ class CityRUDView(common.CityViewMixin, generics.RetrieveUpdateDestroyAPIView): permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin] +class CityGalleryCreateDestroyView(common.CityViewMixin, + CreateDestroyGalleryViewMixin): + """Resource for a create gallery for product for back-office users.""" + serializer_class = serializers.CityGallerySerializer + permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin] + + 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.city_gallery, image_id=self.kwargs['image_id']) + + # May raise a permission denied + self.check_object_permissions(self.request, gallery) + + return gallery + + +class CityGalleryListView(common.CityViewMixin, + generics.ListAPIView): + """Resource for returning gallery for product for back-office users.""" + serializer_class = ImageBaseSerializer + permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin] + + 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().crop_gallery + + # Region class RegionListCreateView(common.RegionViewMixin, generics.ListCreateAPIView): """Create view for model Region""" diff --git a/apps/product/models.py b/apps/product/models.py index 2e3d26e7..6baaf43e 100644 --- a/apps/product/models.py +++ b/apps/product/models.py @@ -365,7 +365,7 @@ class ProductStandard(models.Model): class ProductGallery(IntermediateGalleryModelMixin): - + """Gallery for model Product.""" product = models.ForeignKey(Product, null=True, related_name='product_gallery', on_delete=models.CASCADE,