category va elonlanri olish optimalashtrildi

This commit is contained in:
2026-04-02 12:10:42 +05:00
parent ad41dc8387
commit c5703bbb88

View File

@@ -3,22 +3,27 @@ Firebase → Django mahsulotlarni sinxronlashtirish skripti.
Ishlatish: Ishlatish:
python sync_firebase_products.py python sync_firebase_products.py
python sync_firebase_products.py --dry-run # haqiqatda saqlamaydi python sync_firebase_products.py --dry-run
python sync_firebase_products.py --update # mavjudlarni ham yangilaydi python sync_firebase_products.py --update
python sync_firebase_products.py --after "2024-01-15"
python sync_firebase_products.py --after "2024-01-15 10:30:00"
Logika: Logika:
- firestore_id bo'yicha tekshiradi - firestore_id bo'yicha tekshiradi
- Mavjud bo'lsa — o'tkazib ketadi (--update bilan yangilaydi) - Mavjud bo'lsa — o'tkazib ketadi (--update bilan yangilaydi)
- Yangi bo'lsa — create qiladi - Yangi bo'lsa — create qiladi
- --after bilan faqat o'sha vaqtdan keyin qo'shilganlarni oladi
""" """
import os import os
import sys import sys
import json
import argparse import argparse
import django import django
import firebase_admin import firebase_admin
from firebase_admin import credentials, firestore from firebase_admin import credentials, firestore
from decimal import Decimal, InvalidOperation from decimal import Decimal, InvalidOperation
from datetime import datetime, timezone
# ── Django sozlash ─────────────────────────────────────────────────────────── # ── Django sozlash ───────────────────────────────────────────────────────────
BASE_DIR = os.path.dirname(os.path.abspath(__file__)) BASE_DIR = os.path.dirname(os.path.abspath(__file__))
@@ -54,7 +59,6 @@ db = firestore.client()
# ── Yordamchi funksiyalar ──────────────────────────────────────────────────── # ── Yordamchi funksiyalar ────────────────────────────────────────────────────
def to_decimal(value, default=Decimal("0")): def to_decimal(value, default=Decimal("0")):
"""Har qanday qiymatni Decimal'ga o'tkazadi."""
try: try:
return Decimal(str(value)).quantize(Decimal("0.01")) return Decimal(str(value)).quantize(Decimal("0.01"))
except (InvalidOperation, TypeError): except (InvalidOperation, TypeError):
@@ -68,35 +72,47 @@ def to_int(value, default=-1):
return default return default
def parse_after_date(date_str: str) -> datetime:
"""'2024-01-15' yoki '2024-01-15 10:30:00' ni timezone-aware datetime'ga o'tkazadi."""
for fmt in ("%Y-%m-%d %H:%M:%S", "%Y-%m-%d"):
try:
dt = datetime.strptime(date_str, fmt)
return dt.replace(tzinfo=timezone.utc)
except ValueError:
continue
raise ValueError(
f"Noto'g'ri sana formati: '{date_str}'\n"
"To'g'ri formatlar: '2024-01-15' yoki '2024-01-15 10:30:00'"
)
def get_category(firestore_id: str): def get_category(firestore_id: str):
"""CategoryModel'ni firestore_id bo'yicha topadi."""
if not firestore_id: if not firestore_id:
return None return None
return CategoryModel.objects.filter(firestore_id=firestore_id).first() return CategoryModel.objects.filter(firestore_id=firestore_id).first()
def get_section(firestore_id: str): def get_section(firestore_id: str):
"""SectionModel'ni firestore_id bo'yicha topadi."""
if not firestore_id: if not firestore_id:
return None return None
return SectionModel.objects.filter(firestore_id=firestore_id).first() return SectionModel.objects.filter(firestore_id=firestore_id).first()
def build_product_data(doc_id: str, data: dict) -> dict: def parse_spec(spec):
"""Firebase hujjatidan Django model uchun dict yasaydi.""" """product_specification ni dict'ga o'tkazadi."""
if isinstance(spec, dict) and spec:
# product_specification: {"Og'irlik": "1 kg", "Hajm": "500 ml"} kabi return spec
spec = data.get("product_specification")
if isinstance(spec, str): if isinstance(spec, str):
# Ba'zida JSON string sifatida keladi
import json
try: try:
spec = json.loads(spec) parsed = json.loads(spec)
if isinstance(parsed, dict) and parsed:
return parsed
except Exception: except Exception:
spec = None pass
if not isinstance(spec, dict) or not spec: return None
spec = None
def build_product_data(doc_id: str, data: dict) -> dict:
return dict( return dict(
firestore_id=doc_id, firestore_id=doc_id,
vendor=data.get("vendorID") or data.get("vendor") or "", vendor=data.get("vendorID") or data.get("vendor") or "",
@@ -107,30 +123,52 @@ def build_product_data(doc_id: str, data: dict) -> dict:
quantity=to_int(data.get("quantity", -1)), quantity=to_int(data.get("quantity", -1)),
is_publish=bool(data.get("publish", True)), is_publish=bool(data.get("publish", True)),
photos_json=data.get("photos") or None, photos_json=data.get("photos") or None,
product_specification=spec, product_specification=parse_spec(data.get("product_specification")),
# ForeignKey'lar ayrida to'ldiriladi
_category_id=data.get("categoryID") or "", _category_id=data.get("categoryID") or "",
_section_id=data.get("section_id") or "", _section_id=data.get("section_id") or "",
) )
def sync_products(dry_run: bool = False, update: bool = False): def print_progress(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}"
)
print(line, end="", flush=True)
# ── Asosiy funksiya ───────────────────────────────────────────────────────────
def sync_products(dry_run: bool = False, update: bool = False, after: datetime | None = None):
print("Firebase vendor_products kolleksiyasi o'qilmoqda…") print("Firebase vendor_products kolleksiyasi o'qilmoqda…")
docs = list(db.collection("vendor_products").stream())
print(f"Jami Firebase'da: {len(docs)} ta mahsulot\n")
created = 0 query = db.collection("vendor_products")
updated = 0 if after:
skipped = 0 # Firestore'da createdAt field bo'yicha server tomonida filter
errors = 0 query = query.where("createdAt", ">=", after)
print(f"Filter: createdAt >= {after.strftime('%Y-%m-%d %H:%M:%S')} (UTC)")
for doc in docs: docs = list(query.stream())
total = len(docs)
print(f"Jami Firebase'da: {total} ta mahsulot\n")
if total == 0:
print("Sinxronlash uchun mahsulot topilmadi.")
return
created = updated = skipped = errors = 0
for i, doc in enumerate(docs, start=1):
doc_id = doc.id doc_id = doc.id
data = doc.to_dict() or {} data = doc.to_dict() or {}
if not data.get("name"): if not data.get("name"):
print(f" [O'TKAZILDI] {doc_id} — nomi yo'q")
skipped += 1 skipped += 1
print_progress(i, total, created, updated, skipped, errors)
continue continue
try: try:
@@ -142,9 +180,9 @@ def sync_products(dry_run: bool = False, update: bool = False):
if exists and not update: if exists and not update:
skipped += 1 skipped += 1
print_progress(i, total, created, updated, skipped, errors)
continue continue
# ForeignKey topish
category = get_category(category_fid) category = get_category(category_fid)
section = get_section(section_fid) section = get_section(section_fid)
@@ -155,7 +193,6 @@ def sync_products(dry_run: bool = False, update: bool = False):
exists.section = section exists.section = section
if not dry_run: if not dry_run:
exists.save() exists.save()
print(f" [YANGILANDI] {doc_id}{product_data['name']}")
updated += 1 updated += 1
else: else:
if not dry_run: if not dry_run:
@@ -164,18 +201,21 @@ def sync_products(dry_run: bool = False, update: bool = False):
category=category, category=category,
section=section, section=section,
) )
print(f" [YARATILDI] {doc_id}{product_data['name']}")
created += 1 created += 1
except Exception as e: except Exception as e:
print(f" [XATO] {doc_id}: {e}") print(f"\n [XATO] {doc_id}: {e}")
errors += 1 errors += 1
print("\n" + "=" * 50) print_progress(i, total, created, updated, skipped, errors)
print("\n\n" + "=" * 50)
print(f"Yaratildi : {created}") print(f"Yaratildi : {created}")
print(f"Yangilandi: {updated}") if update:
print(f"Yangilandi: {updated}")
print(f"O'tkazildi: {skipped}") print(f"O'tkazildi: {skipped}")
print(f"Xatolar : {errors}") if errors:
print(f"Xatolar : {errors}")
if dry_run: if dry_run:
print("\n[DRY-RUN] Hech narsa saqlanmadi.") print("\n[DRY-RUN] Hech narsa saqlanmadi.")
@@ -194,6 +234,21 @@ if __name__ == "__main__":
action="store_true", action="store_true",
help="Mavjud mahsulotlarni ham yangilaydi (default: o'tkazib ketadi)", help="Mavjud mahsulotlarni ham yangilaydi (default: o'tkazib ketadi)",
) )
parser.add_argument(
"--after",
type=str,
default=None,
metavar="SANA",
help="Faqat shu vaqtdan keyin qo'shilgan mahsulotlar. Misol: '2024-01-15' yoki '2024-01-15 10:30:00'",
)
args = parser.parse_args() args = parser.parse_args()
sync_products(dry_run=args.dry_run, update=args.update) after_dt: datetime | None = None
if args.after:
try:
after_dt = parse_after_date(args.after)
except ValueError as e:
print(f"[XATO] {e}")
sys.exit(1)
sync_products(dry_run=args.dry_run, update=args.update, after=after_dt)