【问题标题】:Create a Blog which support multiple type of post创建一个支持多种类型帖子的博客
【发布时间】: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”在单个请求中检索所有元素。但它的可扩展性不如前一个(我需要修改整个表结构以添加新字段或新元素)。

老实说,我完全不知道哪种解决方案是最好的。而且我确信存在其他(更好的)解决方案,但我糟糕的面向对象技能无法让我有能力考虑它们(:()...

我想要一个将来可以轻松修改的解决方案(例如,如果我想在博客上添加一个新的类型“日历”,它引用一个日期时间)。如果我想检索与页面相关的所有元素,这将很容易在我的应用程序中使用......

感谢您的关注:)

【问题讨论】:

  • 您可以创建textimage video 等模型。现在创建您的页面模型,将带有外键的文本添加到模型文本、带有模型图像的图像等等。如果您想添加 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有类似“一个页面由元素组成”的设计)。

标签: python django oop


【解决方案1】:

我不确定它是否适合您的问题,但在这种情况下使用 GenericForeignKeys/ContentType framework 可能是合适的。当一个人掌握了这个概念时,它是非常强大的。

示例构造:

class Page(models.Model):
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    page_element = GenericForeignKey('content_type', 'object_id')
    ...

您现在可以通过 GenericFK 将任何模型对象连接到 Page 模型。因此,在稍后阶段添加新类型(作为新模型)不会造成干扰。

更新:

正如评论指出的那样,这种结构不能很好地支持许多 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')

一个页面可以有许多“抽象”页面元素,而 content_object 是“具体的页面元素模型/实现”。 轻松检索特定页面的所有元素,并允许检查 ContentType 以检查元素类型等。

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

【讨论】:

  • 这是正确的做法。特别是generic relations 是您想要的。
  • 我认为元素应该是页面的外键,而不是相反,因为我们想要定义每个页面包含多个模块,这些模块对于该特定页面是唯一的。在您提出的解决方案中,几个页面可能会指向同一个 PageVideoElement 或 PageImageElement 模块。如此不同的帖子,可能包含相同的视频或图像,绝对不是我们想要的结果。
  • 当我回到办公室时,我会看看它。但是您的解决方案似乎真的很好:)。谢谢
【解决方案2】:

要在 Django 中建立 PagePageElement 之间的关系,您宁愿使用外键关系,而不是继承。

class PageImageElement(PageElement):
    page = models.ForeignKey(Page,
                             on_delete=models.CASCADE,
                             related_name='images')
    image = models.ImageField(null=True)
    image_url = models.URLField(null=True)

每个用户的帖子都会创建一个Page 的实例。每次向页面添加图像都会创建一个PageImageElement 的实例,您可以使用相关名称查询它们。这样可以很容易地访问单个Page的所有视频、图像、文本模块。

在相关的说明中,我想说PageElement 类可以是抽象的see the docs,如果您声明字段可能包含null 值,如video = models.FileField(null=True) 中那样,那么它可能也值得声明blank=True , 否则在创建这些字段未定义的对象时会出错。例如,在这里讨论过:differentiate null=True, blank=True in django

【讨论】:

    【解决方案3】:

    我不能只写“page.elements”来获取所有类型的所有元素

    实际上,如果您使用multi-table inheritance,您可以。问题是返回的所有记录都是PageElement 的实例,这意味着您丢失了子类类型的所有信息以及这些子对象可能包含的附加数据。
    有很多包可以解决这个多态性问题: django packages: Model inheritance

    【讨论】:

      猜你喜欢
      • 2021-07-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-17
      • 1970-01-01
      • 2015-03-24
      • 2014-01-03
      相关资源
      最近更新 更多