+ array of payment_proof

+ Checklist.images is m2m now
+ Image.type & dynamic upload path
This commit is contained in:
Phil Zhitnikov 2023-07-07 08:22:15 +04:00
parent 810fd7ce3a
commit fc2297b17c
5 changed files with 158 additions and 26 deletions

View File

@ -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='Подтверждение оплаты'),
),
]

View File

@ -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='Файл изображения'),
),
]

View 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='Файл изображения'),
),
]

View File

@ -1,3 +1,4 @@
import posixpath
from decimal import Decimal
import random
import string
@ -166,18 +167,37 @@ class PaymentMethod(models.Model):
return self.name
def image_upload_path(instance, filename):
dirname = Image.TYPE_TO_UPLOAD_PATH[instance.type]
return posixpath.join(dirname, filename)
@cleanup.select
class Image(models.Model):
image = models.ImageField(upload_to='checklist_images')
is_preview = models.BooleanField(default=False)
checklist = models.ForeignKey('Checklist', on_delete=models.CASCADE, related_name='images')
DEFAULT = 0
PREVIEW = 1
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:
verbose_name = 'Изображение'
verbose_name_plural = 'Изображения'
def __str__(self):
return getattr(self.image, 'name')
return f"{self.get_type_display()}: {getattr(self.image, 'name', '')}"
def generate_checklist_id():
@ -195,6 +215,7 @@ def generate_checklist_id():
class ChecklistQuerySet(models.QuerySet):
def with_base_related(self):
return self.select_related('manager', 'category', 'payment_method', 'promocode')\
.prefetch_related('payment_proof') \
.prefetch_related(Prefetch('images', to_attr='_images'))
def default_ordering(self):
@ -299,8 +320,10 @@ class Checklist(models.Model):
payment_method = models.ForeignKey('PaymentMethod', verbose_name='Метод оплаты',
null=True, blank=True,
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)
# trackid
@ -330,6 +353,8 @@ class Checklist(models.Model):
no_comission = False
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
price -= promocode.discount * self.price_rub
@ -362,10 +387,10 @@ class Checklist(models.Model):
def preview_image(self):
# Prefer annotated field
if hasattr(self, '_images'):
# Get first is_preview image from all images
return next(filter(lambda x: x.is_preview, self._images), None)
# Get first preview image from all images
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
def preview_image_url(self):
@ -375,9 +400,9 @@ class Checklist(models.Model):
def main_images(self) -> list:
# Prefer prefetched field
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
def title(self):
@ -399,11 +424,13 @@ class Checklist(models.Model):
preview.save(image_io, format='JPEG')
# 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',
content=ContentFile(image_io.getvalue()),
save=True)
self.images.add(image_obj)
def save(self, *args, **kwargs):
if self.id:
old_obj = Checklist.objects.filter(id=self.id).first()

View File

@ -58,7 +58,8 @@ class ImageListSerializer(serializers.ListSerializer):
child = ImageSerializer()
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)
def to_representation(self, data):
@ -72,7 +73,7 @@ class ChecklistSerializer(serializers.ModelSerializer):
link = serializers.URLField(source='product_link', required=False)
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)
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',
queryset=PaymentMethod.objects.all(),
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)
trackid = serializers.CharField(source='poizon_tracking', 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)
currentDate = serializers.DateTimeField(source='status_updated_at', read_only=True)
def _create_images(self, instance, images):
img_objs = [Image(image=img['image'], checklist_id=instance.id) for img in images]
img_objs = Image.objects.bulk_create(img_objs)
def _collect_images_by_fields(self, validated_data):
images = {}
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))
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):
images = validated_data.pop('main_images', [])
images = self._collect_images_by_fields(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
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)
# Replace images basically
if images:
instance.images.all().delete()
self._create_images(instance, images)
instance.refresh_from_db(fields=('images',))
# Invalidate prefetched images
delattr(instance, '_images')
return instance
@staticmethod