Designing a Gold Jewelry E-Commerce Database with Django
Learn how to design a scalable Django database for a gold jewelry e-commerce platform by modeling products, attributes, and purchasable product variants using real-world domain-driven design principles.
Django
Backend
Architecture
Aug 4, 2026 · 5 min read
At first glance, building an online jewelry store looks no different from building any other e-commerce platform. You have products, customers, orders, and payments. Simple enough, right?
That was exactly what I thought when I accepted a project to develop a jewelry gallery and online store.
A few hours later, after defining the business rules and identifying the domain entities, I realized the real challenge wasn't writing code—it was designing the data model.
Start with Business Scenarios, Not Models
One of the biggest mistakes developers make is opening the IDE before understanding the business.
Before creating a single Django model, I always walk through the application's use cases.
Imagine a customer visiting the website:
- They browse products.
- They compare different items.
- They inspect colors, materials, weight, and specifications.
- They check the price.
- They compare similar products.
- They add one or more items to the shopping cart.
- Finally, they place an order.
For a jewelry store, the scenario becomes more interesting.
Customers don't just care about the product itself. They also care about:
- Gold weight
- Karat (purity)
- Making fee
- Gemstones
- Color
- Size
- Additional materials such as leather straps
These values directly affect the final price.
Once you analyze these scenarios, the domain entities naturally begin to appear.
Identifying the Core Entities
The first obvious entity is the customer.
User
The customer browses products.
Product
Every product has characteristics.
ProductAttribute
The customer eventually adds something to the shopping cart.
Order
OrderItem
So far everything looks like a standard e-commerce system.
But here's the important question.
What Is the Customer Actually Buying?
This is where many database designs fail.
If you've previously built stores for books, digital products, or simple inventory, it's tempting to reference Product directly from the order.
That works until your products become configurable.
Consider a single ring.
The same ring may exist in several variations:
- Different weights
- Different sizes
- Different karats
- Different gemstones
- Different colors
- Different stock quantities
- Different prices
Although customers see one product page, they are actually purchasing one specific variation.
This means an order should never point directly to Product.
Instead, it should reference a purchasable variant.
The Product Model
The base product remains simple.
class Product(models.Model):
name = models.CharField(max_length=255)
description = models.TextField(blank=True)
A product simply represents the parent item.
Think of it as the catalog entry.
Categories and Tags
Customers often search by category.
For example:
- Rings
- Necklaces
- Bracelets
Or they may search by tags.
Category
Tag
Depending on your application, categories and tags may overlap or serve completely different purposes.
Product Attributes
A reusable attribute model makes filtering much easier.
class ProductAttribute(BaseModel):
ATTRIBUTE_CHOICES = (
("color", "Color"),
("size", "Size"),
("weight", "Weight"),
("karat", "Karat"),
("gem", "Gem"),
("strap", "Strap"),
("other", "Other"),
)
attribute_type = models.CharField(...)
key = models.CharField(...)
value = models.CharField(...)
Notice that these attributes are only raw pieces of information.
They don't define a sellable product.
They don't have:
- Price
- Inventory
- SKU
- Making fee
- Profit margin
Those belong somewhere else.
Introducing Product Variants
This is the missing piece.
class ProductVariable(BaseModel):
product = models.ForeignKey(
Product,
on_delete=models.CASCADE,
related_name="variables"
)
code = models.CharField(max_length=255)
price = models.DecimalField(max_digits=10, decimal_places=0)
making_fee = models.DecimalField(max_digits=10, decimal_places=0)
stock = models.IntegerField(default=0)
attributes = models.ManyToManyField(
ProductAttribute,
blank=True
)
Each ProductVariable represents an actual purchasable item.
A single product can have multiple variants, each with:
- Different attributes
- Different stock
- Different price
- Different making fee
- Different SKU
This is the model that inventory, payments, invoices, and order calculations should reference.
Not the parent product.
Why This Design Matters
Imagine a ring available in four variations.
| Variant | Weight | Karat | Price | Stock |
|---|---|---|---|---|
| A | 3.2g | 18K | $420 | 2 |
| B | 3.5g | 18K | $455 | 5 |
| C | 4.0g | 21K | $610 | 1 |
| D | 4.2g | 21K | $640 | 0 |
The customer visits a single product page.
However, when they click Buy, they are purchasing Variant C—not the generic ring.
That distinction makes all the difference in your database design.
Real-World Scenarios
Real businesses introduce even more complexity.
For example:
- An item may be out of stock.
- Customers may place custom manufacturing orders.
- Products may contain image galleries.
- Products may include promotional videos.
- Prices may change according to the daily gold rate.
- Making fees may differ between variants.
The more scenarios you analyze before coding, the fewer architectural changes you'll need later.
A Typical System Design Workflow
Before writing code, I usually follow these steps:
- Gather business requirements.
- Write the primary use cases.
- Identify domain entities.
- Define relationships between entities.
- Draw a Class Diagram.
- Create the ER Diagram.
- Design the database schema.
- Review edge cases.
- Validate the design with stakeholders.
- Finally, start implementing the models and business logic.
Skipping these steps often leads to frequent database migrations, broken relationships, and unnecessary refactoring.
Final Thoughts
Designing a jewelry e-commerce platform taught me an important lesson:
Customers don't buy products—they buy product variants.
Once you model that concept correctly, everything else becomes much simpler.
Orders reference variants.
Inventory tracks variants.
Invoices are generated for variants.
Pricing is calculated from variants.
The parent Product simply becomes the container that groups all of those purchasable variations together.
In future articles, I'll dive deeper into each step of the architecture—from writing use cases and drawing class diagrams to implementing the complete Django models with real-world examples.
Related Notes
Django Transactions: What atomic() Actually Protects
Learn how Django transactions work, what transaction.atomic() actually protects, and how to handle race conditions, row locking, database constraints, nested transactions, and post-commit side effects with select_for_update() and on_commit().
The Hidden Cost of Django REST Framework Serializers
Learn how Django REST Framework serializers can cause N+1 queries, slow API responses, and unnecessary database work—and how to optimize them with select_related, prefetch_related, annotations, and better serializer design.
Building a Django Package — Part 4: Publishing django-healthkit to PyPI
In this final part, we prepare django-healthkit for release, build and validate the package, test it on TestPyPI, publish it to PyPI, and create a Git tag and GitHub release.
Building a Django Package — Part 3: Health Check Manager and Endpoint
In Part 3, we connect the database and cache health checks, build the health check manager, expose a Django health endpoint, measure check latency, and return a structured health status.
Building a Django Package — Part 2: Database and Cache Health Checks
In this part, we implement the first health checks for django-healthkit, covering database connectivity and Django cache functionality with simple, independent, and testable checks.
Building a Django Package — Part 1: Setting Up django-healthkit
In this part, we build the initial structure of django-healthkit, configure the package with pyproject.toml, and prepare it for development.
Building a Secure Webhook Receiver for Server-to-Server Communication
How I built a secure FastAPI webhook receiver using RSA signatures to enable authenticated server-to-server communication, proxy requests, and connect applications across different network environments.
Why I Switched to Conventional Git Commit Messages
Why I switched from inconsistent Git commit messages to a Conventional Commits style, with practical examples for cleaner and more maintainable Git history.
Building django-healthkit
How a simple health endpoint for Bidar turned into django-healthkit, a lightweight Django health-check package.
How to Fix Common Next.js 16 Build Errors (Proxy, Dynamic Rendering & Revalidation)
Learn how to fix common Next.js 16 build errors, understand Proxy, Dynamic Rendering, and Revalidation, and improve your application's SEO and performance.
Why I Built cyber-ui: A Minimal Design System for Developers
How my projects, from backend systems to monitoring tools, led me to create a minimal and personal UI foundation.