DEV Community

Cover image for Why Adding a Unique SKU Field Broke My Django Migration
Michael Nyawade
Michael Nyawade

Posted on

Why Adding a Unique SKU Field Broke My Django Migration

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
Enter fullscreen mode Exit fullscreen mode

After creating the model, I generated and applied migrations:

python manage.py makemigrations
python manage.py migrate
Enter fullscreen mode Exit fullscreen mode

I also registered the model in admin.py:

from django.contrib import admin
from .models import Product


admin.site.register(Product)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

I also updated admin.py:

from django.contrib import admin
from .models import Category, Product


admin.site.register(Category)
admin.site.register(Product)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

I got this:

django.db.utils.IntegrityError:
UNIQUE constraint failed: new__inventory_product.sku
Enter fullscreen mode Exit fullscreen mode

At first, I was confused.

The sku field looked correct:

sku = models.CharField(max_length=50, unique=True)
Enter fullscreen mode Exit fullscreen mode

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:

  1. Create a new version of the table
  2. Copy the old data into the new table
  3. 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
Enter fullscreen mode Exit fullscreen mode

2. Deleted the SQLite Database

db.sqlite3
Enter fullscreen mode Exit fullscreen mode

3. Deleted Old Migration Files

I deleted the migration files inside the app’s migrations folder, except:

__init__.py
Enter fullscreen mode Exit fullscreen mode

4. Recreated Migrations

python manage.py makemigrations
Enter fullscreen mode Exit fullscreen mode

5. Applied Migrations Again

python manage.py migrate
Enter fullscreen mode Exit fullscreen mode

6. Recreated the Superuser

python manage.py createsuperuser
Enter fullscreen mode Exit fullscreen mode

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)