+ array of payment_proof
+ Checklist.images is m2m now + Image.type & dynamic upload path
This commit is contained in:
parent
810fd7ce3a
commit
fc2297b17c
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Generated by Django 4.2.2 on 2023-07-07 03:08
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('store', '0034_alter_promocode_discount'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='image',
|
||||||
|
name='checklist',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='checklist',
|
||||||
|
name='images',
|
||||||
|
field=models.ManyToManyField(related_name='+', to='store.image', verbose_name='Изображения'),
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='checklist',
|
||||||
|
name='payment_proof',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='checklist',
|
||||||
|
name='payment_proof',
|
||||||
|
field=models.ManyToManyField(related_name='+', to='store.image', verbose_name='Подтверждение оплаты'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Generated by Django 4.2.2 on 2023-07-07 03:17
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('store', '0035_remove_image_checklist_checklist_images_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='image',
|
||||||
|
name='is_preview',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='image',
|
||||||
|
name='type',
|
||||||
|
field=models.PositiveSmallIntegerField(choices=[(0, 'Изображение'), (1, 'Превью')], default=0, verbose_name='Тип'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='checklist',
|
||||||
|
name='receipt',
|
||||||
|
field=models.ImageField(blank=True, null=True, upload_to='', verbose_name='Фото чека'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='image',
|
||||||
|
name='image',
|
||||||
|
field=models.ImageField(upload_to='', verbose_name='Файл изображения'),
|
||||||
|
),
|
||||||
|
]
|
||||||
19
store/migrations/0037_alter_image_image.py
Normal file
19
store/migrations/0037_alter_image_image.py
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Generated by Django 4.2.2 on 2023-07-07 03:23
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import store.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('store', '0036_remove_image_is_preview_image_type_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='image',
|
||||||
|
name='image',
|
||||||
|
field=models.ImageField(upload_to=store.models.image_upload_path, verbose_name='Файл изображения'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import posixpath
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
|
|
@ -166,18 +167,37 @@ class PaymentMethod(models.Model):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
def image_upload_path(instance, filename):
|
||||||
|
dirname = Image.TYPE_TO_UPLOAD_PATH[instance.type]
|
||||||
|
return posixpath.join(dirname, filename)
|
||||||
|
|
||||||
|
|
||||||
@cleanup.select
|
@cleanup.select
|
||||||
class Image(models.Model):
|
class Image(models.Model):
|
||||||
image = models.ImageField(upload_to='checklist_images')
|
DEFAULT = 0
|
||||||
is_preview = models.BooleanField(default=False)
|
PREVIEW = 1
|
||||||
checklist = models.ForeignKey('Checklist', on_delete=models.CASCADE, related_name='images')
|
DOC = 2
|
||||||
|
|
||||||
|
TYPE_CHOICES = (
|
||||||
|
(DEFAULT, 'Изображение'),
|
||||||
|
(PREVIEW, 'Превью'),
|
||||||
|
)
|
||||||
|
|
||||||
|
TYPE_TO_UPLOAD_PATH = {
|
||||||
|
DEFAULT: 'checklist_images/',
|
||||||
|
PREVIEW: 'checklist_images/',
|
||||||
|
DOC: 'docs/',
|
||||||
|
}
|
||||||
|
|
||||||
|
image = models.ImageField('Файл изображения', upload_to=image_upload_path)
|
||||||
|
type = models.PositiveSmallIntegerField('Тип', choices=TYPE_CHOICES, default=DEFAULT)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = 'Изображение'
|
verbose_name = 'Изображение'
|
||||||
verbose_name_plural = 'Изображения'
|
verbose_name_plural = 'Изображения'
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return getattr(self.image, 'name')
|
return f"{self.get_type_display()}: {getattr(self.image, 'name', '')}"
|
||||||
|
|
||||||
|
|
||||||
def generate_checklist_id():
|
def generate_checklist_id():
|
||||||
|
|
@ -195,6 +215,7 @@ def generate_checklist_id():
|
||||||
class ChecklistQuerySet(models.QuerySet):
|
class ChecklistQuerySet(models.QuerySet):
|
||||||
def with_base_related(self):
|
def with_base_related(self):
|
||||||
return self.select_related('manager', 'category', 'payment_method', 'promocode')\
|
return self.select_related('manager', 'category', 'payment_method', 'promocode')\
|
||||||
|
.prefetch_related('payment_proof') \
|
||||||
.prefetch_related(Prefetch('images', to_attr='_images'))
|
.prefetch_related(Prefetch('images', to_attr='_images'))
|
||||||
|
|
||||||
def default_ordering(self):
|
def default_ordering(self):
|
||||||
|
|
@ -299,8 +320,10 @@ class Checklist(models.Model):
|
||||||
payment_method = models.ForeignKey('PaymentMethod', verbose_name='Метод оплаты',
|
payment_method = models.ForeignKey('PaymentMethod', verbose_name='Метод оплаты',
|
||||||
null=True, blank=True,
|
null=True, blank=True,
|
||||||
on_delete=models.SET_NULL)
|
on_delete=models.SET_NULL)
|
||||||
payment_proof = models.ImageField('Подтверждение оплаты', upload_to='docs', null=True, blank=True) # paymentproovement
|
|
||||||
receipt = models.ImageField('Фото чека', upload_to='docs', null=True, blank=True) # checkphoto
|
images = models.ManyToManyField('Image', verbose_name='Изображения', related_name='+')
|
||||||
|
payment_proof = models.ManyToManyField('Image', verbose_name='Подтверждение оплаты', related_name='+') # paymentproovement
|
||||||
|
receipt = models.ImageField('Фото чека', upload_to=Image.TYPE_TO_UPLOAD_PATH[Image.DOC], null=True, blank=True) # checkphoto
|
||||||
|
|
||||||
delivery = models.CharField('Тип доставки', max_length=10, choices=DeliveryType.CHOICES, null=True, blank=True)
|
delivery = models.CharField('Тип доставки', max_length=10, choices=DeliveryType.CHOICES, null=True, blank=True)
|
||||||
# trackid
|
# trackid
|
||||||
|
|
@ -330,6 +353,8 @@ class Checklist(models.Model):
|
||||||
no_comission = False
|
no_comission = False
|
||||||
|
|
||||||
if self.promocode_id:
|
if self.promocode_id:
|
||||||
|
# We assume that working promocode was bound correctly through serializer
|
||||||
|
# and intentionally don't check if promocode is active here
|
||||||
promocode = self.promocode
|
promocode = self.promocode
|
||||||
price -= promocode.discount * self.price_rub
|
price -= promocode.discount * self.price_rub
|
||||||
|
|
||||||
|
|
@ -362,10 +387,10 @@ class Checklist(models.Model):
|
||||||
def preview_image(self):
|
def preview_image(self):
|
||||||
# Prefer annotated field
|
# Prefer annotated field
|
||||||
if hasattr(self, '_images'):
|
if hasattr(self, '_images'):
|
||||||
# Get first is_preview image from all images
|
# Get first preview image from all images
|
||||||
return next(filter(lambda x: x.is_preview, self._images), None)
|
return next(filter(lambda x: x.type == Image.PREVIEW, self._images), None)
|
||||||
|
|
||||||
return self.images.filter(is_preview=True).first()
|
return self.images.filter(type=Image.PREVIEW).first()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def preview_image_url(self):
|
def preview_image_url(self):
|
||||||
|
|
@ -375,9 +400,9 @@ class Checklist(models.Model):
|
||||||
def main_images(self) -> list:
|
def main_images(self) -> list:
|
||||||
# Prefer prefetched field
|
# Prefer prefetched field
|
||||||
if hasattr(self, '_images'):
|
if hasattr(self, '_images'):
|
||||||
return [img for img in self._images if not img.is_preview]
|
return [img for img in self._images if img.type == Image.DEFAULT]
|
||||||
|
|
||||||
return self.images.filter(is_preview=False)
|
return self.images.filter(type=Image.DEFAULT)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def title(self):
|
def title(self):
|
||||||
|
|
@ -399,11 +424,13 @@ class Checklist(models.Model):
|
||||||
preview.save(image_io, format='JPEG')
|
preview.save(image_io, format='JPEG')
|
||||||
|
|
||||||
# Create Image model and save it
|
# Create Image model and save it
|
||||||
image_obj = Image(is_preview=True, checklist_id=self.id)
|
image_obj = Image(type=Image.PREVIEW)
|
||||||
image_obj.image.save(name=f'{self.id}_preview.jpg',
|
image_obj.image.save(name=f'{self.id}_preview.jpg',
|
||||||
content=ContentFile(image_io.getvalue()),
|
content=ContentFile(image_io.getvalue()),
|
||||||
save=True)
|
save=True)
|
||||||
|
|
||||||
|
self.images.add(image_obj)
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
if self.id:
|
if self.id:
|
||||||
old_obj = Checklist.objects.filter(id=self.id).first()
|
old_obj = Checklist.objects.filter(id=self.id).first()
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,8 @@ class ImageListSerializer(serializers.ListSerializer):
|
||||||
child = ImageSerializer()
|
child = ImageSerializer()
|
||||||
|
|
||||||
def to_internal_value(self, data):
|
def to_internal_value(self, data):
|
||||||
data = [{"image": x} for x in data]
|
if isinstance(data, list):
|
||||||
|
data = [{"image": x} for x in data]
|
||||||
return super().to_internal_value(data)
|
return super().to_internal_value(data)
|
||||||
|
|
||||||
def to_representation(self, data):
|
def to_representation(self, data):
|
||||||
|
|
@ -72,7 +73,7 @@ class ChecklistSerializer(serializers.ModelSerializer):
|
||||||
link = serializers.URLField(source='product_link', required=False)
|
link = serializers.URLField(source='product_link', required=False)
|
||||||
category = serializers.SlugRelatedField(slug_field='slug', queryset=Category.objects.all())
|
category = serializers.SlugRelatedField(slug_field='slug', queryset=Category.objects.all())
|
||||||
|
|
||||||
image = ImageListSerializer(source='main_images')
|
image = ImageListSerializer(source='main_images', required=False)
|
||||||
previewimage = serializers.ImageField(source='preview_image_url', read_only=True)
|
previewimage = serializers.ImageField(source='preview_image_url', read_only=True)
|
||||||
|
|
||||||
promo = serializers.SlugRelatedField(source='promocode', slug_field='name',
|
promo = serializers.SlugRelatedField(source='promocode', slug_field='name',
|
||||||
|
|
@ -100,7 +101,7 @@ class ChecklistSerializer(serializers.ModelSerializer):
|
||||||
paymenttype = serializers.SlugRelatedField(source='payment_method', slug_field='slug',
|
paymenttype = serializers.SlugRelatedField(source='payment_method', slug_field='slug',
|
||||||
queryset=PaymentMethod.objects.all(),
|
queryset=PaymentMethod.objects.all(),
|
||||||
required=False, allow_null=True)
|
required=False, allow_null=True)
|
||||||
paymentproovement = Base64ImageField(source='payment_proof', required=False, allow_null=True)
|
paymentproovement = ImageListSerializer(source='payment_proof', required=False, allow_null=True)
|
||||||
checkphoto = Base64ImageField(source='receipt', required=False, allow_null=True)
|
checkphoto = Base64ImageField(source='receipt', required=False, allow_null=True)
|
||||||
trackid = serializers.CharField(source='poizon_tracking', required=False, allow_null=True)
|
trackid = serializers.CharField(source='poizon_tracking', required=False, allow_null=True)
|
||||||
cdek_tracking = serializers.CharField(required=False, allow_null=True)
|
cdek_tracking = serializers.CharField(required=False, allow_null=True)
|
||||||
|
|
@ -109,27 +110,49 @@ class ChecklistSerializer(serializers.ModelSerializer):
|
||||||
startDate = serializers.DateTimeField(source='created_at', read_only=True)
|
startDate = serializers.DateTimeField(source='created_at', read_only=True)
|
||||||
currentDate = serializers.DateTimeField(source='status_updated_at', read_only=True)
|
currentDate = serializers.DateTimeField(source='status_updated_at', read_only=True)
|
||||||
|
|
||||||
def _create_images(self, instance, images):
|
def _collect_images_by_fields(self, validated_data):
|
||||||
img_objs = [Image(image=img['image'], checklist_id=instance.id) for img in images]
|
images = {}
|
||||||
img_objs = Image.objects.bulk_create(img_objs)
|
for k in ('main_images', 'payment_proof'):
|
||||||
|
if k in validated_data:
|
||||||
|
images[k] = validated_data.pop(k)
|
||||||
|
|
||||||
|
return images
|
||||||
|
|
||||||
|
def _create_main_images(self, instance, images):
|
||||||
|
if images is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
img_objs = [Image(image=img['image'], type=Image.DEFAULT) for img in images]
|
||||||
|
img_objs = Image.objects.bulk_create(img_objs)
|
||||||
|
instance.images.set(img_objs)
|
||||||
instance.generate_preview(next(iter(img_objs), None))
|
instance.generate_preview(next(iter(img_objs), None))
|
||||||
|
|
||||||
|
def _create_payment_proofs(self, instance, images):
|
||||||
|
if images is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
img_objs = [Image(image=img['image'], type=Image.DOC) for img in images]
|
||||||
|
img_objs = Image.objects.bulk_create(img_objs)
|
||||||
|
instance.payment_proof.set(img_objs)
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
images = validated_data.pop('main_images', [])
|
images = self._collect_images_by_fields(validated_data)
|
||||||
|
|
||||||
instance = super().create(validated_data)
|
instance = super().create(validated_data)
|
||||||
self._create_images(instance, images)
|
self._create_main_images(instance, images.get('main_images'))
|
||||||
|
self._create_payment_proofs(instance, images.get('payment_proof'))
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
images = validated_data.pop('main_images', [])
|
images = self._collect_images_by_fields(validated_data)
|
||||||
|
|
||||||
|
self._create_main_images(instance, images.get('main_images'))
|
||||||
|
self._create_payment_proofs(instance, images.get('payment_proof'))
|
||||||
instance: Checklist = super().update(instance, validated_data)
|
instance: Checklist = super().update(instance, validated_data)
|
||||||
|
|
||||||
# Replace images basically
|
# Invalidate prefetched images
|
||||||
if images:
|
delattr(instance, '_images')
|
||||||
instance.images.all().delete()
|
|
||||||
self._create_images(instance, images)
|
|
||||||
instance.refresh_from_db(fields=('images',))
|
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user