Joost
Joost

Reputation: 4134

Django data migration raising a Type Error

I've got two relevant models; Item and Category, and I'm trying to write a data migration to place existing items into categories. The migration looks like this so far:

def categorise(apps, schema_editor):
    Category = apps.get_model("orders", "Category")
    Item = apps.get_model("orders", "Item")
    some_category = Category.objects.create(name="Category name")
    some_category.items.add(Item.objects.get(name="Item name"))


class Migration(migrations.Migration):

    dependencies = [
        ('orders', '0005_populate'),
    ]

    operations = [
        migrations.RunPython(categorise)
    ]

And the (relevant parts of the) models look like this:

class Item(models.Model):
    name = models.CharField(max_length=200)

class Category(models.Model):
    name = models.CharField(max_length=200)
    items = models.ManyToManyField(Item)

When I perform the migration listed above, though, I run into a type error on the last line of the categorise function. It tells me TypeError: 'Item' instance expected, got <Item: Item object>. That's odd, since they seem like the same type. I took care to import the Item model via apps rather than through a regular import in order to make sure it would be the same historical model definition that created the items currently in the database.

What could be the reason?

EDIT: Here are the relevant bits from the previous migrations. I now realise some of these originate from before I updated to Django 1.8. Is that relevant?

EDIT 2: As it turns out, running python manage.py migrate twice in direct succession does fix the issue. What does that mean?

Update: this turned out to be a bug in 1.8, but will be fixed when the patch for ticket 24573 is merged.

0001:

class Migration(migrations.Migration):

    dependencies = [
    ]

    operations = [
        migrations.CreateModel(
            name='Item',
            fields=[
                ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
                ('name', models.CharField(max_length=200))
            ],
            options={
            },
            bases=(models.Model,),
        ),
    ]

0002:

class Migration(migrations.Migration):

    dependencies = [
        ('orders', '0001_initial'),
    ]

    operations = [
        migrations.CreateModel(
            name='Category',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('name', models.CharField(max_length=200)),
                ('items', models.ManyToManyField(to='orders.Item'))
            ],
            options={
            },
            bases=(models.Model,),
        ),
    ]

0003:

class Migration(migrations.Migration):

    dependencies = [
        ('orders', '0002_category'),
    ]

    operations = [
        migrations.AlterField(
            model_name='item',
            name='discounts',
            field=models.ManyToManyField(blank=True, to='orders.Discount'),
            preserve_default=True,
        ),
    ]

0005:

def populate(apps, schema_editor):
    Item = apps.get_model("orders", "Item")

    def add_item(name):
        item = Item.objects.create(name=name)
        # ..

    add_item("Item name", 160)
    # .. etc

class Migration(migrations.Migration):

    dependencies = [
        ('orders', '0004_auto_20150328_0929'),
    ]

    operations = [
        migrations.RunPython(populate)
    ]

Upvotes: 4

Views: 1887

Answers (1)

knbk
knbk

Reputation: 53669

My guess is that you're on 1.7. and the models in apps are not properly reloaded after a change from a previous migration has affected one of them. Handling of this is better in 1.8.

A workaround is to use directly add the primary key of Item:

def categorise(apps, schema_editor):
    Category = apps.get_model("orders", "Category")
    Item = apps.get_model("orders", "Item")
    some_category = Category.objects.create(name="Category name")
    some_category.items.add(Item.objects.get(name="Item name").pk)
    # or for multiple items
    some_category.items.add(Item.objects.filter(...).values_list('pk', flat=True))

Upvotes: 4

Related Questions