Spaces:
Sleeping
Sleeping
| from django.db import models | |
| from django.utils import timezone | |
| import uuid | |
| from .storage import ProductImageStorage | |
| product_image_storage = ProductImageStorage() | |
| class Size(models.Model): | |
| """Size definitions with measurement ranges""" | |
| name = models.CharField(max_length=10, unique=True) # S, M, L, XL, XXL | |
| chest_min = models.DecimalField(max_digits=5, decimal_places=2) # cm | |
| chest_max = models.DecimalField(max_digits=5, decimal_places=2) # cm | |
| waist_min = models.DecimalField(max_digits=5, decimal_places=2) # cm | |
| waist_max = models.DecimalField(max_digits=5, decimal_places=2) # cm | |
| shoulder_min = models.DecimalField(max_digits=5, decimal_places=2) # cm | |
| shoulder_max = models.DecimalField(max_digits=5, decimal_places=2) # cm | |
| height_min = models.DecimalField(max_digits=5, decimal_places=2) # cm | |
| height_max = models.DecimalField(max_digits=5, decimal_places=2) # cm | |
| class Meta: | |
| ordering = ['id'] | |
| def __str__(self): | |
| return self.name | |
| class Color(models.Model): | |
| """Color definitions with hex codes""" | |
| CATEGORY_CHOICES = [ | |
| ('light', 'Light Colors'), | |
| ('medium', 'Medium Colors'), | |
| ('dark', 'Dark Colors'), | |
| ('neutral', 'Neutral Colors'), | |
| ('vibrant', 'Vibrant Colors'), | |
| ] | |
| name = models.CharField(max_length=50) | |
| hex_code = models.CharField(max_length=7) # e.g., #FF5733 | |
| category = models.CharField(max_length=20, choices=CATEGORY_CHOICES) | |
| class Meta: | |
| ordering = ['name'] | |
| def __str__(self): | |
| return self.name | |
| class Product(models.Model): | |
| """Clothing products""" | |
| CATEGORY_CHOICES = [ | |
| ('shirt', 'Shirt'), | |
| ('pants', 'Pants'), | |
| ('jacket', 'Jacket'), | |
| ('dress', 'Dress'), | |
| ('skirt', 'Skirt'), | |
| ] | |
| GENDER_CHOICES = [ | |
| ('men', 'Men'), | |
| ('women', 'Women'), | |
| ('unisex', 'Unisex'), | |
| ] | |
| name = models.CharField(max_length=200) | |
| category = models.CharField(max_length=20, choices=CATEGORY_CHOICES) | |
| gender = models.CharField(max_length=10, choices=GENDER_CHOICES) | |
| price = models.DecimalField(max_digits=10, decimal_places=2) | |
| description = models.TextField() | |
| image = models.ImageField( | |
| upload_to='images/products/', | |
| storage=product_image_storage, | |
| blank=True, | |
| null=True, | |
| ) | |
| created_at = models.DateTimeField(auto_now_add=True) | |
| class Meta: | |
| ordering = ['name'] | |
| def __str__(self): | |
| return f"{self.name} ({self.gender})" | |
| class ProductVariant(models.Model): | |
| """Combination of product + size + color""" | |
| product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='variants') | |
| size = models.ForeignKey(Size, on_delete=models.CASCADE) | |
| color = models.ForeignKey(Color, on_delete=models.CASCADE) | |
| sku = models.CharField(max_length=50, unique=True) | |
| created_at = models.DateTimeField(auto_now_add=True) | |
| class Meta: | |
| ordering = ['product', 'size', 'color'] | |
| unique_together = ['product', 'size', 'color'] | |
| def __str__(self): | |
| return f"{self.product.name} - {self.size.name} - {self.color.name}" | |
| class Inventory(models.Model): | |
| """Stock tracking for each variant""" | |
| product_variant = models.OneToOneField(ProductVariant, on_delete=models.CASCADE, related_name='inventory') | |
| quantity = models.IntegerField(default=0) | |
| low_stock_threshold = models.IntegerField(default=5) | |
| last_updated = models.DateTimeField(auto_now=True) | |
| class Meta: | |
| verbose_name_plural = 'Inventories' | |
| def __str__(self): | |
| return f"{self.product_variant} - Stock: {self.quantity}" | |
| def is_low_stock(self): | |
| return 0 < self.quantity <= self.low_stock_threshold | |
| def is_out_of_stock(self): | |
| return self.quantity <= 0 | |
| def is_available(self): | |
| return self.quantity > 0 | |
| class BodyScan(models.Model): | |
| """Stored measurement data (no images)""" | |
| SKIN_TONE_CHOICES = [ | |
| ('very_light', 'Very Light'), | |
| ('light', 'Light'), | |
| ('intermediate', 'Intermediate'), | |
| ('tan', 'Tan'), | |
| ('dark', 'Dark'), | |
| ] | |
| UNDERTONE_CHOICES = [ | |
| ('warm', 'Warm'), | |
| ('cool', 'Cool'), | |
| ] | |
| BODY_SHAPE_CHOICES = [ | |
| ('hourglass', 'Hourglass'), | |
| ('rectangle', 'Rectangle'), | |
| ('triangle', 'Triangle (Pear)'), | |
| ('inverted_triangle', 'Inverted Triangle (Athletic)'), | |
| ('oval', 'Oval (Apple)'), | |
| ] | |
| session_id = models.UUIDField(default=uuid.uuid4, unique=True, editable=False) | |
| # Core measurements (required) | |
| height = models.DecimalField(max_digits=5, decimal_places=1) # cm - used for calibration | |
| shoulder_width = models.DecimalField(max_digits=5, decimal_places=1) # cm | |
| chest = models.DecimalField(max_digits=5, decimal_places=1) # cm - circumference | |
| waist = models.DecimalField(max_digits=5, decimal_places=1) # cm - circumference | |
| # Fashion-specific measurements (nullable for backward compatibility) | |
| hip = models.DecimalField(max_digits=5, decimal_places=1, null=True, blank=True) # cm - circumference | |
| torso_length = models.DecimalField(max_digits=5, decimal_places=1, null=True, blank=True) # cm | |
| arm_length = models.DecimalField(max_digits=5, decimal_places=1, null=True, blank=True) # cm | |
| inseam = models.DecimalField(max_digits=5, decimal_places=1, null=True, blank=True) # cm - for pants | |
| # Body shape classification | |
| body_shape = models.CharField(max_length=20, choices=BODY_SHAPE_CHOICES, null=True, blank=True) | |
| # Skin analysis | |
| skin_tone = models.CharField(max_length=15, choices=SKIN_TONE_CHOICES) | |
| undertone = models.CharField(max_length=10, choices=UNDERTONE_CHOICES, default='warm') | |
| # Measurement quality metrics | |
| confidence_score = models.DecimalField(max_digits=3, decimal_places=2, default=1.0) # 0.0-1.0 | |
| frame_count = models.IntegerField(default=1) # Number of frames used for averaging | |
| is_fallback = models.BooleanField(default=False) | |
| error_message = models.TextField(null=True, blank=True) | |
| scanned_at = models.DateTimeField(auto_now_add=True) | |
| class Meta: | |
| ordering = ['-scanned_at'] | |
| def __str__(self): | |
| return f"Scan {self.session_id} - {self.scanned_at.strftime('%Y-%m-%d %H:%M')}" | |
| def chest_to_waist_ratio(self): | |
| """Calculate body proportion ratio for fit recommendation""" | |
| if self.waist > 0: | |
| return float(self.chest) / float(self.waist) | |
| return 1.0 | |
| def body_shape_display(self): | |
| """Get human-readable body shape name""" | |
| if self.body_shape: | |
| return dict(self.BODY_SHAPE_CHOICES).get(self.body_shape, self.body_shape) | |
| return None | |
| class Recommendation(models.Model): | |
| """Generated recommendations""" | |
| body_scan = models.ForeignKey(BodyScan, on_delete=models.CASCADE, related_name='recommendations') | |
| product = models.ForeignKey(Product, on_delete=models.CASCADE) | |
| recommended_size = models.CharField(max_length=10) | |
| recommended_fit = models.CharField(max_length=20) | |
| recommended_colors = models.TextField() # Comma-separated color names | |
| priority = models.IntegerField(default=0) # Higher = more relevant | |
| created_at = models.DateTimeField(auto_now_add=True) | |
| class Meta: | |
| ordering = ['-priority', 'product'] | |
| def __str__(self): | |
| return f"Recommendation for {self.body_scan.session_id} - {self.product.name}" | |
| def get_recommended_colors_list(self): | |
| """Return recommended colors as a list""" | |
| return [c.strip() for c in self.recommended_colors.split(',') if c.strip()] | |