Django ReverseSingleRelatedObjectDescriptor.__set__ ValueError
Posted
技术标签:
【中文标题】Django ReverseSingleRelatedObjectDescriptor.__set__ ValueError【英文标题】: 【发布时间】:2015-05-21 20:04:39 【问题描述】:我正在创建一个自定义数据迁移,以根据跨两个不同模型的现有条目在数据库中自动创建 GenericRelation 条目。
示例模型.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)
示例 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,
),
]
当我运行这个(Django 1.7.4)时,我得到了
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.
如果我在 Django 模块 (django.db.models.fields.related.ReverseSingleRelatedObjectDescriptor.set) 中注释掉引发值错误的节,它会按预期工作:
...
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,
# )
#)
...
应该首先提出这个异常吗?这是 Django 中的错误还是我做错了?
【问题讨论】:
我不明白你在评论什么。请问你能显示完整的回溯吗? 【参考方案1】:编辑:
下面的解决方案实际上不起作用;它只是最终没有运行 forwards_func 所以没有错误。欢迎新的解决方案:
我可以使用Django's post_migrate signal 解决这个问题。
这还解决了这些类型的迁移(引用 ContentType 表的数据迁移)带来的一些其他问题。
我的理解是,本质上,问题是出于性能原因,直到迁移结束时才会创建 ContentType 表。这意味着我实际上并没有检索关系模块正在检查的相同类型的 ContentType 对象。
解决方案是将此类数据迁移作为回调运行:
# -*- 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,
),
]
【讨论】:
【参考方案2】:貌似和这个 Django bug/wontfix有点关系
在我的迁移中尝试为用户组重新分配权限时,我遇到了类似的错误。要修复它,我必须按照here 的描述手动发出 post_migrate 信号
这是我的迁移代码示例:
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations
from django.core.management.sql import emit_post_migrate_signal
def trigger_post_migrate(db_alias):
try:
# Django 1.9
emit_post_migrate_signal(2, False, db_alias)
except TypeError:
# Django < 1.9
try:
# Django 1.8
emit_post_migrate_signal(2, False, 'default', db_alias)
except TypeError:
# Django < 1.8
emit_post_migrate_signal([], 2, False, 'default', db_alias)
def up(apps, schema_editor):
# trigger post migrate to make sure all permissions and contenttypes are in place
trigger_post_migrate(schema_editor.connection.alias)
# migration code here...
class Migration(migrations.Migration):
dependencies = [
('accounts', '0008_auto_20160712_0608'),
]
operations = [
migrations.RunPython(up),
]
【讨论】:
以上是关于Django ReverseSingleRelatedObjectDescriptor.__set__ ValueError的主要内容,如果未能解决你的问题,请参考以下文章