Spaces:
Sleeping
Sleeping
Upload app/api/seed.py
Browse files- app/api/seed.py +145 -259
app/api/seed.py
CHANGED
|
@@ -1,5 +1,8 @@
|
|
| 1 |
from fastapi import APIRouter, Depends
|
| 2 |
from sqlalchemy.orm import Session
|
|
|
|
|
|
|
|
|
|
| 3 |
|
| 4 |
from app.db.base import get_db
|
| 5 |
from app.models.product import Product, ProductImage, Category
|
|
@@ -10,222 +13,36 @@ from app.schemas.auth import AuthResponse
|
|
| 10 |
|
| 11 |
router = APIRouter(prefix="/seed", tags=["Seed Data"])
|
| 12 |
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
]
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
"https://images.unsplash.com/photo-1484704849700-f032a568e944?w=600",
|
| 39 |
-
],
|
| 40 |
-
},
|
| 41 |
-
{
|
| 42 |
-
"name_ar": "ساعة ذكية ألترا",
|
| 43 |
-
"name_en": "Ultra Smart Watch",
|
| 44 |
-
"description_ar": "ساعة ذكية متقدمة مع شاشة AMOLED ومقاومة للماء حتى 50 متر. تتبع اللياقة البدنية ومراقبة معدل ضربات القلب والنوم.",
|
| 45 |
-
"description_en": "Advanced smartwatch with AMOLED display and 50m water resistance. Comprehensive fitness tracking, heart rate monitoring, sleep analysis, and GPS navigation.",
|
| 46 |
-
"price": 449.99,
|
| 47 |
-
"compare_price": 599.99,
|
| 48 |
-
"stock": 30,
|
| 49 |
-
"category_idx": 3,
|
| 50 |
-
"rating": 4.6,
|
| 51 |
-
"rating_count": 156,
|
| 52 |
-
"is_featured": True,
|
| 53 |
-
"images": [
|
| 54 |
-
"https://images.unsplash.com/photo-1523275335684-37898b6baf30?w=600",
|
| 55 |
-
"https://images.unsplash.com/photo-1546868871-af0de0ae72be?w=600",
|
| 56 |
-
],
|
| 57 |
-
},
|
| 58 |
-
{
|
| 59 |
-
"name_ar": "حقيبة ظهر جلدية",
|
| 60 |
-
"name_en": "Leather Backpack",
|
| 61 |
-
"description_ar": "حقيبة ظهر من الجلد الطبيعي الفاخر مع جيوب متعددة وبطانة مقاومة للماء. مثالية للعمل والسفر.",
|
| 62 |
-
"description_en": "Premium genuine leather backpack with multiple compartments and water-resistant lining. Perfect for work and travel with laptop sleeve up to 15 inches.",
|
| 63 |
-
"price": 189.99,
|
| 64 |
-
"compare_price": 249.99,
|
| 65 |
-
"stock": 45,
|
| 66 |
-
"category_idx": 4,
|
| 67 |
-
"rating": 4.5,
|
| 68 |
-
"rating_count": 89,
|
| 69 |
-
"is_featured": True,
|
| 70 |
-
"images": [
|
| 71 |
-
"https://images.unsplash.com/photo-1553062407-98eeb64c6a62?w=600",
|
| 72 |
-
"https://images.unsplash.com/photo-1622560480605-d83c853bc5c3?w=600",
|
| 73 |
-
],
|
| 74 |
-
},
|
| 75 |
-
{
|
| 76 |
-
"name_ar": "حذاء رياضي إير ماكس",
|
| 77 |
-
"name_en": "Air Max Running Shoes",
|
| 78 |
-
"description_ar": "حذاء رياضي خفيف الوزن مع تقنية التهوية المتقدمة ونعل مرن يوفر أقصى درجات الراحة أثناء الجري.",
|
| 79 |
-
"description_en": "Lightweight running shoes with advanced ventilation technology and flexible sole for maximum comfort during runs. Responsive cushioning and premium mesh upper.",
|
| 80 |
-
"price": 159.99,
|
| 81 |
-
"compare_price": 199.99,
|
| 82 |
-
"stock": 80,
|
| 83 |
-
"category_idx": 2,
|
| 84 |
-
"rating": 4.7,
|
| 85 |
-
"rating_count": 312,
|
| 86 |
-
"is_featured": True,
|
| 87 |
-
"images": [
|
| 88 |
-
"https://images.unsplash.com/photo-1542291026-7eec264c27ff?w=600",
|
| 89 |
-
"https://images.unsplash.com/photo-1608231387042-66d1773070a5?w=600",
|
| 90 |
-
],
|
| 91 |
-
},
|
| 92 |
-
{
|
| 93 |
-
"name_ar": "لابتوب ألتراسليم برو",
|
| 94 |
-
"name_en": "UltraSlim Pro Laptop",
|
| 95 |
-
"description_ar": "لابتوب احترافي بشاشة 14 بوصة 4K ومعالج من الجيل الأخير. خفيف الوزن 1.2 كغ مع بطارية تدوم 18 ساعة.",
|
| 96 |
-
"description_en": "Professional laptop with 14-inch 4K display and latest-gen processor. Ultra-lightweight at 1.2kg with 18-hour battery life. 16GB RAM, 512GB SSD.",
|
| 97 |
-
"price": 1299.99,
|
| 98 |
-
"compare_price": 1599.99,
|
| 99 |
-
"stock": 20,
|
| 100 |
-
"category_idx": 0,
|
| 101 |
-
"rating": 4.9,
|
| 102 |
-
"rating_count": 178,
|
| 103 |
-
"is_featured": True,
|
| 104 |
-
"images": [
|
| 105 |
-
"https://images.unsplash.com/photo-1496181133206-80ce9b88a853?w=600",
|
| 106 |
-
"https://images.unsplash.com/photo-1525547719571-a2d4ac8945e2?w=600",
|
| 107 |
-
],
|
| 108 |
-
},
|
| 109 |
-
{
|
| 110 |
-
"name_ar": "نظارات شمسية كلاسيك",
|
| 111 |
-
"name_en": "Classic Sunglasses",
|
| 112 |
-
"description_ar": "نظارات شمسية بتصميم كلاسيكي أنيق مع عدسات بولارايزد توفر حماية كاملة من أشعة UV. إطار معدني خفيف.",
|
| 113 |
-
"description_en": "Classic design sunglasses with polarized lenses providing full UV protection. Lightweight metal frame with adjustable nose pads for perfect fit.",
|
| 114 |
-
"price": 79.99,
|
| 115 |
-
"compare_price": 129.99,
|
| 116 |
-
"stock": 100,
|
| 117 |
-
"category_idx": 5,
|
| 118 |
-
"rating": 4.4,
|
| 119 |
-
"rating_count": 67,
|
| 120 |
-
"is_featured": False,
|
| 121 |
-
"images": [
|
| 122 |
-
"https://images.unsplash.com/photo-1572635196237-14b3f281503f?w=600",
|
| 123 |
-
],
|
| 124 |
-
},
|
| 125 |
-
{
|
| 126 |
-
"name_ar": "قميص قطن بريميوم",
|
| 127 |
-
"name_en": "Premium Cotton Shirt",
|
| 128 |
-
"description_ar": "قميص من القطن المصري الفاخر بتصميم عصري مريح. مناسب للمناسبات الرسمية وغير الرسمية.",
|
| 129 |
-
"description_en": "Premium Egyptian cotton shirt with modern comfortable design. Suitable for both formal and casual occasions. Breathable fabric with wrinkle-resistant finish.",
|
| 130 |
-
"price": 89.99,
|
| 131 |
-
"compare_price": None,
|
| 132 |
-
"stock": 120,
|
| 133 |
-
"category_idx": 1,
|
| 134 |
-
"rating": 4.3,
|
| 135 |
-
"rating_count": 45,
|
| 136 |
-
"is_featured": False,
|
| 137 |
-
"images": [
|
| 138 |
-
"https://images.unsplash.com/photo-1602810318383-e386cc2a3ccf?w=600",
|
| 139 |
-
],
|
| 140 |
-
},
|
| 141 |
-
{
|
| 142 |
-
"name_ar": "سماعات أذن لاسلكية",
|
| 143 |
-
"name_en": "Wireless Earbuds",
|
| 144 |
-
"description_ar": "سماعات أذن لاسلكية مع علبة شحن توفر 24 ساعة من الاستخدام. مقاومة للماء والعرق مع صوت عالي الدقة.",
|
| 145 |
-
"description_en": "True wireless earbuds with charging case providing 24 hours of use. Water and sweat resistant (IPX5) with high-definition sound and active noise cancellation.",
|
| 146 |
-
"price": 129.99,
|
| 147 |
-
"compare_price": 179.99,
|
| 148 |
-
"stock": 65,
|
| 149 |
-
"category_idx": 0,
|
| 150 |
-
"rating": 4.5,
|
| 151 |
-
"rating_count": 198,
|
| 152 |
-
"is_featured": False,
|
| 153 |
-
"images": [
|
| 154 |
-
"https://images.unsplash.com/photo-1590658268037-6bf12f032f55?w=600",
|
| 155 |
-
],
|
| 156 |
-
},
|
| 157 |
-
{
|
| 158 |
-
"name_ar": "محفظة جلدية فاخرة",
|
| 159 |
-
"name_en": "Luxury Leather Wallet",
|
| 160 |
-
"description_ar": "محفظة من الجلد الإيطالي الفاخر مع حماية RFID. تصميم أنيق مع فتحات متعددة للبطاقات وجيب للعملات.",
|
| 161 |
-
"description_en": "Premium Italian leather wallet with RFID protection. Elegant design with multiple card slots, bill compartment, and coin pocket. Gift box included.",
|
| 162 |
-
"price": 69.99,
|
| 163 |
-
"compare_price": 99.99,
|
| 164 |
-
"stock": 90,
|
| 165 |
-
"category_idx": 5,
|
| 166 |
-
"rating": 4.6,
|
| 167 |
-
"rating_count": 112,
|
| 168 |
-
"is_featured": False,
|
| 169 |
-
"images": [
|
| 170 |
-
"https://images.unsplash.com/photo-1627123424574-724758594e93?w=600",
|
| 171 |
-
],
|
| 172 |
-
},
|
| 173 |
-
{
|
| 174 |
-
"name_ar": "جاكيت شتوي مقاوم للماء",
|
| 175 |
-
"name_en": "Waterproof Winter Jacket",
|
| 176 |
-
"description_ar": "جاكيت شتوي دافئ ومقاوم للماء والرياح. حشوة داون طبيعية مع قلنسوة قابلة للإزالة وجيوب متعددة.",
|
| 177 |
-
"description_en": "Warm waterproof and windproof winter jacket. Natural down insulation with removable hood and multiple pockets. Breathable membrane technology.",
|
| 178 |
-
"price": 249.99,
|
| 179 |
-
"compare_price": 349.99,
|
| 180 |
-
"stock": 35,
|
| 181 |
-
"category_idx": 1,
|
| 182 |
-
"rating": 4.7,
|
| 183 |
-
"rating_count": 76,
|
| 184 |
-
"is_featured": False,
|
| 185 |
-
"images": [
|
| 186 |
-
"https://images.unsplash.com/photo-1544923246-77307dd270b1?w=600",
|
| 187 |
-
],
|
| 188 |
-
},
|
| 189 |
-
{
|
| 190 |
-
"name_ar": "كاميرا ميرورليس احترافية",
|
| 191 |
-
"name_en": "Professional Mirrorless Camera",
|
| 192 |
-
"description_ar": "كاميرا ميرورليس احترافية بدقة 45 ميجابكسل وتصوير فيديو 8K. نظام تركيز تلقائي متقدم مع تثبيت صورة 5 محاور.",
|
| 193 |
-
"description_en": "Professional mirrorless camera with 45MP sensor and 8K video recording. Advanced autofocus system with 5-axis image stabilization. Weather-sealed body.",
|
| 194 |
-
"price": 2499.99,
|
| 195 |
-
"compare_price": 2999.99,
|
| 196 |
-
"stock": 10,
|
| 197 |
-
"category_idx": 0,
|
| 198 |
-
"rating": 4.9,
|
| 199 |
-
"rating_count": 54,
|
| 200 |
-
"is_featured": True,
|
| 201 |
-
"images": [
|
| 202 |
-
"https://images.unsplash.com/photo-1516035069371-29a1b244cc32?w=600",
|
| 203 |
-
"https://images.unsplash.com/photo-1502920917128-1aa500764cbd?w=600",
|
| 204 |
-
],
|
| 205 |
-
},
|
| 206 |
-
{
|
| 207 |
-
"name_ar": "حذاء رسمي جلدي",
|
| 208 |
-
"name_en": "Formal Leather Shoes",
|
| 209 |
-
"description_ar": "حذاء رسمي من الجلد الطبيعي المصقول يدوياً. نعل مطاطي مقاوم للانزلاق مع بطانة جلدية مريحة.",
|
| 210 |
-
"description_en": "Hand-polished genuine leather formal shoes. Non-slip rubber sole with comfortable leather lining. Classic Oxford design with Goodyear welt construction.",
|
| 211 |
-
"price": 199.99,
|
| 212 |
-
"compare_price": None,
|
| 213 |
-
"stock": 40,
|
| 214 |
-
"category_idx": 2,
|
| 215 |
-
"rating": 4.4,
|
| 216 |
-
"rating_count": 38,
|
| 217 |
-
"is_featured": False,
|
| 218 |
-
"images": [
|
| 219 |
-
"https://images.unsplash.com/photo-1614252369475-531eba835eb1?w=600",
|
| 220 |
-
],
|
| 221 |
-
},
|
| 222 |
-
]
|
| 223 |
-
|
| 224 |
|
| 225 |
@router.post("", response_model=AuthResponse)
|
| 226 |
async def seed_database(db: Session = Depends(get_db)):
|
| 227 |
-
"""Populate database with
|
| 228 |
-
# Always ensure admin user exists
|
| 229 |
existing_admin = db.query(User).filter(User.email == "admin@vortex.com").first()
|
| 230 |
if not existing_admin:
|
| 231 |
admin_user = User(
|
|
@@ -247,54 +64,123 @@ async def seed_database(db: Session = Depends(get_db)):
|
|
| 247 |
statusCode=200,
|
| 248 |
)
|
| 249 |
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
# Create products
|
| 259 |
-
for prod_data in DEMO_PRODUCTS:
|
| 260 |
-
images_data = prod_data.pop("images")
|
| 261 |
-
category_idx = prod_data.pop("category_idx")
|
| 262 |
-
|
| 263 |
-
product = Product(
|
| 264 |
-
**prod_data,
|
| 265 |
-
category_id=categories[category_idx].id,
|
| 266 |
)
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 275 |
)
|
| 276 |
-
db.add(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 277 |
|
| 278 |
-
|
| 279 |
-
DEMO_COUPONS = [
|
| 280 |
-
{"code": "WELCOME10", "discount_percent": 10.0, "is_active": True},
|
| 281 |
-
{"code": "VORTEX20", "discount_percent": 20.0, "is_active": True},
|
| 282 |
-
{"code": "SUMMER50", "discount_percent": 50.0, "is_active": False},
|
| 283 |
-
]
|
| 284 |
-
|
| 285 |
-
for coupon_data in DEMO_COUPONS:
|
| 286 |
-
coupon = Coupon(**coupon_data)
|
| 287 |
-
db.add(coupon)
|
| 288 |
|
| 289 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 290 |
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
"
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
statusCode=201,
|
| 300 |
-
)
|
|
|
|
| 1 |
from fastapi import APIRouter, Depends
|
| 2 |
from sqlalchemy.orm import Session
|
| 3 |
+
import json
|
| 4 |
+
import random
|
| 5 |
+
import os
|
| 6 |
|
| 7 |
from app.db.base import get_db
|
| 8 |
from app.models.product import Product, ProductImage, Category
|
|
|
|
| 13 |
|
| 14 |
router = APIRouter(prefix="/seed", tags=["Seed Data"])
|
| 15 |
|
| 16 |
+
# Mapping distinct categories from Excel to a hierarchical structure
|
| 17 |
+
CATEGORY_MAP = {
|
| 18 |
+
# Parents (Keys) : Subcategories (Values)
|
| 19 |
+
"الأزياء": ["الملابس الرجالية", "الملابس النسائية"],
|
| 20 |
+
"الأحذية": ["الأحذية"],
|
| 21 |
+
"الأطفال والألعاب": ["الأطفال"],
|
| 22 |
+
"المنزل والمطبخ": ["المنزل والمطبخ"],
|
| 23 |
+
"العطور والتجميل": ["العطور", "التجميل والعناية"],
|
| 24 |
+
"الأجهزة الإلكترونية": ["الهواتف والإلكترونيات"],
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
# Image placeholders by Parent Category
|
| 28 |
+
IMAGE_KEYWORDS = {
|
| 29 |
+
"الأزياء": "clothing,fashion,apparel",
|
| 30 |
+
"الأحذية": "shoes,sneakers",
|
| 31 |
+
"الأطفال والألعاب": "kids,toys,baby",
|
| 32 |
+
"المنزل والمطبخ": "kitchen,home,appliance",
|
| 33 |
+
"العطور والتجميل": "perfume,beauty,skincare",
|
| 34 |
+
"الأجهزة الإلكترونية": "electronics,smartphone,gadget",
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
def get_image_url(parent_category: str, image_index: int):
|
| 38 |
+
# Generating consistent but different images based on a seed
|
| 39 |
+
seed = random.randint(1000, 9999) + image_index
|
| 40 |
+
return f"https://picsum.photos/seed/{seed}/800/800"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
|
| 42 |
@router.post("", response_model=AuthResponse)
|
| 43 |
async def seed_database(db: Session = Depends(get_db)):
|
| 44 |
+
"""Populate database with proper product data from json dump and an admin user."""
|
| 45 |
+
# Always ensure admin user exists
|
| 46 |
existing_admin = db.query(User).filter(User.email == "admin@vortex.com").first()
|
| 47 |
if not existing_admin:
|
| 48 |
admin_user = User(
|
|
|
|
| 64 |
statusCode=200,
|
| 65 |
)
|
| 66 |
|
| 67 |
+
json_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "products_dump.json")
|
| 68 |
+
if not os.path.exists(json_path):
|
| 69 |
+
return AuthResponse(
|
| 70 |
+
isSuccess=False,
|
| 71 |
+
value={"message": f"Source file {json_path} not found."},
|
| 72 |
+
statusCode=404,
|
| 73 |
+
error="File not found"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
)
|
| 75 |
+
|
| 76 |
+
try:
|
| 77 |
+
with open(json_path, "r", encoding="utf-8") as f:
|
| 78 |
+
products_data = json.load(f)
|
| 79 |
+
|
| 80 |
+
# 1. Create Parent Categories
|
| 81 |
+
parent_cats = {}
|
| 82 |
+
for parent_name, sub_names in CATEGORY_MAP.items():
|
| 83 |
+
parent_cat = db.query(Category).filter(Category.name_ar == parent_name).first()
|
| 84 |
+
if not parent_cat:
|
| 85 |
+
parent_cat = Category(
|
| 86 |
+
name_ar=parent_name,
|
| 87 |
+
name_en=parent_name,
|
| 88 |
+
icon="grid",
|
| 89 |
+
)
|
| 90 |
+
db.add(parent_cat)
|
| 91 |
+
db.commit()
|
| 92 |
+
db.refresh(parent_cat)
|
| 93 |
+
parent_cats[parent_name] = parent_cat
|
| 94 |
+
|
| 95 |
+
# 2. Create Subcategories
|
| 96 |
+
sub_cats = {}
|
| 97 |
+
for parent_name, sub_names in CATEGORY_MAP.items():
|
| 98 |
+
for sub_name in sub_names:
|
| 99 |
+
sub_cat = db.query(Category).filter(Category.name_ar == sub_name).first()
|
| 100 |
+
if not sub_cat:
|
| 101 |
+
sub_cat = Category(
|
| 102 |
+
name_ar=sub_name,
|
| 103 |
+
name_en=sub_name,
|
| 104 |
+
icon="list",
|
| 105 |
+
parent_id=parent_cats[parent_name].id
|
| 106 |
+
)
|
| 107 |
+
db.add(sub_cat)
|
| 108 |
+
db.commit()
|
| 109 |
+
db.refresh(sub_cat)
|
| 110 |
+
sub_cats[sub_name] = {"model": sub_cat, "parent_name": parent_name}
|
| 111 |
+
|
| 112 |
+
# 3. Create Products
|
| 113 |
+
inserted_count = 0
|
| 114 |
+
for item in products_data:
|
| 115 |
+
cat_name = item.get("الفئة")
|
| 116 |
+
if not cat_name or cat_name not in sub_cats:
|
| 117 |
+
continue
|
| 118 |
+
|
| 119 |
+
sub_cat_info = sub_cats[cat_name]
|
| 120 |
+
|
| 121 |
+
price = float(item.get("سعر البيع (ريال)", 0))
|
| 122 |
+
compare_price = price * 1.2
|
| 123 |
+
|
| 124 |
+
prod = Product(
|
| 125 |
+
name_ar=item.get("اسم المنتج", ""),
|
| 126 |
+
name_en=item.get("اسم المنتج", ""),
|
| 127 |
+
description_ar=item.get("الوصف التسويقي", ""),
|
| 128 |
+
description_en=item.get("الوصف التسويقي", ""),
|
| 129 |
+
price=price,
|
| 130 |
+
compare_price=round(compare_price, 2),
|
| 131 |
+
stock=random.randint(10, 100),
|
| 132 |
+
category_id=sub_cat_info["model"].id,
|
| 133 |
+
rating=round(random.uniform(3.5, 5.0), 1),
|
| 134 |
+
rating_count=random.randint(0, 50),
|
| 135 |
+
is_featured=random.choice([True, False]),
|
| 136 |
+
is_active=True
|
| 137 |
)
|
| 138 |
+
db.add(prod)
|
| 139 |
+
db.flush()
|
| 140 |
+
|
| 141 |
+
# 4. Create multiple images
|
| 142 |
+
num_images = random.randint(2, 4)
|
| 143 |
+
for i in range(num_images):
|
| 144 |
+
img_url = get_image_url(sub_cat_info["parent_name"], i)
|
| 145 |
+
prod_img = ProductImage(
|
| 146 |
+
product_id=prod.id,
|
| 147 |
+
image_url=img_url,
|
| 148 |
+
alt_text=prod.name_ar,
|
| 149 |
+
sort_order=i
|
| 150 |
+
)
|
| 151 |
+
db.add(prod_img)
|
| 152 |
+
|
| 153 |
+
inserted_count += 1
|
| 154 |
+
|
| 155 |
+
# Seed Coupons
|
| 156 |
+
DEMO_COUPONS = [
|
| 157 |
+
{"code": "WELCOME10", "discount_percent": 10.0, "is_active": True},
|
| 158 |
+
{"code": "VORTEX20", "discount_percent": 20.0, "is_active": True},
|
| 159 |
+
{"code": "SUMMER50", "discount_percent": 50.0, "is_active": False},
|
| 160 |
+
]
|
| 161 |
+
|
| 162 |
+
for coupon_data in DEMO_COUPONS:
|
| 163 |
+
coupon = Coupon(**coupon_data)
|
| 164 |
+
db.add(coupon)
|
| 165 |
|
| 166 |
+
db.commit()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
|
| 168 |
+
return AuthResponse(
|
| 169 |
+
isSuccess=True,
|
| 170 |
+
value={
|
| 171 |
+
"message": "Database seeded successfully",
|
| 172 |
+
"categories_count": db.query(Category).count(),
|
| 173 |
+
"products_count": inserted_count,
|
| 174 |
+
"coupons_count": len(DEMO_COUPONS),
|
| 175 |
+
},
|
| 176 |
+
statusCode=201,
|
| 177 |
+
)
|
| 178 |
|
| 179 |
+
except Exception as e:
|
| 180 |
+
db.rollback()
|
| 181 |
+
return AuthResponse(
|
| 182 |
+
isSuccess=False,
|
| 183 |
+
value={"message": str(e)},
|
| 184 |
+
statusCode=500,
|
| 185 |
+
error=str(e)
|
| 186 |
+
)
|
|
|
|
|
|