创建一个支持多种帖子的博客

gra*_*lle 5 python oop django

我是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).如果我想要检索与页面相关的所有元素,那么在我的应用程序中这将很容易使用...

感谢您的关注 :)

Dan*_*man 2

我不确定它是否适合您的问题,但在这种情况下使用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 以检查元素的类型等。

这只是解决这个特定问题的众多方法中的一种。