1

I'm trying to migrate a 1-to-many field to an existing many-to-many field. I'll explain.

If I understood what was happening exactly, this question would be shorter. However, I'll try to give all the detail in hopes someone will spot the issue.

For migrating a 1-to-many to a new many-to-many, I read this blog post (also like this question), which worked. To summarize:

They started with a 1-to-many model (pizza has one topping):

from django.db import models

class Topping(models.Model):
    name = models.CharField(max_length=20)

class Pizza(models.Model):
    name = models.CharField(max_length=20)
    topping = models.ForeignKey(Topping)

and showed the migrations to get from one topping to multiple toppings per pizza.

In the second of three migrations they add a topping to the new many-to-many toppings field in a forward method of a migration which looks like this:

def forward(apps, schema_editor):
    Pizza = apps.get_model("pizza", "Pizza")
    for pizza in Pizza.objects.all():
        pizza.toppings.add(pizza.topping)

However, now I'm trying to migrate a different model to use the existing toppings. add isn't right, because I don't want to create new relationships, I want to use existing ones.

This is stretching the pizza example, but suppose I have a note for some pizzas about their topping (I dunno, calories?). That worked when there was one topping per pizza, but now that there are multiple toppings, I want that note to move to a pizza topping instead of a pizza. Something like:

class Note(models.Model):
  # We're planning to get rid of pizza after copying
  pizza = models.ForeignKey(Pizza)
  # I named the many-to-many field with a through object called PizzaTopping.
  # null=True because we want to migrate existing values into this field
  topping = db.ForeignKey(PizzaTopping, on_delete=db.CASCADE, null=True)

Now I have a migration for Note with:

    def forward(apps, schema_editor):
        Note = apps.get_model('pizza', 'Note')
        for note in Note.objects.all():
            # Find the existing pizza toppings for this Note
            # There's only one, since we just migrated pizza topping to toppings
            pizza_topping = PizzaTopping.objects.get(pizza_id=note.pizza)
            note.topping = pizza_topping

I get an error:

ValueError: Cannot assign "<PizzaTopping: PizzaTopping object (606)>":
  "Note.topping" must be a "PizzaTopping" instance.

That's pretty confusing, those types look the same! However, I traced this to a line in Django's related_descriptor.py in the ForwardManyToOneDescriptor object, __set__ method:

        # An object must be an instance of the related class.
        if value is not None and not isinstance(
            value, self.field.remote_field.model._meta.concrete_model):

The issue may be that value is a PizzaTopping, while self.field.remote_field.model._meta.concrete_model is a __fake__.PizzaTopping.

What is the complication here that introduces __fake__? Is it the ForwardManyToOneDescriptor? Is it that I'm in a migration?

How do I copy an existing PizzaTopping into my note in this migration code?

dfrankow
  • 20,191
  • 41
  • 152
  • 214

0 Answers0