1

I'm creating an ecommerce store that sells T-shirts, hoodies, mugs, shot glasses, etc. For the t-shirts and hoodies there are sizes and sometimes color associated with each product. I'm trying to add multiple variations for each product. Here's my model.py code:

class Category(models.Model):
    name = models.CharField(max_length = 255, db_index=True, null=True, blank=True)
    slug = models.SlugField(max_length=255, unique=True, default='')
    class Meta:
        verbose_name_plural = 'categories'

    def get_absolute_url(self):
        return reverse('main:category_list', args=[self.slug])
    def __str__(self):
        return self.name

class Product(models.Model):
    category = models.ForeignKey(Category, on_delete=models.CASCADE, null=True, blank=True)
    name = models.CharField(max_length = 255, default='')
    description = models.TextField(blank=True)
    image = models.ImageField(upload_to='products/', null=True, blank=True)
    slug = models.SlugField(max_length = 255, default='')
    price = models.DecimalField(max_digits=4,decimal_places=2)
    update_defaults = models.BooleanField(default=False)
    #inventory = models.DecimalField(max_digits=5, decimal_places=None)

    class Meta:
        ordering=(['name'])

    def get_absolute_url(self):
        return reverse('main:product_detail', args=[self.slug])

    def __str__(self):
        return self.name

class VariationManager(models.Manager):
    def all(self):
        return super(VariationManager, self).filter(active=True)

    def sizes(self):
        return self.all().filter(category='size')

    def colors(self):
        return self.all().filter(category='color')


VAR_CATEGORIES = (
    ('size', 'size'),
    ('color', 'color'),
    )


class Variation(models.Model):
    product = models.ForeignKey(Product, related_name="product_attrs", on_delete=models.CASCADE)
    category = models.CharField(max_length=120, choices=VAR_CATEGORIES, default='size')
    title = models.CharField(max_length=120)
    price = models.DecimalField(max_digits=100, decimal_places=2, null=True, blank=True)
    updated = models.DateTimeField(auto_now_add=False, auto_now=True)
    active = models.BooleanField(default=True)

    objects = VariationManager()

    def __str__(self):
        return self.category + " " + self.title

def product_defaults(sender, instance, created, *args, **kwargs):
    if instance.update_defaults:
        categories = Category.objects.all()
        print (categories)
        for cat in categories:
            print (cat.id)
            if cat.id == 1: #for t-shirts
                small_size = Variation.objects.get_or_create(product=instance,
                                            category='size',
                                            title='Small')
                medium_size = Variation.objects.get_or_create(product=instance,
                                            category='size',
                                            title='Medium')
                large_size = Variation.objects.get_or_create(product=instance,
                                            category='size',
                                            title='Large')
                XL_size = Variation.objects.get_or_create(product=instance,
                                            category='size',
                                            title='XL')
                DoubleXL_size = Variation.objects.get_or_create(product=instance,
                                            category='size',
                                            title='2XL')
                TripleXL_size = Variation.objects.get_or_create(product=instance,
                                            category='size',
                                            title='3XL')
        instance.update_defaults = False
        instance.save()

post_save.connect(product_defaults, sender=Product)

The way it appears right now in my admin interface there is a name, attribute, value, and price (only named relevant fields for clarity). If I add a product like such: "t-shirt_1, size, sm, 17.98", then the next item I need to add is "t-shirt_1, size, med, 17.98" and so forth (2xl and above, price goes up). Is there a way to simplify this where I just enter the product name once, then add all sizes and associated pricing, as well as inventory tracking (haven't created field yet) for each size within the product?

edit: I've edited my code. I got it figured out on the variations. Now I can't figure out how I could tie inventory quantities into it. If I put it in Product class, it's not specifying what size (i.e. 10 small, 8 medium, 12 Large).

Mike1982
  • 439
  • 10
  • 26

2 Answers2

0

Remove corresponding attribute fields from Product model and create OneToMany relationship between Product and ProductAttribute. Then create separate Size and Color models, and relate them to ProductAttribute with ManyToMany relationship:

class Product(models.Model):
    category = models.ForeignKey(Category, on_delete=models.CASCADE, null=True, blank=True)
    name = models.CharField(max_length = 255, default='')
    description = models.TextField(blank=True)
    image = models.ImageField(upload_to='products/', null=True, blank=True)
    slug = models.SlugField(max_length = 255, default='')
    price = models.DecimalField(max_digits=4,decimal_places=2)
    has_attributes = models.BooleanField(default=False)
    ...


class ProductAttribute(models.Model):
    product = models.ForeignKey('Product', related_name="product_attrs", on_delete=models.CASCADE)
    sizes = models.ManyToManyField('Size', related_name="product_sizes", null=True, blank=True)
    colors = models.ManyToManyField('Product', related_name="product_colors", null=True, blank=True)
    ...


class Size(models.Model):
    size_num = models.CharField(max_length=10)
    ...


class Color(models.Model):
    color_name = models.CharField(max_length=15)
    ...

Now you can create Product object, then go to ProductAttribute to relate corresponding product object with product attribute model and add each attribute of that product (sizes or colors). Whenever you need to get product size or color you can do it as follows:

# let's say you need to get sizes of last product
p = Product.objects.last()
sizes = p.product_attrs.sizes.all()
# ↑ returns queryset containing all sizes of product
Rustam Garayev
  • 2,632
  • 1
  • 9
  • 13
  • Thank you for your assistance. It didn't quite work as expected. sizes = p.product_attrs.sizes.all() won't work because Product doesn't have a field named "sizes". I modified your solution to one that works, but now I have a different problem. I got rid of the intermediary ProductAttributes class and made separate classes for Color and Size and added them as fields in Products, but how can I associated a quantity remaining and Price for each variant tied to each Product? – Mike1982 Sep 16 '21 at 21:02
  • If you did everything correctly, p.product_attrs should return ProductAttribute object rather than Product. – Rustam Garayev Sep 17 '21 at 06:13
  • Thank you. I got it. Slight modification to retrieving the queryset. instead of p.product_attrs.sizes.all() it's just p.product_attrs.all() since there's no "size" in Product class. I'm still not sure where or how to store inventory quantity. If I put it in Products, it would be the quantity for what? Small? Medium? etc. If I store in size or color, what if it's not a t-shirt? – Mike1982 Sep 20 '21 at 20:09
  • I've revamped my whole code. I even have a function that fills everything with default sizes. Posted up above the new code. Possible ideas for linking inventory quantities with product variants. – Mike1982 Sep 20 '21 at 20:14
  • I encourage you to open a new thread and explain your problem there, since you are not really using my way – Rustam Garayev Sep 20 '21 at 21:22
0

Not required

class Attribute(models.Model):
    name = models.CharField(max_length = 255,   default='')

    def __str__(self):
        return self.name
Jeremy Caney
  • 7,102
  • 69
  • 48
  • 77
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-ask). – Community Sep 16 '21 at 04:26