我是Django的新用户,我正在试图弄清楚如何创建一个可以支持许多种(类型)元素的模型.
这是情节:我想在我的应用程序上创建一个Blog模块.要做到这一点,我创建了一个模型页,描述一个博客页面.还有一个模型PageElement,用于描述博客上的帖子.每个页面都可以包含许多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'])
]
Run Code Online (Sandbox Code Playgroud)
目前,我有两个解决方案,第一个使用继承:当您在博客上创建新帖子时,您创建一个继承自PageElement模型的Element.以下是我针对每种情况的不同模型:
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'
)
Run Code Online (Sandbox Code Playgroud)
如果我必须使用"纯"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
Run Code Online (Sandbox Code Playgroud)
我的第二个"解决方案"使用一个包含我需要的所有字段的唯一类.根据我想要创建的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
)
Run Code Online (Sandbox Code Playgroud)
使用此解决方案,我可以使用"page.elements"检索单个请求中的所有元素.但它不像前一个那样可扩展(我需要修改我的整个表结构以添加新字段或新类型的元素).
说实话,我绝对不知道哪种解决方案是最好的.而且我确信其他(更好)的解决方案存在,但是我的可怜的Oriented-Object技能并没有让我能够思考它们(:()......
我想要一个可以在将来轻松修改的解决方案(例如,我想在Blog上添加一个新的类型"日历",它引用一个DateTime).如果我想要检索与页面相关的所有元素,那么在我的应用程序中这将很容易使用...
感谢您的关注 :)
我不确定它是否适合您的问题,但在这种情况下使用GenericForeignKeys/ContentType 框架可能合适。当一个人掌握了这个概念时,它是非常强大的。
构造示例:
class Page(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
page_element = GenericForeignKey('content_type', 'object_id')
...
Run Code Online (Sandbox Code Playgroud)
您现在可以通过 GenericFK 将任何模型对象连接到页面模型。因此,在稍后阶段添加新类型(作为新模型)并不具有侵入性。
更新:
正如评论指出的那样,此构造不能以良好的方式支持页面的许多 PageElements。
详细来说,解决该问题的一种方法是仍然利用 GenericFK...
class PageElement(models.Model):
class Meta:
unique_together=('page', 'content_type', 'object_id') # Solve the unique per page
page = models.ForeignKey(Page, related_name='page_elements')
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
Run Code Online (Sandbox Code Playgroud)
一个页面可以有许多“抽象”PageElement,而 content_object 是“具体 PageElement 模型/实现”。轻松检索特定页面的所有元素,并允许检查 ContentType 以检查元素的类型等。
这只是解决这个特定问题的众多方法中的一种。
| 归档时间: |
|
| 查看次数: |
187 次 |
| 最近记录: |