categorylanri import qilish qoshildi
This commit is contained in:
@@ -18,43 +18,25 @@ LOGGING = {
|
|||||||
"disable_existing_loggers": False,
|
"disable_existing_loggers": False,
|
||||||
"formatters": {
|
"formatters": {
|
||||||
"verbose": {
|
"verbose": {
|
||||||
"format": "%(asctime)s %(name)s %(levelname)s %(pathname)s:%(lineno)d - %(message)s",
|
"format": "%(asctime)s %(name)s %(levelname)s - %(message)s",
|
||||||
},
|
|
||||||
},
|
|
||||||
"filters": {
|
|
||||||
"exclude_errors": {
|
|
||||||
"()": ExcludeErrorsFilter,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"handlers": {
|
"handlers": {
|
||||||
"daily_rotating_file": {
|
"console": {
|
||||||
"level": "INFO",
|
"level": "DEBUG",
|
||||||
"class": "logging.handlers.TimedRotatingFileHandler",
|
"class": "logging.StreamHandler",
|
||||||
"filename": LOG_DIR / "django.log",
|
|
||||||
"when": "midnight",
|
|
||||||
"backupCount": 30,
|
|
||||||
"formatter": "verbose",
|
|
||||||
"filters": ["exclude_errors"],
|
|
||||||
},
|
|
||||||
"error_file": {
|
|
||||||
"level": "ERROR",
|
|
||||||
"class": "logging.handlers.TimedRotatingFileHandler",
|
|
||||||
"filename": LOG_DIR / "django_error.log",
|
|
||||||
"when": "midnight",
|
|
||||||
"backupCount": 30,
|
|
||||||
"formatter": "verbose",
|
"formatter": "verbose",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"root": {
|
||||||
|
"handlers": ["console"],
|
||||||
|
"level": "INFO",
|
||||||
|
},
|
||||||
"loggers": {
|
"loggers": {
|
||||||
"django": {
|
"django": {
|
||||||
"handlers": ["daily_rotating_file", "error_file"],
|
"handlers": ["console"],
|
||||||
"level": "INFO",
|
"level": "INFO",
|
||||||
"propagate": True,
|
"propagate": False,
|
||||||
},
|
|
||||||
"root": {
|
|
||||||
"handlers": ["daily_rotating_file", "error_file"],
|
|
||||||
"level": "INFO",
|
|
||||||
"propagate": True,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
0
core/apps/shared/management/__init__.py
Normal file
0
core/apps/shared/management/__init__.py
Normal file
0
core/apps/shared/management/commands/__init__.py
Normal file
0
core/apps/shared/management/commands/__init__.py
Normal file
60
core/apps/shared/management/commands/sync_external_images.py
Normal file
60
core/apps/shared/management/commands/sync_external_images.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import requests
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.core.files.base import ContentFile
|
||||||
|
from core.apps.vendors.models import VendorproductModel, CategoryModel, VendorModel
|
||||||
|
import os
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Syncs images from photo_url/photos_json to ImageField if they are empty'
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
self.sync_categories()
|
||||||
|
self.sync_vendors()
|
||||||
|
self.sync_products()
|
||||||
|
|
||||||
|
def download_image(self, url):
|
||||||
|
if not url or not url.startswith('http'):
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
response = requests.get(url, timeout=10)
|
||||||
|
if response.status_code == 200:
|
||||||
|
name = os.path.basename(url.split('?')[0])
|
||||||
|
if not name:
|
||||||
|
name = "image.png"
|
||||||
|
return ContentFile(response.content, name=name)
|
||||||
|
except Exception as e:
|
||||||
|
self.stdout.write(self.style.ERROR(f"Error downloading {url}: {e}"))
|
||||||
|
return None
|
||||||
|
|
||||||
|
def sync_categories(self):
|
||||||
|
self.stdout.write("Syncing Category images...")
|
||||||
|
for obj in CategoryModel.objects.filter(photo__isnull=True).exclude(photo_url__isnull=True):
|
||||||
|
if not obj.photo_url.startswith('http'): continue
|
||||||
|
file_content = self.download_image(obj.photo_url)
|
||||||
|
if file_content:
|
||||||
|
obj.photo.save(file_content.name, file_content, save=True)
|
||||||
|
self.stdout.write(self.style.SUCCESS(f"Saved photo for category: {obj.title}"))
|
||||||
|
|
||||||
|
def sync_vendors(self):
|
||||||
|
self.stdout.write("Syncing Vendor images...")
|
||||||
|
for obj in VendorModel.objects.filter(photo__isnull=True).exclude(photo_url__isnull=True):
|
||||||
|
if not obj.photo_url.startswith('http'): continue
|
||||||
|
file_content = self.download_image(obj.photo_url)
|
||||||
|
if file_content:
|
||||||
|
obj.photo.save(file_content.name, file_content, save=True)
|
||||||
|
self.stdout.write(self.style.SUCCESS(f"Saved photo for vendor: {obj.title}"))
|
||||||
|
|
||||||
|
def sync_products(self):
|
||||||
|
self.stdout.write("Syncing Product images...")
|
||||||
|
for obj in VendorproductModel.objects.filter(image__isnull=True).exclude(photos_json__isnull=True):
|
||||||
|
if not isinstance(obj.photos_json, list) or len(obj.photos_json) == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
url = obj.photos_json[0]
|
||||||
|
if not isinstance(url, str) or not url.startswith('http'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
file_content = self.download_image(url)
|
||||||
|
if file_content:
|
||||||
|
obj.image.save(file_content.name, file_content, save=True)
|
||||||
|
self.stdout.write(self.style.SUCCESS(f"Saved image for product: {obj.name}"))
|
||||||
9
core/apps/vendors/admin/category.py
vendored
9
core/apps/vendors/admin/category.py
vendored
@@ -8,6 +8,13 @@ from core.apps.vendors.models import CategoryModel
|
|||||||
class CategoryAdmin(ModelAdmin):
|
class CategoryAdmin(ModelAdmin):
|
||||||
list_display = (
|
list_display = (
|
||||||
"id",
|
"id",
|
||||||
"__str__",
|
"title",
|
||||||
|
"section",
|
||||||
|
"photo",
|
||||||
|
"photo_url",
|
||||||
|
"is_publish",
|
||||||
|
"order",
|
||||||
)
|
)
|
||||||
|
search_fields = ("title", "firestore_id")
|
||||||
|
list_filter = ("section", "is_publish")
|
||||||
|
|
||||||
|
|||||||
17
core/apps/vendors/admin/vendor_product.py
vendored
17
core/apps/vendors/admin/vendor_product.py
vendored
@@ -8,13 +8,26 @@ from core.apps.vendors.models import ProductimageModel, VendorproductModel
|
|||||||
class VendorproductAdmin(ModelAdmin):
|
class VendorproductAdmin(ModelAdmin):
|
||||||
list_display = (
|
list_display = (
|
||||||
"id",
|
"id",
|
||||||
"__str__",
|
"name",
|
||||||
|
"vendor",
|
||||||
|
"category",
|
||||||
|
"section",
|
||||||
|
"price",
|
||||||
|
"quantity",
|
||||||
|
"is_publish",
|
||||||
|
"image",
|
||||||
)
|
)
|
||||||
|
search_fields = ("name", "firestore_id", "vendor")
|
||||||
|
list_filter = ("is_publish", "category", "section")
|
||||||
|
autocomplete_fields = ("category", "section")
|
||||||
|
|
||||||
|
|
||||||
@admin.register(ProductimageModel)
|
@admin.register(ProductimageModel)
|
||||||
class ProductimageAdmin(ModelAdmin):
|
class ProductimageAdmin(ModelAdmin):
|
||||||
list_display = (
|
list_display = (
|
||||||
"id",
|
"id",
|
||||||
"__str__",
|
"product",
|
||||||
|
"image",
|
||||||
|
"order",
|
||||||
)
|
)
|
||||||
|
list_filter = ("product",)
|
||||||
|
|||||||
@@ -19,23 +19,58 @@ class BaseCategorySerializer(serializers.ModelSerializer):
|
|||||||
"is_publish",
|
"is_publish",
|
||||||
"order",
|
"order",
|
||||||
]
|
]
|
||||||
|
def to_representation(self, instance):
|
||||||
|
ret = super().to_representation(instance)
|
||||||
|
request = self.context.get("request")
|
||||||
|
|
||||||
|
photo_url = ret.get("photo_url")
|
||||||
|
if photo_url and isinstance(photo_url, str):
|
||||||
|
if not photo_url.startswith("http"):
|
||||||
|
path = f"/resources/media/{photo_url.lstrip('/')}"
|
||||||
|
if request:
|
||||||
|
ret["photo_url"] = request.build_absolute_uri(path)
|
||||||
|
else:
|
||||||
|
ret["photo_url"] = path
|
||||||
|
elif "localhost" in photo_url and request and "localhost" not in request.get_host():
|
||||||
|
path = photo_url.split("/resources/media/")[-1]
|
||||||
|
ret["photo_url"] = request.build_absolute_uri(f"/resources/media/{path}")
|
||||||
|
|
||||||
|
# Fallback for main photo if it's null
|
||||||
|
if not ret.get("photo") and ret.get("photo_url"):
|
||||||
|
ret["photo"] = ret["photo_url"]
|
||||||
|
|
||||||
|
# Ensure main photo is absolute if it's a relative path
|
||||||
|
photo = ret.get("photo")
|
||||||
|
if photo and isinstance(photo, str) and not photo.startswith("http"):
|
||||||
|
if not photo.startswith("/resources/"):
|
||||||
|
photo = f"/resources/media/{photo.lstrip('/')}"
|
||||||
|
|
||||||
|
if request:
|
||||||
|
ret["photo"] = request.build_absolute_uri(photo)
|
||||||
|
else:
|
||||||
|
ret["photo"] = photo
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
class ListCategorySerializer(BaseCategorySerializer):
|
class ListCategorySerializer(BaseCategorySerializer):
|
||||||
class Meta(BaseCategorySerializer.Meta): ...
|
class Meta(BaseCategorySerializer.Meta):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class RetrieveCategorySerializer(BaseCategorySerializer):
|
class RetrieveCategorySerializer(BaseCategorySerializer):
|
||||||
class Meta(BaseCategorySerializer.Meta): ...
|
class Meta(BaseCategorySerializer.Meta):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class CreateCategorySerializer(BaseCategorySerializer):
|
class CreateCategorySerializer(BaseCategorySerializer):
|
||||||
class Meta(BaseCategorySerializer.Meta):
|
class Meta(BaseCategorySerializer.Meta):
|
||||||
fields = [
|
fields = [
|
||||||
|
"id",
|
||||||
"firestore_id",
|
"firestore_id",
|
||||||
"section",
|
"section",
|
||||||
"title",
|
"title",
|
||||||
"description",
|
"description",
|
||||||
|
"photo",
|
||||||
"photo_url",
|
"photo_url",
|
||||||
"is_publish",
|
"is_publish",
|
||||||
"order",
|
"order",
|
||||||
|
|||||||
33
core/apps/vendors/serializers/section/Section.py
vendored
33
core/apps/vendors/serializers/section/Section.py
vendored
@@ -15,6 +15,39 @@ class BaseSectionSerializer(serializers.ModelSerializer):
|
|||||||
"service_type",
|
"service_type",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def to_representation(self, instance):
|
||||||
|
ret = super().to_representation(instance)
|
||||||
|
request = self.context.get("request")
|
||||||
|
|
||||||
|
image_url = ret.get("image_url")
|
||||||
|
if image_url and isinstance(image_url, str):
|
||||||
|
if not image_url.startswith("http"):
|
||||||
|
path = f"/resources/media/{image_url.lstrip('/')}"
|
||||||
|
if request:
|
||||||
|
ret["image_url"] = request.build_absolute_uri(path)
|
||||||
|
else:
|
||||||
|
ret["image_url"] = path
|
||||||
|
elif "localhost" in image_url and request and "localhost" not in request.get_host():
|
||||||
|
path = image_url.split("/resources/media/")[-1]
|
||||||
|
ret["image_url"] = request.build_absolute_uri(f"/resources/media/{path}")
|
||||||
|
|
||||||
|
# Fallback for main image if it's null
|
||||||
|
if not ret.get("image") and ret.get("image_url"):
|
||||||
|
ret["image"] = ret["image_url"]
|
||||||
|
|
||||||
|
# Ensure main image is absolute if it's a relative path
|
||||||
|
image = ret.get("image")
|
||||||
|
if image and isinstance(image, str) and not image.startswith("http"):
|
||||||
|
if not image.startswith("/resources/"):
|
||||||
|
image = f"/resources/media/{image.lstrip('/')}"
|
||||||
|
|
||||||
|
if request:
|
||||||
|
ret["image"] = request.build_absolute_uri(image)
|
||||||
|
else:
|
||||||
|
ret["image"] = image
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
class ListSectionSerializer(BaseSectionSerializer):
|
class ListSectionSerializer(BaseSectionSerializer):
|
||||||
pass
|
pass
|
||||||
|
|||||||
39
core/apps/vendors/serializers/vendor/vendor.py
vendored
39
core/apps/vendors/serializers/vendor/vendor.py
vendored
@@ -20,25 +20,60 @@ class BaseVendorSerializer(serializers.ModelSerializer):
|
|||||||
"photo_url",
|
"photo_url",
|
||||||
"is_active",
|
"is_active",
|
||||||
]
|
]
|
||||||
|
def to_representation(self, instance):
|
||||||
|
ret = super().to_representation(instance)
|
||||||
|
request = self.context.get("request")
|
||||||
|
|
||||||
|
photo_url = ret.get("photo_url")
|
||||||
|
if photo_url and isinstance(photo_url, str):
|
||||||
|
if not photo_url.startswith("http"):
|
||||||
|
path = f"/resources/media/{photo_url.lstrip('/')}"
|
||||||
|
if request:
|
||||||
|
ret["photo_url"] = request.build_absolute_uri(path)
|
||||||
|
else:
|
||||||
|
ret["photo_url"] = path
|
||||||
|
elif "localhost" in photo_url and request and "localhost" not in request.get_host():
|
||||||
|
path = photo_url.split("/resources/media/")[-1]
|
||||||
|
ret["photo_url"] = request.build_absolute_uri(f"/resources/media/{path}")
|
||||||
|
|
||||||
|
# Fallback for main photo if it's null
|
||||||
|
if not ret.get("photo") and ret.get("photo_url"):
|
||||||
|
ret["photo"] = ret["photo_url"]
|
||||||
|
|
||||||
|
# Ensure main photo is absolute if it's a relative path
|
||||||
|
photo = ret.get("photo")
|
||||||
|
if photo and isinstance(photo, str) and not photo.startswith("http"):
|
||||||
|
if not photo.startswith("/resources/"):
|
||||||
|
photo = f"/resources/media/{photo.lstrip('/')}"
|
||||||
|
|
||||||
|
if request:
|
||||||
|
ret["photo"] = request.build_absolute_uri(photo)
|
||||||
|
else:
|
||||||
|
ret["photo"] = photo
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
class ListVendorSerializer(BaseVendorSerializer):
|
class ListVendorSerializer(BaseVendorSerializer):
|
||||||
class Meta(BaseVendorSerializer.Meta): ...
|
class Meta(BaseVendorSerializer.Meta):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class RetrieveVendorSerializer(BaseVendorSerializer):
|
class RetrieveVendorSerializer(BaseVendorSerializer):
|
||||||
class Meta(BaseVendorSerializer.Meta): ...
|
class Meta(BaseVendorSerializer.Meta):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class CreateVendorSerializer(BaseVendorSerializer):
|
class CreateVendorSerializer(BaseVendorSerializer):
|
||||||
class Meta(BaseVendorSerializer.Meta):
|
class Meta(BaseVendorSerializer.Meta):
|
||||||
fields = [
|
fields = [
|
||||||
|
"id",
|
||||||
"firestore_id",
|
"firestore_id",
|
||||||
"section",
|
"section",
|
||||||
"title",
|
"title",
|
||||||
"description",
|
"description",
|
||||||
"phone",
|
"phone",
|
||||||
"location",
|
"location",
|
||||||
|
"photo",
|
||||||
"photo_url",
|
"photo_url",
|
||||||
"is_active",
|
"is_active",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ class BaseProductimageSerializer(serializers.ModelSerializer):
|
|||||||
model = ProductimageModel
|
model = ProductimageModel
|
||||||
fields = [
|
fields = [
|
||||||
"id",
|
"id",
|
||||||
"name",
|
"product",
|
||||||
|
"image",
|
||||||
|
"order",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -23,6 +25,7 @@ class RetrieveProductimageSerializer(BaseProductimageSerializer):
|
|||||||
class CreateProductimageSerializer(BaseProductimageSerializer):
|
class CreateProductimageSerializer(BaseProductimageSerializer):
|
||||||
class Meta(BaseProductimageSerializer.Meta):
|
class Meta(BaseProductimageSerializer.Meta):
|
||||||
fields = [
|
fields = [
|
||||||
"id",
|
"product",
|
||||||
"name",
|
"image",
|
||||||
|
"order",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -3,9 +3,13 @@ from rest_framework import serializers
|
|||||||
from core.apps.vendors.models import VendorproductModel, VendorModel, CategoryModel, SectionModel
|
from core.apps.vendors.models import VendorproductModel, VendorModel, CategoryModel, SectionModel
|
||||||
|
|
||||||
|
|
||||||
|
from core.apps.vendors.serializers.vendor_product.ProductImage import ListProductimageSerializer
|
||||||
|
|
||||||
|
|
||||||
class BaseVendorproductSerializer(serializers.ModelSerializer):
|
class BaseVendorproductSerializer(serializers.ModelSerializer):
|
||||||
category = serializers.SlugRelatedField(slug_field='firestore_id', queryset=CategoryModel.objects.all(), required=False, allow_null=True)
|
category = serializers.SlugRelatedField(slug_field='firestore_id', queryset=CategoryModel.objects.all(), required=False, allow_null=True)
|
||||||
section = serializers.SlugRelatedField(slug_field='firestore_id', queryset=SectionModel.objects.all(), required=False, allow_null=True)
|
section = serializers.SlugRelatedField(slug_field='firestore_id', queryset=SectionModel.objects.all(), required=False, allow_null=True)
|
||||||
|
images = ListProductimageSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = VendorproductModel
|
model = VendorproductModel
|
||||||
@@ -22,30 +26,106 @@ class BaseVendorproductSerializer(serializers.ModelSerializer):
|
|||||||
"quantity",
|
"quantity",
|
||||||
"is_publish",
|
"is_publish",
|
||||||
"image",
|
"image",
|
||||||
|
"images",
|
||||||
"photos_json",
|
"photos_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def to_representation(self, instance):
|
||||||
|
ret = super().to_representation(instance)
|
||||||
|
request = self.context.get("request")
|
||||||
|
photos = ret.get("photos_json")
|
||||||
|
|
||||||
|
# Handle photos_json list
|
||||||
|
processed_photos = []
|
||||||
|
if photos and isinstance(photos, list):
|
||||||
|
for photo in photos:
|
||||||
|
if isinstance(photo, str):
|
||||||
|
# Agar URL to'liq bo'lmasa yoki localhost bo'lsa, uni dinamik qilamiz
|
||||||
|
if not photo.startswith("http"):
|
||||||
|
path = f"/resources/media/{photo}"
|
||||||
|
if request:
|
||||||
|
processed_photos.append(request.build_absolute_uri(path))
|
||||||
|
else:
|
||||||
|
processed_photos.append(path)
|
||||||
|
elif "localhost" in photo and request and "localhost" not in request.get_host():
|
||||||
|
# Agar bazada localhost saqlangan bo'lsa-yu, biz boshqa domenda bo'lsak
|
||||||
|
path = photo.split("/resources/media/")[-1]
|
||||||
|
processed_photos.append(request.build_absolute_uri(f"/resources/media/{path}"))
|
||||||
|
else:
|
||||||
|
processed_photos.append(photo)
|
||||||
|
else:
|
||||||
|
processed_photos.append(photo)
|
||||||
|
|
||||||
|
ret["photos_json"] = processed_photos
|
||||||
|
|
||||||
|
# Fallback for main image if it's null
|
||||||
|
if not ret.get("image") and processed_photos:
|
||||||
|
ret["image"] = processed_photos[0]
|
||||||
|
|
||||||
|
# Ensure main image is absolute if it's a relative path (fallback for custom storage)
|
||||||
|
image = ret.get("image")
|
||||||
|
if image and isinstance(image, str) and not image.startswith("http"):
|
||||||
|
# Check if it already has resources prefix
|
||||||
|
if not image.startswith("/resources/"):
|
||||||
|
image = f"/resources/media/{image.lstrip('/')}"
|
||||||
|
|
||||||
|
if request:
|
||||||
|
ret["image"] = request.build_absolute_uri(image)
|
||||||
|
else:
|
||||||
|
ret["image"] = image
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ListVendorproductSerializer(BaseVendorproductSerializer):
|
class ListVendorproductSerializer(BaseVendorproductSerializer):
|
||||||
class Meta(BaseVendorproductSerializer.Meta): ...
|
class Meta(BaseVendorproductSerializer.Meta):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class RetrieveVendorproductSerializer(BaseVendorproductSerializer):
|
class RetrieveVendorproductSerializer(BaseVendorproductSerializer):
|
||||||
class Meta(BaseVendorproductSerializer.Meta): ...
|
class Meta(BaseVendorproductSerializer.Meta):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
from core.apps.vendors.models import VendorproductModel, ProductimageModel, VendorModel, CategoryModel, SectionModel
|
||||||
|
|
||||||
class CreateVendorproductSerializer(BaseVendorproductSerializer):
|
class CreateVendorproductSerializer(BaseVendorproductSerializer):
|
||||||
|
uploaded_images = serializers.ListField(
|
||||||
|
child=serializers.ImageField(max_length=1000000, allow_empty_file=False, use_url=False),
|
||||||
|
write_only=True,
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
|
||||||
class Meta(BaseVendorproductSerializer.Meta):
|
class Meta(BaseVendorproductSerializer.Meta):
|
||||||
fields = [
|
fields = BaseVendorproductSerializer.Meta.fields + [
|
||||||
"firestore_id",
|
"uploaded_images",
|
||||||
"vendor",
|
|
||||||
"category",
|
|
||||||
"section",
|
|
||||||
"name",
|
|
||||||
"description",
|
|
||||||
"price",
|
|
||||||
"discount_price",
|
|
||||||
"quantity",
|
|
||||||
"is_publish",
|
|
||||||
"photos_json",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
uploaded_images = validated_data.pop("uploaded_images", [])
|
||||||
|
instance = super().create(validated_data)
|
||||||
|
|
||||||
|
# Save additional gallery images
|
||||||
|
for i, img in enumerate(uploaded_images):
|
||||||
|
ProductimageModel.objects.create(
|
||||||
|
product=instance,
|
||||||
|
image=img,
|
||||||
|
order=i
|
||||||
|
)
|
||||||
|
return instance
|
||||||
|
|
||||||
|
def update(self, instance, validated_data):
|
||||||
|
uploaded_images = validated_data.pop("uploaded_images", [])
|
||||||
|
instance = super().update(instance, validated_data)
|
||||||
|
|
||||||
|
if uploaded_images:
|
||||||
|
# Optionally clear existing gallery or append?
|
||||||
|
# I'll append for now based on user's "how to upload multiple"
|
||||||
|
for i, img in enumerate(uploaded_images):
|
||||||
|
ProductimageModel.objects.create(
|
||||||
|
product=instance,
|
||||||
|
image=img,
|
||||||
|
order=instance.images.count() + i
|
||||||
|
)
|
||||||
|
return instance
|
||||||
|
|||||||
2
core/apps/vendors/views/vendor_product.py
vendored
2
core/apps/vendors/views/vendor_product.py
vendored
@@ -61,7 +61,7 @@ class VendorproductView(BaseViewSetMixin, ModelViewSet):
|
|||||||
|
|
||||||
|
|
||||||
@extend_schema(tags=["ProductImage"])
|
@extend_schema(tags=["ProductImage"])
|
||||||
class ProductimageView(BaseViewSetMixin, ReadOnlyModelViewSet):
|
class ProductimageView(BaseViewSetMixin, ModelViewSet):
|
||||||
queryset = ProductimageModel.objects.all()
|
queryset = ProductimageModel.objects.all()
|
||||||
serializer_class = ListProductimageSerializer
|
serializer_class = ListProductimageSerializer
|
||||||
permission_classes = [AllowAny]
|
permission_classes = [AllowAny]
|
||||||
|
|||||||
@@ -1,8 +1,20 @@
|
|||||||
from typing import Optional, Union
|
from typing import Optional, Union
|
||||||
|
from storages.backends.s3boto3 import S3Boto3Storage
|
||||||
from config.env import env
|
from config.env import env
|
||||||
|
|
||||||
|
|
||||||
|
class CustomMediaStorage(S3Boto3Storage):
|
||||||
|
"""Media fayllar uchun nisbiy URL qaytaradigan storage."""
|
||||||
|
def url(self, name, parameters=None, expire=None, http_method=None):
|
||||||
|
# Name: products/img.png -> /resources/media/products/img.png
|
||||||
|
return f"/resources/media/{name}"
|
||||||
|
|
||||||
|
class CustomStaticStorage(S3Boto3Storage):
|
||||||
|
"""Static fayllar uchun nisbiy URL qaytaradigan storage."""
|
||||||
|
def url(self, name, parameters=None, expire=None, http_method=None):
|
||||||
|
return f"/resources/static/{name}"
|
||||||
|
|
||||||
|
|
||||||
class Storage:
|
class Storage:
|
||||||
|
|
||||||
storages = ["AWS", "MINIO", "FILE", "STATIC"]
|
storages = ["AWS", "MINIO", "FILE", "STATIC"]
|
||||||
@@ -16,13 +28,15 @@ class Storage:
|
|||||||
def get_backend(self) -> Optional[str]:
|
def get_backend(self) -> Optional[str]:
|
||||||
match self.storage:
|
match self.storage:
|
||||||
case "AWS" | "MINIO":
|
case "AWS" | "MINIO":
|
||||||
return "storages.backends.s3boto3.S3Boto3Storage"
|
if self.sorage_type == "default":
|
||||||
|
return "core.utils.storage.CustomMediaStorage"
|
||||||
|
return "core.utils.storage.CustomStaticStorage"
|
||||||
case "FILE":
|
case "FILE":
|
||||||
return "django.core.files.storage.FileSystemStorage"
|
return "django.core.files.storage.FileSystemStorage"
|
||||||
case "STATIC":
|
case "STATIC":
|
||||||
return "django.contrib.staticfiles.storage.StaticFilesStorage"
|
return "django.contrib.staticfiles.storage.StaticFilesStorage"
|
||||||
|
|
||||||
def get_options(self) -> Optional[str]:
|
def get_options(self) -> Optional[dict]:
|
||||||
match self.storage:
|
match self.storage:
|
||||||
case "AWS" | "MINIO":
|
case "AWS" | "MINIO":
|
||||||
if self.sorage_type == "default":
|
if self.sorage_type == "default":
|
||||||
@@ -31,3 +45,4 @@ class Storage:
|
|||||||
return {"bucket_name": env.str("STORAGE_BUCKET_STATIC")}
|
return {"bucket_name": env.str("STORAGE_BUCKET_STATIC")}
|
||||||
case _:
|
case _:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user