Home > Blockchain >  Inherit model or make separate ones?
Inherit model or make separate ones?

Time:09-27

I have a model called "Phones" which has: screen size, RAM, etc. I have another one called "Laptops" which has: screen size, RAM, and Keyboard (QWERTZ, QWERTY, etc.). I could make a main model with basic fields like Name and Price. I want to just select a "Laptop" or a "Phone", without having unnecessary fields (e.g.: keyboard type for phones, or rear camera for laptops).

Should I make all fields and leave unneeded ones empty (would look silly to have "RAM" and "Keyboard type" and "Rear camera mpx" for a Mug)? Or should I make separate models for each? But then how could I combine query results (search for "Xiaomi" returning from the different models like phones, laptops, bikes, vacuum cleaners, etc.)?

CodePudding user response:

Have a look at abstract classes. What you are describing is explained in the official documentation: https://docs.djangoproject.com/en/4.1/topics/db/models/#abstract-base-classes

CodePudding user response:

I'm not sure what is bad practice, but I'll throw you some potential ideas on how you could do this:

#1 Abstract Model

class BaseProduct(models.Model):
    name = models.CharField(max_length=200)
    cost = models.DecimalField(max_digits=10, decimal_places=2, default=0)

    class Meta:
        abstract = True

# all models below will have a name   cost attibute
#   django might even throw them in the save table in the backend (not 100% sure)

class Phone(BaseProduct):
    rear_camera_mpx = models.CharField(max_length=200)
    # ..etc


class Laptop(BaseProduct):
    ram = models.CharField(max_length=200)
    # ..etc

###############################

# Example Query:
Laptop.objects.filter(name__icontains='MSI', ram='8gb')

# Filter Multiple Products
from itertools import chain
queryset_chain = chain(
    Phone.objects.filter(name__icontains=query),
    Laptop.objects.filter(name__icontains=query),
)

for i in queryset_chain
    if type(i) == Laptop:
        print(i.ram)
    # elif
    # .. etc

#2 Foreign Key Pointing back from Attributes

class BaseProduct(models.Model):
    name = models.CharField(max_length=200)
    cost = models.DecimalField(max_digits=10, decimal_places=2, default=0)

    # could add a type
    product_type = models.CharField(max_length=2, choices=PRODUCTTYPE_CHOICES, default='NA')

# Extra attributes, points back to base

class Phone(models.Model):
    product = models.ForeignKey(BaseProduct, on_delete=models.PROTECT)
    rear_camera_mpx = models.CharField(max_length=200)
    # ..etc


class Laptop(models.Model):
    product = models.ForeignKey(BaseProduct, on_delete=models.PROTECT)
    ram = models.CharField(max_length=200)
    # ..etc

###############################

# Example Query:
Laptop.objects.filter(product__name__icontains='MSI', ram='8gb')

# Search All Products
BaseProduct.objects.filter(name__icontains='MSI')

# then when you click on, use type to grab the correct full class based on "product_type"
if product_type == '01':
    return Laptop.objects.filter(product__pk=clickedOnDetailPk).first()

#3 GenericForeign Key Pointing to Attributes

  • Note: I find generic keys very clumsy and hard to use (that's just me tho)
class BaseProduct(models.Model):
    name = models.CharField(max_length=200)
    cost = models.DecimalField(max_digits=10, decimal_places=2, default=0)

    # could add a type
    product_type = models.CharField(max_length=2, choices=PRODUCTTYPE_CHOICES, default='NA')

    # Below the mandatory fields for generic relation
    content_type   = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id      = models.PositiveIntegerField()
    content_object = GenericForeignKey()

# Extra attributes, points back to base

class Phone(models.Model):
    rear_camera_mpx = models.CharField(max_length=200)
    # ..etc


class Laptop(models.Model):
    ram = models.CharField(max_length=200)
    # ..etc

###############################

# Example Query:

# Search All Products
l = BaseProduct.objects.filter(name__icontains='MSI')

for i in l:
    print(i.name, i.cost)
    print('Generic Key Obj:', i.content_object)
    print('Generic Key PK:', i.content_id)
    print('Generic Key Type:', i.content_type) # is number / can change if re-creating db (not fun)

    if i.product_type == '01': # Laptop Type / could also go by content_type with some extra logic
        print('RAM:', i.content_object.ram)

# to do stuff like \/ you need extra stuff (never sat down to figure this out)
BaseProduct.objects.filter(content_object__ram='8gb')

#4 Json Field Cram it all into a Single Table

  • Requires newer version of DBs Django
  • This could be abstracted out with proxy models Managers pretty crazily. I've done it for a table for a similar use case, except imagine creating a laptop & including all the components that themselves are products :D. Not sure if it's bad practice, it's ALOT of custom stuff, but I've really liked my results.
class BaseProduct(models.Model):
    name = models.CharField(max_length=200)
    cost = models.DecimalField(max_digits=10, decimal_places=2, default=0)

    # could add a type
    product_type = models.CharField(max_length=2, choices=PRODUCTTYPE_CHOICES, default='NA')

    # cram all the extra stuff as JSON
    attr = models.JSONField(null=True)


###############################

# laptop search
l = BaseProduct.objects.filter(name__icontains='MSI', attr__ram='8gb')

for i in l:
    print(i.name, i.cost, i.attr['ram'])

Overall

Overall I think #1 or #2 are the ways to go..
Unless you want to go wild and pretty much write everything, forms, admins, etc, then go #4

  • Related