Django 管理后台 - OneToOneField 内联抛出“没有 ForeignKey”异常
我有一个非常简单的应用程序,目前定义了两个模型:一个叫“Content”,它只是用来存储内容数据;另一个叫“Page”,它包含了“Content”,并且是一个一对一的关系。
我这样做的原因是希望“Page”能作为一个具体的类来使用。当我计划在其他模块中需要页面数据时,可以简单地将“Content”作为一对一的关系来包含进来。我选择这种方式是为了避免使用继承,而是采用组合的方式。
models.py:
from django.db import models
class Content(models.Model):
"""Basic page data which can be used by other modules"""
title = models.CharField(max_length=200)
html_title = models.CharField(max_length=200)
meta_desc = models.CharField(max_length=200)
keywords = models.CharField(max_length=200)
content = models.TextField()
class Page(models.Model):
"""Concrete implementation of a basic page managed by the admin"""
slug = models.SlugField()
content = models.OneToOneField(Content)
def __str__(self):
return self.content.title
admin.py:
from django.contrib import admin
from content.models import Page, Content
class ContentInline(admin.TabularInline):
model = Content
fields = ('title', 'html_title', 'meta_desc', 'keywords', 'content')
class PageAdmin(admin.ModelAdmin):
fields = ('slug',)
inlines = [ContentInline]
在页面的管理界面上,我遇到了这个异常:
Exception at /admin/content/page/add/
<class 'content.models.Content'> has no ForeignKey to <class 'content.models.Page'>
它说的确实没错,但我似乎找不到实现我想要的方式,也就是在关系的非定义方包含一个内联。我不想在“Content”上声明这个关系,因为那样我就得在这个类里定义每一个与之相关的关系,这样会引入对其他模块的依赖,而我认为它不应该知道这些。
我使用的是Django 1.6和Python 3.3。
编辑:正如评论中提到的,我决定使用继承。我最初担心的是我想要能够灵活地从多个其他类组合类。然而,由于Django的ORM支持多重继承,如果我早知道这种方法叫做“mixins”(这是我刚接触Python时的事),我会更早找到解决方案。
模型的示例mixins:
from django.db import models
class Content(models.Model):
"""Basic page data which can be used by other modules"""
title = models.CharField(max_length=200)
html_title = models.CharField(max_length=200)
meta_desc = models.CharField(max_length=200)
keywords = models.CharField(max_length=200)
content = models.TextField()
def __str__(self):
return self.title
class Meta:
abstract = True
class Data(models.Model):
data_name = models.CharField(max_length=200)
class Meta:
abstract = True
class Page(Content, Data):
"""Concrete implementation of a basic page managed by the admin"""
slug = models.SlugField()
然后我可以在admin.py中将其作为一个模型使用。
2 个回答
如果你不想对你的模型做任何修改,可以使用一个叫做django_reverse_admin的Django模块,它可以让你在页面上直接显示那些不主要的部分。你可以在这里找到它:django_reverse_admin
你需要把django_reverse_admin添加到你的requirements.txt文件里:
-e git+https://github.com/anziem/django_reverse_admin.git#egg=django_reverse_admin
然后导入它:
在admin.py文件中:
from django.contrib import admin
from django_reverse_admin import ReverseModelAdmin
from content.models import Page, Content
# don't need to define an inline anymore for Content
class PageAdmin(ReverseModelAdmin):
fields = ('slug',)
inline_reverse = ['content']
inline_type = 'tabular' # or could be 'stacked'
另一种解决方案是把 OneToOneField
从 Content
移到 Page
。
class Content(models.Model):
"""Basic page data which can be used by other modules"""
title = models.CharField(max_length=200)
html_title = models.CharField(max_length=200)
meta_desc = models.CharField(max_length=200)
keywords = models.CharField(max_length=200)
content = models.TextField()
page = models.OneToOneField(Page, primary_key=True, related_name="content")
class Page(models.Model):
"""Concrete implementation of a basic page managed by the admin"""
slug = models.SlugField()
def __str__(self):
return self.content.title
这样你仍然可以使用 page.content
,并且内联表单会直接正常工作。
补充说明:
这种方法的一个缺点是,用户可以创建一个页面而不为其分配任何内容(这样的话 page.content
会出错)。
不过,解决这个问题非常简单,只需要创建一个自定义表单。
class ContentAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
kwargs["empty_permitted"] = False
super(ContentAdminForm, self).__init__(*args, **kwargs)
然后在管理页面中。
class ContentInline(admin.TabularInline):
model = Content
form = ContentAdminForm
fields = ('title', 'html_title', 'meta_desc', 'keywords', 'content')