The Ghost in the Migration: Why Your Model's Methods Disappear
Ever had your Django data migration mysteriously fail on a model property that you know exists? Here’s why.
I hit a wall. One of those classic Django head-scratchers that feels like it shouldn’t be happening. I had a simple data migration to run, and it kept failing with an error that made no sense: AttributeError: 'User' object has no attribute 'full_name'
.
I stared at my screen, confused. “But it does!” I thought. I could see the property right there in my models.py
:
# In my app/models.py
class User(AbstractUser):
# ... other fields ...
@property
def full_name(self):
"""A simple property to get the user's full name."""
return f"{self.first_name} {self.last_name}".strip()
And here was my migration file, trying to use it:
# In my app/migrations/0012_populate_names.py
from django.db import migrations
def populate_user_display_names(apps, schema_editor):
User = apps.get_model('my_app', 'User')
for user in User.objects.all():
# This is the line that was failing!
print(f"Processing user: {user.full_name}")
# ... do something with the name
class Migration(migrations.Migration):
dependencies = [('my_app', '0011_previous_migration')]
operations = [
migrations.RunPython(populate_user_display_names),
]
The code was clean. The property was there. So why was Django telling me it didn’t exist? The answer lies in one of the most important concepts of the migration framework: Historical Models.
When you run a migration, Django doesn’t just import your current models.py
file. If it did, your migrations would be fragile and break every time you changed a method’s signature or removed a property. Imagine trying to run a six-month-old migration against your brand-new code—it would be chaos.
Instead, Django does something clever. Each migration file acts as a snapshot of your models at that specific point in time. When RunPython
is executed, the line apps.get_model('my_app', 'User')
doesn’t give you your actual User
class. It gives you a dynamically-created “historical” version of it, built from the serialized field definitions stored in the migration history.
The key takeaway is that this serialization process only includes the schema—the database fields (CharField
, ForeignKey
, etc.) and their attributes. It does not include your custom Python methods, @property
definitions, or overridden save()
methods.
To Django’s migration engine, my User
model looked like this:
# What the migration ACTUALLY sees
class User:
# It sees the database fields...
first_name = ...
last_name = ...
email = ...
# ... but none of the Python methods or properties.
# The 'full_name' property is a ghost; it was never serialized.
The AttributeError
suddenly made perfect sense. The object I was working with inside the migration truly didn’t have a full_name
attribute.
The Fix: Think Like a Migration
The solution is to work with the data that the historical model does have: its fields. Instead of relying on the property, I had to replicate its logic directly inside the migration function.
# The CORRECTED app/migrations/0012_populate_names.py
from django.db import migrations
def populate_user_display_names(apps, schema_editor):
User = apps.get_model('my_app', 'User')
for user in User.objects.all():
# Replicate the logic using the fields the migration knows about.
full_name = f"{user.first_name} {user.last_name}".strip()
print(f"Processing user: {full_name}")
# ... now I can proceed
class Migration(migrations.Migration):
# ... (same as before) ...
operations = [
migrations.RunPython(populate_user_display_names),
]
By working with the raw fields (user.first_name
, user.last_name
), I was operating within the rules of the historical model. The migration now worked perfectly.
So next time you get a mysterious AttributeError
inside a data migration, don’t question your code. Remember the ghost in the migration and ask yourself: “Am I trying to use a piece of Python logic that only exists in my current models, and not in the history books?”
💡 Need a Developer Who Gets It Done?
If this post helped solve your problem, imagine what we could build together! I'm a full-stack developer with expertise in Python, Django, Typescript, and modern web technologies. I specialize in turning complex ideas into clean, efficient solutions.