category va elonlanri olish optimalashtrildi
This commit is contained in:
0
core/apps/vendors/management/__init__.py
vendored
Normal file
0
core/apps/vendors/management/__init__.py
vendored
Normal file
0
core/apps/vendors/management/commands/__init__.py
vendored
Normal file
0
core/apps/vendors/management/commands/__init__.py
vendored
Normal file
183
core/apps/vendors/management/commands/sync_firebase_products.py
vendored
Normal file
183
core/apps/vendors/management/commands/sync_firebase_products.py
vendored
Normal file
@@ -0,0 +1,183 @@
|
||||
"""
|
||||
Django management command: Firebase → DB mahsulot sync.
|
||||
|
||||
Ishlatish:
|
||||
python manage.py sync_firebase_products
|
||||
python manage.py sync_firebase_products --dry-run
|
||||
python manage.py sync_firebase_products --update
|
||||
|
||||
Docker orqali:
|
||||
docker compose exec web python manage.py sync_firebase_products
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
from decimal import Decimal, InvalidOperation
|
||||
|
||||
import firebase_admin
|
||||
from firebase_admin import credentials, firestore
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from core.apps.vendors.models import VendorproductModel, CategoryModel, SectionModel
|
||||
|
||||
CERT_PATH = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
"../../../../..", # project root
|
||||
"fondexuzb-firebase-adminsdk-fbsvc-7b0e2d6200.json",
|
||||
)
|
||||
|
||||
|
||||
def _init_firebase():
|
||||
if firebase_admin._apps:
|
||||
return firestore.client()
|
||||
cert = os.path.normpath(CERT_PATH)
|
||||
if not os.path.exists(cert):
|
||||
raise FileNotFoundError(f"Firebase credentials topilmadi: {cert}")
|
||||
cred = credentials.Certificate(cert)
|
||||
firebase_admin.initialize_app(cred)
|
||||
return firestore.client()
|
||||
|
||||
|
||||
def _to_decimal(value, default=Decimal("0")):
|
||||
try:
|
||||
return Decimal(str(value)).quantize(Decimal("0.01"))
|
||||
except (InvalidOperation, TypeError):
|
||||
return default
|
||||
|
||||
|
||||
def _to_int(value, default=-1):
|
||||
try:
|
||||
return int(value)
|
||||
except (TypeError, ValueError):
|
||||
return default
|
||||
|
||||
|
||||
def _parse_spec(spec):
|
||||
"""product_specification ni dict'ga parse qiladi."""
|
||||
if isinstance(spec, dict) and spec:
|
||||
return spec
|
||||
if isinstance(spec, str):
|
||||
try:
|
||||
parsed = json.loads(spec)
|
||||
if isinstance(parsed, dict) and parsed:
|
||||
return parsed
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Firebase vendor_products → Django DB sync"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Haqiqatda DB'ga yozmaydi",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--update",
|
||||
action="store_true",
|
||||
help="Mavjud mahsulotlarni ham yangilaydi",
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
dry_run = options["dry_run"]
|
||||
update = options["update"]
|
||||
|
||||
try:
|
||||
db = _init_firebase()
|
||||
except FileNotFoundError as e:
|
||||
self.stderr.write(self.style.ERROR(str(e)))
|
||||
return
|
||||
|
||||
self.stdout.write("Firebase vendor_products o'qilmoqda…")
|
||||
docs = list(db.collection("vendor_products").stream())
|
||||
self.stdout.write(f"Jami Firebase'da: {len(docs)} ta mahsulot\n")
|
||||
|
||||
created = updated = skipped = errors = 0
|
||||
total = len(docs)
|
||||
|
||||
for i, doc in enumerate(docs, start=1):
|
||||
doc_id = doc.id
|
||||
data = doc.to_dict() or {}
|
||||
|
||||
if not data.get("name"):
|
||||
skipped += 1
|
||||
self._print_progress(i, total, created, updated, skipped, errors)
|
||||
continue
|
||||
|
||||
try:
|
||||
name = (data.get("name") or "").strip() or "—"
|
||||
spec = _parse_spec(data.get("product_specification"))
|
||||
|
||||
product_fields = dict(
|
||||
vendor=data.get("vendorID") or data.get("vendor") or "",
|
||||
name=name,
|
||||
description=data.get("description") or "",
|
||||
price=_to_decimal(data.get("price", 0)),
|
||||
discount_price=_to_decimal(data.get("disPrice", 0)),
|
||||
quantity=_to_int(data.get("quantity", -1)),
|
||||
is_publish=bool(data.get("publish", True)),
|
||||
photos_json=data.get("photos") or None,
|
||||
product_specification=spec,
|
||||
)
|
||||
|
||||
category = CategoryModel.objects.filter(
|
||||
firestore_id=data.get("categoryID", "")
|
||||
).first()
|
||||
section = SectionModel.objects.filter(
|
||||
firestore_id=data.get("section_id", "")
|
||||
).first()
|
||||
|
||||
existing = VendorproductModel.objects.filter(firestore_id=doc_id).first()
|
||||
|
||||
if existing and not update:
|
||||
skipped += 1
|
||||
self._print_progress(i, total, created, updated, skipped, errors)
|
||||
continue
|
||||
|
||||
if existing:
|
||||
for field, val in product_fields.items():
|
||||
setattr(existing, field, val)
|
||||
existing.category = category
|
||||
existing.section = section
|
||||
if not dry_run:
|
||||
existing.save()
|
||||
updated += 1
|
||||
else:
|
||||
if not dry_run:
|
||||
VendorproductModel.objects.create(
|
||||
firestore_id=doc_id,
|
||||
category=category,
|
||||
section=section,
|
||||
**product_fields,
|
||||
)
|
||||
created += 1
|
||||
|
||||
except Exception as e:
|
||||
self.stderr.write(f"\n [XATO] {doc_id}: {e}")
|
||||
errors += 1
|
||||
|
||||
self._print_progress(i, total, created, updated, skipped, errors)
|
||||
|
||||
self.stdout.write("\n\n" + "=" * 50)
|
||||
self.stdout.write(self.style.SUCCESS(f"Yaratildi : {created}"))
|
||||
if update:
|
||||
self.stdout.write(self.style.SUCCESS(f"Yangilandi: {updated}"))
|
||||
self.stdout.write(f"O'tkazildi: {skipped}")
|
||||
if errors:
|
||||
self.stdout.write(self.style.ERROR(f"Xatolar : {errors}"))
|
||||
if dry_run:
|
||||
self.stdout.write(self.style.WARNING("\n[DRY-RUN] Hech narsa saqlanmadi."))
|
||||
|
||||
def _print_progress(self, current, total, created, updated, skipped, errors):
|
||||
percent = int(current / total * 100) if total else 0
|
||||
bar_filled = percent // 5
|
||||
bar = "█" * bar_filled + "░" * (20 - bar_filled)
|
||||
line = (
|
||||
f"\r [{bar}] {percent:3d}% "
|
||||
f"{current}/{total} | "
|
||||
f"✓ {created} ↑ {updated} — {skipped} ✗ {errors}"
|
||||
)
|
||||
self.stdout.write(line, ending="")
|
||||
Reference in New Issue
Block a user