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?