restore composer.json, add mysqli extension
This commit is contained in:
@@ -49,6 +49,7 @@ INSTALLED_APPS = [
|
|||||||
"django.contrib.sessions",
|
"django.contrib.sessions",
|
||||||
"django.contrib.messages",
|
"django.contrib.messages",
|
||||||
"django.contrib.staticfiles",
|
"django.contrib.staticfiles",
|
||||||
|
"django.contrib.postgres",
|
||||||
] + APPS
|
] + APPS
|
||||||
|
|
||||||
MODULES = [app for app in MODULES if isinstance(app, str)]
|
MODULES = [app for app in MODULES if isinstance(app, str)]
|
||||||
|
|||||||
63
core/apps/vendors/migrations/0006_productattributemodel_productvariantmodel.py
vendored
Normal file
63
core/apps/vendors/migrations/0006_productattributemodel_productvariantmodel.py
vendored
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# Generated by Django 6.0.4 on 2026-04-13 12:09
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("vendors", "0005_vendorproductmodel_product_specification"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="ProductAttributeModel",
|
||||||
|
fields=[
|
||||||
|
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("updated_at", models.DateTimeField(auto_now=True)),
|
||||||
|
(
|
||||||
|
"firestore_id",
|
||||||
|
models.CharField(blank=True, max_length=255, null=True, unique=True, verbose_name="firestore id"),
|
||||||
|
),
|
||||||
|
("name", models.CharField(max_length=255, verbose_name="name")),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "Product Attribute",
|
||||||
|
"verbose_name_plural": "Product Attributes",
|
||||||
|
"db_table": "product_attributes",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="ProductVariantModel",
|
||||||
|
fields=[
|
||||||
|
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("updated_at", models.DateTimeField(auto_now=True)),
|
||||||
|
(
|
||||||
|
"firestore_id",
|
||||||
|
models.CharField(blank=True, max_length=255, null=True, unique=True, verbose_name="firestore id"),
|
||||||
|
),
|
||||||
|
("price", models.DecimalField(decimal_places=2, default=0, max_digits=12, verbose_name="price")),
|
||||||
|
("sku", models.CharField(blank=True, max_length=255, null=True, verbose_name="SKU")),
|
||||||
|
("quantity", models.IntegerField(default=-1, verbose_name="quantity")),
|
||||||
|
("image_url", models.URLField(blank=True, max_length=1000, null=True, verbose_name="image url")),
|
||||||
|
("attribute_data", models.JSONField(blank=True, null=True, verbose_name="attribute data")),
|
||||||
|
(
|
||||||
|
"product",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="variants",
|
||||||
|
to="vendors.vendorproductmodel",
|
||||||
|
verbose_name="product",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "Product Variant",
|
||||||
|
"verbose_name_plural": "Product Variants",
|
||||||
|
"db_table": "product_variants",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
2
core/apps/vendors/models/__init__.py
vendored
2
core/apps/vendors/models/__init__.py
vendored
@@ -2,3 +2,5 @@ from .category import * # noqa
|
|||||||
from .vendor import * # noqa
|
from .vendor import * # noqa
|
||||||
from .vendor_product import * # noqa
|
from .vendor_product import * # noqa
|
||||||
from .section import * # noqa
|
from .section import * # noqa
|
||||||
|
from .product_attribute import * # noqa
|
||||||
|
from .product_variant import * # noqa
|
||||||
|
|||||||
16
core/apps/vendors/models/product_attribute.py
vendored
Normal file
16
core/apps/vendors/models/product_attribute.py
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django_core.models import AbstractBaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class ProductAttributeModel(AbstractBaseModel):
|
||||||
|
firestore_id = models.CharField(verbose_name=_("firestore id"), max_length=255, unique=True, null=True, blank=True)
|
||||||
|
name = models.CharField(verbose_name=_("name"), max_length=255)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "product_attributes"
|
||||||
|
verbose_name = _("Product Attribute")
|
||||||
|
verbose_name_plural = _("Product Attributes")
|
||||||
26
core/apps/vendors/models/product_variant.py
vendored
Normal file
26
core/apps/vendors/models/product_variant.py
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django_core.models import AbstractBaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class ProductVariantModel(AbstractBaseModel):
|
||||||
|
product = models.ForeignKey(
|
||||||
|
"VendorproductModel",
|
||||||
|
verbose_name=_("product"),
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="variants"
|
||||||
|
)
|
||||||
|
firestore_id = models.CharField(verbose_name=_("firestore id"), max_length=255, unique=True, null=True, blank=True)
|
||||||
|
price = models.DecimalField(verbose_name=_("price"), max_digits=12, decimal_places=2, default=0)
|
||||||
|
sku = models.CharField(verbose_name=_("SKU"), max_length=255, null=True, blank=True)
|
||||||
|
quantity = models.IntegerField(verbose_name=_("quantity"), default=-1)
|
||||||
|
image_url = models.URLField(verbose_name=_("image url"), max_length=1000, null=True, blank=True)
|
||||||
|
attribute_data = models.JSONField(verbose_name=_("attribute data"), null=True, blank=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Variant {self.sku} for {self.product.name}"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "product_variants"
|
||||||
|
verbose_name = _("Product Variant")
|
||||||
|
verbose_name_plural = _("Product Variants")
|
||||||
42
scratch/inspect_attributes.py
Normal file
42
scratch/inspect_attributes.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import firebase_admin
|
||||||
|
from firebase_admin import credentials
|
||||||
|
from firebase_admin import firestore
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
cert_path = "fondexuzb-firebase-adminsdk-fbsvc-7b0e2d6200.json"
|
||||||
|
|
||||||
|
if not os.path.exists(cert_path):
|
||||||
|
print(f"Xatolik: {cert_path} fayli topilmadi!")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
if not firebase_admin._apps:
|
||||||
|
cred = credentials.Certificate(cert_path)
|
||||||
|
firebase_admin.initialize_app(cred)
|
||||||
|
|
||||||
|
db = firestore.client()
|
||||||
|
|
||||||
|
def inspect_products():
|
||||||
|
print(f"\n--- vendor_products kolleksiyasini tekshirish ---")
|
||||||
|
|
||||||
|
docs = db.collection('vendor_products').limit(5).stream()
|
||||||
|
|
||||||
|
class DateTimeEncoder(json.JSONEncoder):
|
||||||
|
def default(self, obj):
|
||||||
|
if hasattr(obj, 'isoformat'):
|
||||||
|
return obj.isoformat()
|
||||||
|
return str(obj)
|
||||||
|
|
||||||
|
for doc in docs:
|
||||||
|
data = doc.to_dict()
|
||||||
|
print(f"Hujjat ID: {doc.id}")
|
||||||
|
# Print only keys that might be relevant to attributes
|
||||||
|
for key, value in data.items():
|
||||||
|
if 'attr' in key.lower() or 'spec' in key.lower():
|
||||||
|
print(f" {key}: {value}")
|
||||||
|
# Print everything for the first one to be sure
|
||||||
|
print(json.dumps(data, indent=4, ensure_ascii=False, cls=DateTimeEncoder))
|
||||||
|
print("-" * 20)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
inspect_products()
|
||||||
@@ -40,6 +40,8 @@ from core.apps.vendors.models import (
|
|||||||
VendorproductModel,
|
VendorproductModel,
|
||||||
CategoryModel,
|
CategoryModel,
|
||||||
SectionModel,
|
SectionModel,
|
||||||
|
ProductAttributeModel,
|
||||||
|
ProductVariantModel,
|
||||||
)
|
)
|
||||||
|
|
||||||
# ── Firebase ulanish ─────────────────────────────────────────────────────────
|
# ── Firebase ulanish ─────────────────────────────────────────────────────────
|
||||||
@@ -141,6 +143,23 @@ def print_progress(current, total, created, updated, skipped, errors):
|
|||||||
print(line, end="", flush=True)
|
print(line, end="", flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
def sync_attributes():
|
||||||
|
print("Firebase vendor_attributes kolleksiyasi o'qilmoqda…")
|
||||||
|
docs = db.collection("vendor_attributes").stream()
|
||||||
|
count = 0
|
||||||
|
for doc in docs:
|
||||||
|
data = doc.to_dict()
|
||||||
|
attr_id = data.get("id") or doc.id
|
||||||
|
name = data.get("title") or "Unnamed"
|
||||||
|
|
||||||
|
attr, created = ProductAttributeModel.objects.update_or_create(
|
||||||
|
firestore_id=attr_id,
|
||||||
|
defaults={"name": name}
|
||||||
|
)
|
||||||
|
count += 1
|
||||||
|
print(f"Jami {count} ta atribut sinxronlandi.\n")
|
||||||
|
|
||||||
|
|
||||||
# ── Asosiy funksiya ───────────────────────────────────────────────────────────
|
# ── Asosiy funksiya ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def sync_products(dry_run: bool = False, update: bool = False, after: datetime | None = None):
|
def sync_products(dry_run: bool = False, update: bool = False, after: datetime | None = None):
|
||||||
@@ -193,16 +212,43 @@ def sync_products(dry_run: bool = False, update: bool = False, after: datetime |
|
|||||||
exists.section = section
|
exists.section = section
|
||||||
if not dry_run:
|
if not dry_run:
|
||||||
exists.save()
|
exists.save()
|
||||||
|
product_obj = exists
|
||||||
updated += 1
|
updated += 1
|
||||||
else:
|
else:
|
||||||
|
product_obj = None
|
||||||
if not dry_run:
|
if not dry_run:
|
||||||
VendorproductModel.objects.create(
|
product_obj = VendorproductModel.objects.create(
|
||||||
**product_data,
|
**product_data,
|
||||||
category=category,
|
category=category,
|
||||||
section=section,
|
section=section,
|
||||||
)
|
)
|
||||||
created += 1
|
created += 1
|
||||||
|
|
||||||
|
# ── Variantlarni qayta ishlash ────────────────────────────────────
|
||||||
|
|
||||||
|
item_attr = data.get("item_attribute")
|
||||||
|
if item_attr and isinstance(item_attr, dict) and not dry_run:
|
||||||
|
# Agar product_obj yaratilgan bo'lsa (yoki mavjud bo'lsa)
|
||||||
|
target_product = product_obj or exists
|
||||||
|
if target_product:
|
||||||
|
variants_data = item_attr.get("variants") or []
|
||||||
|
for v_data in variants_data:
|
||||||
|
v_id = v_data.get("variant_id")
|
||||||
|
if not v_id:
|
||||||
|
continue
|
||||||
|
|
||||||
|
ProductVariantModel.objects.update_or_create(
|
||||||
|
firestore_id=v_id,
|
||||||
|
defaults={
|
||||||
|
"product": target_product,
|
||||||
|
"price": to_decimal(v_data.get("variant_price", 0)),
|
||||||
|
"sku": v_data.get("variant_sku"),
|
||||||
|
"quantity": to_int(v_data.get("variant_quantity", -1)),
|
||||||
|
"image_url": v_data.get("variant_image"),
|
||||||
|
"attribute_data": item_attr.get("attributes")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"\n [XATO] {doc_id}: {e}")
|
print(f"\n [XATO] {doc_id}: {e}")
|
||||||
errors += 1
|
errors += 1
|
||||||
@@ -251,4 +297,5 @@ if __name__ == "__main__":
|
|||||||
print(f"[XATO] {e}")
|
print(f"[XATO] {e}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
sync_attributes()
|
||||||
sync_products(dry_run=args.dry_run, update=args.update, after=after_dt)
|
sync_products(dry_run=args.dry_run, update=args.update, after=after_dt)
|
||||||
|
|||||||
Reference in New Issue
Block a user