如何在Django中无关性地链接任何对象/模型?
我正在用Django写一个简单的内容管理系统(CMS)。大多数内容管理系统都需要有一个固定的页面,固定的URL,并使用一个有一个或多个可编辑区域的模板。要有一个可编辑区域,你需要一个页面。为了让系统知道哪个页面,你需要URL。
问题在于,当你不再处理“页面”(无论是FlatPages页面还是其他类型的页面),而是处理另一个模型的实例时,就会出现麻烦。举个例子,如果我有一个产品模型,我可能想创建一个详细页面,其中有多个可编辑区域。
我可以把这些区域直接放到模型里,但在我的情况下,有几个模型,而且我想展示的数据量差别很大。
因此,我想在模板层面构建CMS,并根据“页面”的实例或它使用的模型来指定什么是一个块(可编辑区域)。
我想到一个主意,也许我可以在页面上放一些自定义模板标签,像这样:
{% block unique_object "unique placeholder name" %}
这样就可以根据传入的两个参数找到一个“块”。举个例子:
<h1>{{ product_instance.name }}</h1>
{% block product_instance "detail: product short description" %}
{% block product_instance "detail: product video" %}
{% block product_instance "detail: product long description" %}
听起来不错,对吧?但我遇到的问题是,如何为一个区域创建一个“键”,以便我可以提取正确的块?我将处理一个完全未知的对象(它可能是一个“页面”对象、一个URL、一个模型实例,甚至可能是一个船</fg>
)。
其他Django微应用也必须这样做。你可以用django-tagging给任何东西打标签,对吧?我试着理解它是怎么工作的,但我还是搞不懂。
所以,首先,我是不是疯了?假设我没有,而且这个想法看起来相对合理,我应该如何将一个对象+字符串链接到一个块/可编辑区域呢?
注意:编辑将在页面上进行,所以让用户编辑这些区域并没有什么问题。我不需要在后台做任何复杂的操作。我的最终梦想是允许第三个参数来指定这是什么类型的内容区域(文本、图片、视频等)。如果你对这些有任何意见,我很乐意听取!
3 个回答
你想要在一个通用的模板上显示一些特定对象的内容,对吧?
为了同时支持模型和其他对象,我们需要两个中间模型;一个用来处理字符串,另一个用来处理模型。虽然我们可以用一个模型来实现,但这样效率会低一些。这些模型将作为内容和字符串/模型之间的桥梁。
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
CONTENT_TYPE_CHOICES = (
("video", "Video"),
("text", "Text"),
("image", "Image"),
)
def _get_template(name, type):
"Returns a list of templates to load given a name and a type"
return ["%s_%s.html" % (type, name), "%s.html" % name, "%s.html" % type]
class ModelContentLink(models.Model):
key = models.CharField(max_length=255) # Or whatever you find appropriate
type = models.CharField(max_length=31, choices= CONTENT_TYPE_CHOICES)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
object = generic.GenericForeignKey('content_type', 'object_id')
def get_template(self):
model_name = self.object.__class__.__name__.lower()
return _get_template(model_name, self.type)
class StringContentLink(models.Model):
key = models.CharField(max_length=255) # Or whatever length you find appropriate
type = models.CharField(max_length=31, choices= CONTENT_TYPE_CHOICES)
content = models.TextField()
def get_template(self):
return _get_template(self.content, self.type)
现在,我们只需要一个模板标签来获取这些内容,然后尝试加载模型的 get_template() 方法提供的模板。我现在时间有点紧,所以先说到这里,稍后会更新,大约一个小时后。如果你觉得这个方法不错,请告诉我。
使用contenttypes框架来实现你所描述的查找策略其实很简单:
class Block(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
object = generic.GenericForeignKey() # not actually used here, but may be handy
key = models.CharField(max_length=255)
... other fields ...
class Meta:
unique_together = ('content_type', 'object_id', 'key')
def lookup_block(object, key):
return Block.objects.get(content_type=ContentType.objects.get_for_model(object),
object_id=object.pk,
key=key)
@register.simple_tag
def block(object, key)
block = lookup_block(object, key)
... generate template content using 'block' ...
需要注意的一点是,你不能在Block.objects.get
调用中使用object
字段,因为这并不是一个真正的数据库字段。你必须使用content_type
和object_id
。
我把模型叫做Block
,但是如果你有一些情况是多个独特的(object, key)
组合映射到同一个块,那么实际上可能需要一个中间模型,这个模型本身有一个ForeignKey
指向你的实际Block
模型,或者指向像Van Gale提到的那些辅助应用中的合适模型。
django-tagging 使用了 Django 的 内容类型 框架。这个框架的官方文档解释得比我好,但简单来说,它就是“一个通用的外键,可以指向任何其他模型”。
这可能正是你想要的,但从你的描述来看,听起来你也想做一些和其他现有项目非常相似的事情:
django-flatblocks(“... 像 django.contrib.flatpages,但用于页面的部分;比如你想在主要内容旁边显示的可编辑帮助框。”)
django-better-chunks(“可以把它想象成是 flatpages,用于你可能想插入到模板中的小块可重用内容,并且可以从管理界面进行管理。”)
等等。如果这些项目相似的话,它们会是一个很好的起点。