categorylanri import qilish qoshildi
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -13,6 +13,7 @@ poetry.lock
|
|||||||
# C extensions
|
# C extensions
|
||||||
*.so
|
*.so
|
||||||
|
|
||||||
|
assets/
|
||||||
# Distribution / packaging
|
# Distribution / packaging
|
||||||
.Python
|
.Python
|
||||||
build/
|
build/
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ LOGGING = {
|
|||||||
"daily_rotating_file": {
|
"daily_rotating_file": {
|
||||||
"level": "INFO",
|
"level": "INFO",
|
||||||
"class": "logging.handlers.TimedRotatingFileHandler",
|
"class": "logging.handlers.TimedRotatingFileHandler",
|
||||||
"filename": LOG_DIR / "django.log",
|
"filename": str(LOG_DIR / "django.log"),
|
||||||
"when": "midnight",
|
"when": "midnight",
|
||||||
"backupCount": 30,
|
"backupCount": 30,
|
||||||
"formatter": "verbose",
|
"formatter": "verbose",
|
||||||
@@ -39,7 +39,7 @@ LOGGING = {
|
|||||||
"error_file": {
|
"error_file": {
|
||||||
"level": "ERROR",
|
"level": "ERROR",
|
||||||
"class": "logging.handlers.TimedRotatingFileHandler",
|
"class": "logging.handlers.TimedRotatingFileHandler",
|
||||||
"filename": LOG_DIR / "django_error.log",
|
"filename": str(LOG_DIR / "django_error.log"),
|
||||||
"when": "midnight",
|
"when": "midnight",
|
||||||
"backupCount": 30,
|
"backupCount": 30,
|
||||||
"formatter": "verbose",
|
"formatter": "verbose",
|
||||||
|
|||||||
0
core/apps/api/management/commands/__init__.py
Normal file
0
core/apps/api/management/commands/__init__.py
Normal file
145
core/apps/api/management/commands/import_assets.py
Normal file
145
core/apps/api/management/commands/import_assets.py
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.core.files import File
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import transaction
|
||||||
|
from core.apps.api.models import FilialModel, CategoryModel, SubcategoryModel, ProductsModel, SubProductModel
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Import restaurants and products from JSON assets'
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
base_dir = Path(settings.BASE_DIR)
|
||||||
|
json_dir = base_dir / 'assets' / 'json'
|
||||||
|
images_dir = base_dir / 'assets' / 'images'
|
||||||
|
|
||||||
|
product_map = {} # Maps (filial_name, item_id) to ProductsModel instance
|
||||||
|
|
||||||
|
# 1. Process Main Food Containers
|
||||||
|
food_container_files = sorted(json_dir.glob('FoodContainer*.json'))
|
||||||
|
|
||||||
|
for file_path in food_container_files:
|
||||||
|
file_name = file_path.name
|
||||||
|
# Extract Filial name: FoodContainerKaromat20.json -> Karomat
|
||||||
|
match = re.search(r'FoodContainer([A-Za-z]+)(\d+)?\.json', file_name)
|
||||||
|
if not match:
|
||||||
|
self.stdout.write(self.style.WARNING(f"Skipping {file_name}, regex mismatch"))
|
||||||
|
continue
|
||||||
|
|
||||||
|
filial_name = match.group(1)
|
||||||
|
if not filial_name:
|
||||||
|
filial_name = "Default"
|
||||||
|
|
||||||
|
self.stdout.write(self.style.SUCCESS(f"Processing filial: {filial_name} from {file_name}"))
|
||||||
|
|
||||||
|
# Filial can be shared across multiple JSON files
|
||||||
|
filial, _ = FilialModel.objects.get_or_create(name=filial_name)
|
||||||
|
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
current_category = None
|
||||||
|
current_subcategory = None
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
for item in data:
|
||||||
|
is_header = item.get('isHeader') == 1
|
||||||
|
name = item.get('nameRu') or item.get('nameEn') or "Unnamed"
|
||||||
|
price = item.get('price', 0)
|
||||||
|
image_name = item.get('image')
|
||||||
|
json_id = item.get('id')
|
||||||
|
|
||||||
|
if is_header:
|
||||||
|
# Create Category and Subcategory
|
||||||
|
current_category, created = CategoryModel.objects.get_or_create(
|
||||||
|
filial=filial,
|
||||||
|
name=name
|
||||||
|
)
|
||||||
|
if created and image_name:
|
||||||
|
self.attach_image(current_category, image_name, images_dir)
|
||||||
|
|
||||||
|
current_subcategory, created = SubcategoryModel.objects.get_or_create(
|
||||||
|
category=current_category,
|
||||||
|
name=name
|
||||||
|
)
|
||||||
|
self.stdout.write(f" Category/Subcategory: {name}")
|
||||||
|
else:
|
||||||
|
if not current_subcategory:
|
||||||
|
# Fallback if no header found yet
|
||||||
|
cat_name = "General"
|
||||||
|
current_category, _ = CategoryModel.objects.get_or_create(
|
||||||
|
filial=filial,
|
||||||
|
name=cat_name
|
||||||
|
)
|
||||||
|
current_subcategory, _ = SubcategoryModel.objects.get_or_create(
|
||||||
|
category=current_category,
|
||||||
|
name=cat_name
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create Product
|
||||||
|
# We use get_or_create with name and subcategory to avoid obvious duplicates
|
||||||
|
product, created = ProductsModel.objects.get_or_create(
|
||||||
|
name=name,
|
||||||
|
subcategory=current_subcategory,
|
||||||
|
defaults={'price': price if price > 0 else 0}
|
||||||
|
)
|
||||||
|
if created:
|
||||||
|
if image_name:
|
||||||
|
self.attach_image(product, image_name, images_dir)
|
||||||
|
self.stdout.write(f" Added Product: {name}")
|
||||||
|
|
||||||
|
# Store in map (scoped by filial and ID) for subproduct linking
|
||||||
|
product_map[(filial_name, json_id)] = product
|
||||||
|
|
||||||
|
# 2. Process Food Detail Containers (Subproducts)
|
||||||
|
food_detail_files = sorted(json_dir.glob('FoodDetailContainer*.json'))
|
||||||
|
for file_path in food_detail_files:
|
||||||
|
file_name = file_path.name
|
||||||
|
match = re.search(r'FoodDetailContainer(.*?)(?:\d+)?\.json', file_name)
|
||||||
|
if not match:
|
||||||
|
continue
|
||||||
|
|
||||||
|
filial_name = match.group(1)
|
||||||
|
self.stdout.write(self.style.SUCCESS(f"Processing subproducts for filial: {filial_name} from {file_name}"))
|
||||||
|
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
for item in data:
|
||||||
|
food_id = item.get('foodId')
|
||||||
|
name = item.get('nameEn') or item.get('nameRu') or "Unnamed Subproduct"
|
||||||
|
price = item.get('price', 0)
|
||||||
|
|
||||||
|
product_key = (filial_name, food_id)
|
||||||
|
if product_key in product_map:
|
||||||
|
parent_product = product_map[product_key]
|
||||||
|
sub_product, created = SubProductModel.objects.get_or_create(
|
||||||
|
product=parent_product,
|
||||||
|
name=name,
|
||||||
|
defaults={'price': price if price > 0 else 0}
|
||||||
|
)
|
||||||
|
if created:
|
||||||
|
self.stdout.write(f" Added SubProduct: {name} for {parent_product.name}")
|
||||||
|
else:
|
||||||
|
# Sometimes subproducts might refer to products from other files or just missing
|
||||||
|
pass
|
||||||
|
|
||||||
|
def attach_image(self, instance, image_name, images_dir):
|
||||||
|
# Try different extensions
|
||||||
|
if not image_name:
|
||||||
|
return False
|
||||||
|
|
||||||
|
for ext in ['.jpg', '.png', '.jpeg', '.webp']:
|
||||||
|
img_path = images_dir / (image_name + ext)
|
||||||
|
if img_path.exists():
|
||||||
|
try:
|
||||||
|
with open(img_path, 'rb') as img_file:
|
||||||
|
instance.image.save(img_path.name, File(img_file), save=True)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
self.stdout.write(self.style.ERROR(f" Error saving image {image_name}: {e}"))
|
||||||
|
return False
|
||||||
Reference in New Issue
Block a user