如何在Django中无关性地链接任何对象/模型?

2 投票
3 回答
2237 浏览
提问于 2025-04-15 12:07

我正在用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 个回答

2

你想要在一个通用的模板上显示一些特定对象的内容,对吧?

为了同时支持模型和其他对象,我们需要两个中间模型;一个用来处理字符串,另一个用来处理模型。虽然我们可以用一个模型来实现,但这样效率会低一些。这些模型将作为内容和字符串/模型之间的桥梁。

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() 方法提供的模板。我现在时间有点紧,所以先说到这里,稍后会更新,大约一个小时后。如果你觉得这个方法不错,请告诉我。

2

使用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_typeobject_id

我把模型叫做Block,但是如果你有一些情况是多个独特的(object, key)组合映射到同一个块,那么实际上可能需要一个中间模型,这个模型本身有一个ForeignKey指向你的实际Block模型,或者指向像Van Gale提到的那些辅助应用中的合适模型。

6

django-tagging 使用了 Django 的 内容类型 框架。这个框架的官方文档解释得比我好,但简单来说,它就是“一个通用的外键,可以指向任何其他模型”。

这可能正是你想要的,但从你的描述来看,听起来你也想做一些和其他现有项目非常相似的事情:

  • django-flatblocks(“... 像 django.contrib.flatpages,但用于页面的部分;比如你想在主要内容旁边显示的可编辑帮助框。”)

  • django-better-chunks(“可以把它想象成是 flatpages,用于你可能想插入到模板中的小块可重用内容,并且可以从管理界面进行管理。”)

等等。如果这些项目相似的话,它们会是一个很好的起点。

撰写回答