Hello,
I am somewhat new to Django, I am creating a website for a lighting company just to showcase their products.
The main part of the website is the products tab where the user can look at “Categories” and choose one of its “Subcategories”, then he will be redirected to a page with all the "Products"under that “subcategory” there the user can use a filter on the left side to only show products that match specific “Attribute” values.
I had to design my database structure 3 times because it didn’t fit the website
Database schema 1 (Login required)
Database schema 2 (Login required)
Then I redesigned it because I wasn’t able to present it in Django admin using nested inlines.
Database schema 3 (Current):
Models.py:
from django.core.validators import FileExtensionValidator
from django.db import models
# Create your models here.
class String(models.Model):
value = models.CharField(max_length=50, unique=True, blank=False, null=False)
def __str__(self):
return f"{self.value}"
class Integer(models.Model):
value = models.IntegerField(unique=True, blank=False, null=False)
def __str__(self):
return f"{self.value}"
class Decimal(models.Model):
value = models.DecimalField(unique=True, blank=False, null=False, max_digits=10, decimal_places=2)
def __str__(self):
return f"{self.value}"
class RangedDecimal(models.Model):
min_value = models.DecimalField(max_digits=10, decimal_places=2, null=False)
max_value = models.DecimalField(max_digits=10, decimal_places=2, null=False)
def values_range(self):
values_range = (self.min_value, self.max_value)
return values_range
class Meta:
unique_together = ('min_value', 'max_value')
def __str__(self):
return f"{self.min_value}-{self.max_value}"
class RangedInteger(models.Model):
min_value = models.IntegerField(blank=False, null=False)
max_value = models.IntegerField(blank=False, null=False)
def values_range(self):
values_range = (self.min_value, self.max_value)
return values_range
class Meta:
unique_together = ('min_value', 'max_value')
def __str__(self):
return f"{self.min_value} - {self.max_value}"
class Category(models.Model):
name = models.CharField(max_length=50, unique=True, blank=False, null=False)
def __str__(self):
return f"{self.name}"
class Attribute(models.Model):
name = models.CharField(max_length=50, unique=True, blank=False, null=False)
def __str__(self):
return f"{self.name}"
class Subcategory(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE)
name = models.CharField(max_length=50, unique=True, blank=False, null=False)
attribute = models.ManyToManyField(
Attribute,
through='SubcategoryAttribute',
related_name='subcategory'
)
class Meta:
unique_together = ('category', 'name')
def __str__(self):
return f"{self.name}"
class Media(models.Model):
brand = models.OneToOneField(
'Brand',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='media'
)
category = models.OneToOneField(
'Category',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='media'
)
subcategory = models.OneToOneField(
'Subcategory',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='media'
)
image = models.ImageField(
upload_to='products/',
blank=True,
null=True,
default='products/default.png'
)
pdf = models.FileField(
upload_to='pdfs/',
blank=True,
null=True,
validators=[FileExtensionValidator(['pdf'])],
help_text='Upload product specification PDF'
)
name = models.CharField(max_length=50)
class Meta:
unique_together = [
('image', 'pdf', 'name'),
('image', 'pdf'),
]
def __str__(self):
return f"{self.name}"
class Brand(models.Model):
name = models.CharField(max_length=50, unique=True, blank=False, null=False)
def __str__(self):
return f"{self.name}"
class SubcategoryAttribute(models.Model):
attribute = models.ForeignKey(Attribute, on_delete=models.CASCADE)
subcategory = models.ForeignKey(Subcategory, on_delete=models.CASCADE)
class Meta:
unique_together = ('attribute', 'subcategory')
def __str__(self):
return f"{self.subcategory} - {self.attribute}"
class Product(models.Model):
brand = models.ForeignKey(Brand, on_delete=models.CASCADE)
subcategory = models.ForeignKey(Subcategory, on_delete=models.CASCADE)
name = models.CharField(max_length=50, unique=True, blank=False, null=False)
class Meta:
unique_together = [
('brand', 'subcategory', 'name'),
('brand', 'subcategory'),
]
def __str__(self):
return f"{self.brand} - {self.subcategory} - {self.name}"
class Model(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
model_letter = models.CharField(max_length=10, blank=True, null=False)
model_number = models.IntegerField(blank=False, null=False)
media = models.ManyToManyField(Media, through="ModelMedia")
string_values = models.ManyToManyField(String, through="SubcategoryAttributeModelString")
integer_values = models.ManyToManyField(Integer, through="SubcategoryAttributeModelInteger")
decimal_values = models.ManyToManyField(Decimal, through="SubcategoryAttributeModelDecimal")
ranged_integer_value = models.ManyToManyField(RangedInteger, through="SubcategoryAttributeModelRangedInteger")
ranged_decimal_value = models.ManyToManyField(RangedDecimal, through="SubcategoryAttributeModelRangedDecimal")
def value(self):
values = 0
attribute_value = None
value_attributes = [self.string_values, self.integer_values, self.decimal_values, self.ranged_integer_value,
self.ranged_decimal_value]
for value in value_attributes:
if value is not None:
values += values
attribute_value = value
if values != 1:
raise ValueError("A subcategory attribute cannot have less than or more than 1 value")
else:
return attribute_value
value = value
unique_together = [
('model_number', 'model_letter', 'product', 'value'),
('model_number', 'model_letter', 'product'),
('model_number', 'model_letter'),
]
def __str__(self):
return f"{self.product} - {self.model_letter}{self.model_number}" # i removed - {self.value} here becaue i got eror, find solution
class ModelMedia(models.Model):
model = models.ForeignKey(Model, on_delete=models.CASCADE)
media = models.ForeignKey(Media, on_delete=models.CASCADE)
class Meta:
unique_together = ('media', 'model')
class SubcategoryAttributeModelString(models.Model):
subcategory_attribute = models.ForeignKey(SubcategoryAttribute, on_delete=models.CASCADE)
model = models.ForeignKey(Model, on_delete=models.CASCADE)
string = models.ForeignKey(String, on_delete=models.CASCADE)
class Meta:
unique_together = [
('subcategory_attribute','string'),
('subcategory_attribute', 'string', 'model')
]
def __str__(self):
return f"{self.subcategory_attribute} - {self.model} - {self.string}"
class SubcategoryAttributeModelInteger(models.Model):
subcategory_attribute = models.ForeignKey(SubcategoryAttribute, on_delete=models.CASCADE)
model = models.ForeignKey(Model, on_delete=models.CASCADE)
integer = models.ForeignKey(Integer, on_delete=models.CASCADE)
class Meta:
unique_together = [
('subcategory_attribute','integer'),
('subcategory_attribute', 'integer', 'model')
]
def __str__(self):
return f"{self.subcategory_attribute} - {self.model} - {self.integer}"
class SubcategoryAttributeModelDecimal(models.Model):
subcategory_attribute = models.ForeignKey(SubcategoryAttribute, on_delete=models.CASCADE)
model = models.ForeignKey(Model, on_delete=models.CASCADE)
decimal = models.ForeignKey(Decimal, on_delete=models.CASCADE)
class Meta:
unique_together = [
('subcategory_attribute','decimal'),
('subcategory_attribute', 'decimal', 'model')
]
def __str__(self):
return f"{self.subcategory_attribute} - {self.model} - {self.decimal}"
class SubcategoryAttributeModelRangedInteger(models.Model):
subcategory_attribute = models.ForeignKey(SubcategoryAttribute, on_delete=models.CASCADE)
model = models.ForeignKey(Model, on_delete=models.CASCADE)
ranged_integer = models.ForeignKey(RangedInteger, on_delete=models.CASCADE)
class Meta:
unique_together = [
('subcategory_attribute','ranged_integer'),
('subcategory_attribute', 'ranged_integer', 'model')
]
def __str__(self):
return f"{self.subcategory_attribute} - {self.model} - {self.ranged_integer}"
class SubcategoryAttributeModelRangedDecimal(models.Model):
subcategory_attribute = models.ForeignKey(SubcategoryAttribute, on_delete=models.CASCADE)
model = models.ForeignKey(Model, on_delete=models.CASCADE)
ranged_decimal = models.ForeignKey(RangedDecimal, on_delete=models.CASCADE)
class Meta:
unique_together = [
('subcategory_attribute','ranged_decimal'),
('subcategory_attribute', 'ranged_decimal', 'model')
]
def __str__(self):
return f"{self.subcategory_attribute} - {self.model} - {self.ranged_decimal}"
Admin.py:
import nested_admin
from django.contrib import admin
from .models import *
class MediaInline(admin.TabularInline):
model = Media
can_delete = False
max_num = 1 #(OneToOne)
fields = ('image', 'name')
class ModelMediaInline(nested_admin.NestedTabularInline):
model = ModelMedia
extra = 1
class SubcategoryAttributeModelStringInline(nested_admin.NestedStackedInline):
model = SubcategoryAttributeModelString
extra = 1
class SubcategoryAttributeModelIntegerInline(nested_admin.NestedStackedInline):
model = SubcategoryAttributeModelInteger
extra = 1
class SubcategoryAttributeModelDecimalInline(nested_admin.NestedStackedInline):
model = SubcategoryAttributeModelDecimal
extra = 1
class SubcategoryAttributeModelRangedIntegerInline(nested_admin.NestedStackedInline):
model = SubcategoryAttributeModelRangedInteger
extra = 1
class SubcategoryAttributeModelRangedDecimalInline(nested_admin.NestedStackedInline):
model = SubcategoryAttributeModelRangedDecimal
extra = 1
class AttributeInline(admin.TabularInline):
model = SubcategoryAttribute
class ModelInline(nested_admin.NestedStackedInline):
model = Model
inlines = [
ModelMediaInline,
SubcategoryAttributeModelStringInline,
SubcategoryAttributeModelIntegerInline,
SubcategoryAttributeModelDecimalInline,
SubcategoryAttributeModelRangedIntegerInline,
SubcategoryAttributeModelRangedDecimalInline,
]
extra = 1
@admin.register(Brand)
class BrandAdmin(admin.ModelAdmin):
inlines = [MediaInline] # Add the inline
list_display = ('name',)
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
inlines = [MediaInline] # Add the inline
list_display = ('name',)
@admin.register(Subcategory)
class CategoryAdmin(admin.ModelAdmin):
inlines = [MediaInline, AttributeInline] # Add the inline
list_display = ('name', 'category')
@admin.register(Media)
class MediaAdmin(admin.ModelAdmin):
# Optional: Hide 'brand' in standalone Media admin
exclude = ('brand', 'category', 'subcategory')
list_display = ('image', 'pdf')
@admin.register(Attribute)
class AttributeAdmin(admin.ModelAdmin):
def get_model_perms(self, request):
return {}
@admin.register(Product)
class ProductAdmin(nested_admin.NestedModelAdmin):
inlines = [ModelInline]
@admin.register(Integer)
class IntegerAdmin(admin.ModelAdmin):
def get_model_perms(self, request):
"""
Return empty perms dict thus hiding the model from admin index.
"""
return {}
@admin.register(Decimal)
class DecimalAdmin(admin.ModelAdmin):
def get_model_perms(self, request):
return {}
@admin.register(String)
class StringAdmin(admin.ModelAdmin):
def get_model_perms(self, request):
return {}
@admin.register(RangedDecimal)
class RangedDecimalAdmin(admin.ModelAdmin):
def get_model_perms(self, request):
return {}
@admin.register(RangedInteger)
class RangedIntegerAdmin(admin.ModelAdmin):
def get_model_perms(self, request):
return {}
I feel like the data base have some flaws. One problem that I found Is that Iam allowed to add any “attribute” for a model (even the attributes that are not connected to the subcategory of the product of the model. For example the category “Led frame” have a color and a size attribute while the category “led spot light” have a “wat” and “Volt” attribute, but I’ am allowed to add a wat value for an Led frame which shouldn’t be allowed by my Django admin interface or the database itself. If Iam not careful when inserting data i could make this mistake.
Given my schema, admin.py, models.py, the fact that I want to create a product filter, and the fact that I want the website to support English and Arabic language (wich unlike most languages, it goes from right to left so i have to think about how to do this for the website); Is this database good? If so what improvements I can make?
Your feedbacks are appreciated.
Thank you.