In building projects using Django, one thing that I have realized about database migrations is that things may appear simple until the moment one alters an existing model.
For example, in developing an inventory management app for my project recently, I experienced a migration problem, which, although small, helped me understand how migrations work within Django and in particular, when adding unique constraints to existing databases.
I would like to document this problem since someone else may have experienced the same thing.
The Initial Inventory Model
At the beginning, my Product model was simple.
from django.db import models
class Product(models.Model):
name = models.CharField(max_length=100)
buying_price = models.DecimalField(max_digits=10, decimal_places=2)
selling_price = models.DecimalField(max_digits=10, decimal_places=2)
stock_quantity = models.PositiveIntegerField(default=0)
low_stock_threshold = models.PositiveIntegerField(default=5)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
After creating the model, I generated and applied migrations:
python manage.py makemigrations
python manage.py migrate
I also registered the model in admin.py:
from django.contrib import admin
from .models import Product
admin.site.register(Product)
Everything worked perfectly.
I created a superuser, logged into the Django admin panel, tested the model, and there were no issues.
Updating the Inventory App
As the project evolved, I realized the inventory system needed product categories and SKU support.
So I updated models.py.
from django.db import models
class Category(models.Model):
name = models.CharField(max_length=100, unique=True)
class Meta:
verbose_name_plural = "Categories"
def __str__(self):
return self.name
class Product(models.Model):
category = models.ForeignKey(
Category,
on_delete=models.CASCADE,
related_name='products'
)
name = models.CharField(max_length=100)
sku = models.CharField(max_length=50, unique=True)
buying_price = models.DecimalField(max_digits=10, decimal_places=2)
selling_price = models.DecimalField(max_digits=10, decimal_places=2)
stock_quantity = models.PositiveIntegerField(default=0)
low_stock_threshold = models.PositiveIntegerField(default=5)
created_at = models.DateTimeField(auto_now_add=True)
def profit_per_item(self):
return self.selling_price - self.buying_price
def is_low_stock(self):
return self.stock_quantity <= self.low_stock_threshold
def __str__(self):
return self.name
I also updated admin.py:
from django.contrib import admin
from .models import Category, Product
admin.site.register(Category)
admin.site.register(Product)
Then I created new migrations.
At this point, I expected everything to work normally.
Instead, Django threw a migration error.
The Error I Encountered
When I ran:
python manage.py migrate
I got this:
django.db.utils.IntegrityError:
UNIQUE constraint failed: new__inventory_product.sku
At first, I was confused.
The sku field looked correct:
sku = models.CharField(max_length=50, unique=True)
So why was the migration failing?
What Was Actually Happening
After looking into it more carefully, I realized the issue was related to existing database records.
Before adding the sku field, the Product table already existed in the database.
When Django attempted to apply the migration, SQLite internally tried to:
- Create a new version of the table
- Copy the old data into the new table
- Apply the new constraints
The problem was that the new sku field had unique=True.
Existing records in the database didn’t have SKU values yet.
As SQLite tried moving data into the new table structure, duplicate empty values conflicted with the unique constraint.
That’s why the migration failed.
How I Fixed It
Since I was still in the development stage of the project, I decided to reset the database completely.
Here’s what I did:
1. Stopped the Development Server
CTRL + C
2. Deleted the SQLite Database
db.sqlite3
3. Deleted Old Migration Files
I deleted the migration files inside the app’s migrations folder, except:
__init__.py
4. Recreated Migrations
python manage.py makemigrations
5. Applied Migrations Again
python manage.py migrate
6. Recreated the Superuser
python manage.py createsuperuser
After that, everything worked correctly.
What I Learned
This experience taught me an important lesson:
Adding unique fields to existing Django models can break migrations if the database already contains records.
Even though the code itself may look correct, the migration process also depends on the current database state.
I also learned that:
- Resetting the database is acceptable during early development
- It is not a good solution for production systems
- Database schema changes should be planned carefully
- Unique constraints require special attention when existing data is involved
A Better Production Approach
If this were a production project, deleting the database would obviously be a bad idea.
A safer approach would be:
- Add the field without
unique=True - Populate existing rows with unique values
- Then apply the unique constraint in a later migration
That prevents conflicts with existing records.
Final Thoughts
This wasn’t the biggest bug I’ve encountered in Django, but it was one of those moments that helped me understand migrations better.
Sometimes small errors teach the most useful lessons.
If you’re learning Django and run into migration issues after modifying models, don’t panic immediately. In many cases, the problem is related to how existing database records interact with the new schema changes.
Hopefully this saves someone else a few hours of confusion.
Top comments (0)