【发布时间】:2019-04-02 01:30:22
【问题描述】:
我是 Django 的新用户,我正在尝试弄清楚如何创建一个可以支持多种(类型)元素的模型。
这是情节:我想在我的应用程序上创建一个博客模块。 为此,我创建了一个模型 Page,它描述了一个博客页面。还有一个模型 PageElement,用于描述博客上的帖子。每个 Page 可以包含许多 PageElement。
PageElement 可以有多种类型,因为我希望我的用户可以像短文本、视频或图片一样发布。我还希望(例如)用户可以发布对另一个模型的引用(例如对用户的引用)。根据用户发布的内容类型,HTML 页面将以不同的方式显示每个 PageElement。
但我不知道声明 PageElement 类以支持所有这些情况的正确方法是什么:(
这是我的页面模型:
class Page(models.Model):
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
# Basical informations
title = models.CharField(max_length=150)
description = models.TextField(blank=True)
# Foreign links
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
related_name='pages_as_user'
)
created_at = models.DateTimeField(default=timezone.now)
# Other fields ....
class Meta:
indexes = [
models.Index(fields=['uuid']),
models.Index(fields=['user', 'artist'])
]
目前,我有两个解决方案,第一个使用继承:当您在博客上创建新帖子时,您创建一个继承自 PageElement 模型的元素。以下是我针对每种情况的不同模型:
class PageElement(models.Model):
page = models.ForeignKey(
Page,
on_delete=models.CASCADE,
related_name='%(class)s_elements'
)
updated_at = models.DateTimeField(default=timezone.now)
created_at = models.DateTimeField(default=timezone.now)
class PageImageElement(PageElement):
image = models.ImageField(null=True)
image_url = models.URLField(null=True)
class PageVideoElement(PageElement):
video = models.FileField(null=True)
video_url = models.URLField(null=True)
class PageTextElement(PageElement):
text = models.TextField(null=True)
class PageUserElement(PageElement):
user = models.ForeignKey(
'auth.User',
on_delete=models.CASCADE,
related_name='elements'
)
如果我必须使用“纯”Python,这个解决方案将是我选择的解决方案。因为我可以将每个 PageElement 存储在字典中并按类过滤它们。而且这个解决方案在未来可以很容易地扩展到新的内容类型。
但是对于 Django 模型。似乎这不是最好的解决方案。因为从数据库中获取所有的PageElement子元素真的很难(我不能只写“page.elements”来获取所有类型的所有元素,我需要获取所有%(class)s_elements 元素手动连接它们:/)。我已经考虑过类似下面的解决方案(我还没有尝试过),但对于这个问题(以及必须处理大量请求的数据库)来说,这似乎有点过头了:
class Page(models.Model):
# ...
def get_elements(self):
# Retrieve all PageElements children linked to the current Page
R = []
fields = self._meta.get_fields(include_hidden=True)
for f in fields:
try:
if '_elements' in f.name:
R += getattr(self, f.name)
except TypeError as e:
continue
return R
我的第二个“解决方案”使用一个独特的类,其中包含我需要的所有字段。根据我要创建的 PageElement 类型,我会将 type 字段设置为正确的值,将值放入相应的字段中,然后设置为 NULL 所有其他未使用的字段:
class PageElement(models.Model):
page = models.OneToOneField(
Page,
on_delete=models.CASCADE,
related_name='elements'
)
updated_at = models.DateTimeField(default=timezone.now)
created_at = models.DateTimeField(default=timezone.now)
TYPES_CHOICE = (
('img', 'Image'),
('vid', 'Video'),
('txt', 'Text'),
('usr', 'User'),
)
type = models.CharField(max_length=60, choices=TYPES_CHOICE)
# For type Image
image = models.ImageField(null=True)
image_url = models.URLField(null=True)
# For type Video
video = models.FileField(null=True)
video_url = models.URLField(null=True)
# For type Text
text = models.TextField(null=True)
# For type User
user = models.ForeignKey(
'auth.User',
on_delete=models.CASCADE,
related_name='elements',
null=True
)
使用此解决方案,我可以使用“page.elements”在单个请求中检索所有元素。但它的可扩展性不如前一个(我需要修改整个表结构以添加新字段或新元素)。
老实说,我完全不知道哪种解决方案是最好的。而且我确信存在其他(更好的)解决方案,但我糟糕的面向对象技能无法让我有能力考虑它们(:()...
我想要一个将来可以轻松修改的解决方案(例如,如果我想在博客上添加一个新的类型“日历”,它引用一个日期时间)。如果我想检索与页面相关的所有元素,这将很容易在我的应用程序中使用......
感谢您的关注:)
【问题讨论】:
-
您可以创建
text、imagevideo等模型。现在创建您的页面模型,将带有外键的文本添加到模型文本、带有模型图像的图像等等。如果您想添加 gif,您可以将其添加到您的图像模型中,并且您的页面模型不需要任何更改。 -
我不太明白你的解决方案:/你有例子吗?
-
我觉得你可以考虑动态字段问题的解决方案stackoverflow.com/questions/7933596/django-dynamic-model-fields
-
__Each Page can contain many PageElement__,根据您的模型,这并不令人满意,但我真的无法理解您的问题。请你写一个问题陈述。 -
第一点:它不像关系模型 (SQL) 那样是 OO 问题——Django 模型实际上只是关系数据库的薄包装器,您必须处理它。第二点:这并不是一个真正的新问题,大多数 CMS 都必须解决这个问题,所以你可能想检查一下他们是怎么做的(你可以从这里开始:djangopackages.org/grids/g/cms - 至少 django-cms、feincms 和 django-fluent-pages有类似“一个页面由元素组成”的设计)。