Django ReverseSingleRelatedObjectDescriptor.__set__ ValueError
Question: I am creating a custom data migration to automatically create GenericRelation
entries in the database, based on existing entries across two different
models.

**Example models.py:**

    
    
    ...
    class Place
       content_type = models.ForeignKey(ContentType)
       object_id = models.PositiveIntegerField()
       content_object = generic.GenericForeignKey('content_type', 'object_id')
    
    
    class Restaurant
       name = models.CharField(max_length=60)
       location = models.CharField(max_length=60)
    
    
    class House
       location = models.CharField(max_length=60)
    

**Example 0011_place_data.py:**

    
    
    # -*- coding: utf-8 -*-
    from django.contrib.contenttypes.models import ContentType
    from django.db import models, migrations
    
    def forwards_func(apps, schema_editor):
        Restaurant = apps.get_model("simpleapp", "Restaurant")
        House = apps.get_model("simpleapp", "House")
        Place = apps.get_model("simpleapp", "Place")
    
        db_alias = schema_editor.connection.alias
    
        content_type = ContentType.objects.using(db_alias).get(
            app_label="simpleapp",
            model="restaurant"
        )
    
        for restaurant in Restaurant.objects.using(db_alias).all():
            Place.objects.using(db_alias).create(
                content_type=content_type,
                object_id=restaurant.id)
    
        content_type = ContentType.objects.using(db_alias).get(
            app_label="simpleapp",
            model="house"
        )
        for house in House.objects.using(db_alias).all():
            Place.objects.using(db_alias).create(
                content_type=content_type,
                object_id=house.id)
    
    
    class Migration(migrations.Migration):
    
        dependencies = [
            ('simpleapp', '0010_place')
        ]
    
        operations = [
            migrations.RunPython(
                forwards_func,
            ),
        ]
    

When I run this (Django 1.7.4) I get

    
    
    Operations to perform:
      Apply all migrations: simpleapp, admin, sessions, auth, contenttypes
    Synchronizing apps without migrations:
      Creating tables...
      Installing custom SQL...
      Installing indexes...
    Running migrations:
      Applying projects.0011_place_data...passing
    Traceback (most recent call last):
      File "manage.py", line 10, in <module>
        execute_from_command_line(sys.argv)
      File ".../lib/python3.4/site-packages/django/core/management/__init__.py", line 385, in execute_from_command_line
    utility.execute()
      File ".../lib/python3.4/site-packages/django/core/management/__init__.py", line 377, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
      File ".../lib/python3.4/site-packages/django/core/management/base.py", line 288, in run_from_argv
    self.execute(*args, **options.__dict__)
      File ".../lib/python3.4/site-packages/django/core/management/base.py", line 338, in execute
    output = self.handle(*args, **options)
      File ".../lib/python3.4/site-packages/django/core/management/commands/migrate.py", line 161, in handle
    executor.migrate(targets, plan, fake=options.get("fake", False))
      File ".../lib/python3.4/site-packages/django/db/migrations/executor.py", line 68, in migrate
    self.apply_migration(migration, fake=fake)
      File ".../lib/python3.4/site-packages/django/db/migrations/executor.py", line 102, in apply_migration
    migration.apply(project_state, schema_editor)
      File ".../lib/python3.4/site-packages/django/db/migrations/migration.py", line 108, in apply
    operation.database_forwards(self.app_label, schema_editor, project_state, new_state)
      File ".../lib/python3.4/site-packages/django/db/migrations/operations/special.py", line 117, in database_forwards
    self.code(from_state.render(), schema_editor)
      File ".../simpleapp/migrations/0011_place_data.py", line 19, in forwards_func
    object_id=restaurant.id)
      File ".../lib/python3.4/site-packages/django/db/models/query.py", line 370, in create
    obj = self.model(**kwargs)
      File ".../lib/python3.4/site-packages/django/db/models/base.py", line 440, in __init__
    setattr(self, field.name, rel_obj)
      File ".../lib/python3.4/site-packages/django/db/models/fields/related.py", line 598, in __set__
    self.field.rel.to._meta.object_name,
    ValueError: Cannot assign "<ContentType: restaurant>": "Place.content_type" must be a "ContentType" instance.
    

If I comment out the stanza raising the value error in the Django module
(django.db.models.fields.related.ReverseSingleRelatedObjectDescriptor.**set**)
it works as expected:

    
    
    ...
    
        elif value is not None and not isinstance(value, self.field.rel.to):
            print('skipping')
            #raise ValueError(
            #    'Cannot assign "%r": "%s.%s" must be a "%s" instance.' % (
            #        value,
            #        instance._meta.object_name,
            #        self.field.name,
            #        self.field.rel.to._meta.object_name,
            #    )
            #)
    
    ...
    

Should this exception be raised in the first place? Is this a bug in Django or
am I doing it wrong?


Answer: **Edit** :

The solution below does not actually work; it just ends up not running the
forwards_func so there are no errors. New solutions welcome:

* * *

I was able to resolve this using [Django's post_migrate
signal](https://docs.djangoproject.com/en/1.7/ref/signals/#post-migrate).

This also fixes some other issues that arrive from these types of migrations
(data migrations which reference the ContentType table).

My understanding is that essentially, the issue is the ContentType table
doesn't get created until the very end of a migration for performance reasons.
This means I'm not actually retrieving the same type ContentType object that
the relation module is checking for.

The solution is to run this type of data migration as a callback:

    
    
    # -*- coding: utf-8 -*-
    from django.db.models.signals import post_migrate
    from django.contrib.contenttypes.models import ContentType
    from django.db import models, migrations
    
    def forwards_func(apps, schema_editor):
        Restaurant = apps.get_model("simpleapp", "Restaurant")
        House = apps.get_model("simpleapp", "House")
        Place = apps.get_model("simpleapp", "Place")
    
        db_alias = schema_editor.connection.alias
    
        def add_stuffs(*args, **kwargs)
            content_type = ContentType.objects.using(db_alias).get(
                app_label="simpleapp",
                model="restaurant"
            )
    
            for restaurant in Restaurant.objects.using(db_alias).all():
                Place.objects.using(db_alias).create(
                    content_type=content_type,
                    object_id=restaurant.id)
    
            content_type = ContentType.objects.using(db_alias).get(
                app_label="simpleapp",
                model="house"
            )
            for house in House.objects.using(db_alias).all():
                Place.objects.using(db_alias).create(
                   content_type=content_type,
                   object_id=house.id)
    
        post_migrate.connect(add_stuffs)
    
    
    class Migration(migrations.Migration):
    
        dependencies = [
            ('simpleapp', '0010_place')
        ]
    
        operations = [
            migrations.RunPython(
                forwards_func,
            ),
        ]
    

