【问题标题】:Conditional form display logic with Django ModelForm使用 Django ModelForm 的条件表单显示逻辑
【发布时间】:2014-06-05 20:27:24
【问题描述】:

我想我可以用一种非常丑陋的方式来解决这个问题,其中包含许多数据库查询和大量可怕的 jQuery,但我希望有一个更简单的方法。

我正在为 Django 网站构建一个小型分类应用程序。网站上有各种媒体,它们被分类为属于某种类型、子类型、子子类型和子子子类型。例如,一些媒体可能被分类为音乐(流派)、布鲁斯(子流派)、孟菲斯蓝调(子子流派)、福音蓝调(第二子子流派)和第 32 街班卓蓝调(子子子流派,这个是编的)。

Genre、SubGenre、SubSubGenre 和 SubSubSubGenre 都是模型:

class Genre(models.Model):

    description = models.CharField(max_length=200, unique=True)
    slug = models.SlugField(unique=True)

    def __unicode__(self):
        return self.description


class SubGenre(models.Model):

    parent = models.ForeignKey(Genre, related_name='sub_genre')
    description = models.CharField(max_length=200, unique=True)
    slug = models.SlugField(unique=True)

    def __unicode__(self):
        return self.description


class SubSubGenre(models.Model):

    parent = models.ForeignKey(SubGenre, related_name='sub_sub_genre')    
    description = models.CharField(max_length=200, unique=True)
    slug = models.SlugField(unique=True)

    def __unicode__(self):
        return self.description


class SubSubSubGenre(models.Model):

    parent = models.ForeignKey(SubSubGenre, related_name='sub_sub_sub_genre')
    description = models.CharField(max_length=200, unique=True)
    slug = models.SlugField(unique=True)

    def __unicode__(self):
        return self.description

添加到网站的每条媒体都有一个 MTM 字段,将其与这些限定符中的一个或多个相关联,如下所示:

class Media(models.Model):
    genres = models.ManyToManyField(Genre)
    sub_genres = models.ManyToManyField(SubGenre)
    sub_sub_genres = models.ManyToManyField(SubSubGenre)
    sub_sub_sub_genres = models.ManyToManyField(SubSubSubGenre)
    #other fields

我们网站上的每个用户都有一个用户个人资料(使用一对一关系扩展基本用户模型)。在该用户个人资料中,他们可以告诉我们他们喜欢使用哪种类型的媒体。

class UserProfile(models.Model):
    user = models.OneToOneField(User)
    preferred_genres = models.ManyToManyField(Genre, related_name='genre_pref')
    preferred_sub_genres = models.ManyToManyField(SubGenre, related_name = 'subgenre_pref')
    preferred_sub_sub_genres = models.ManyToManyField(SubSubGenre, related_name = 'subsubgenre_pref')
    preferred_sub_sub_sub_genres = models.ManyToManyField(SubSubSubGenre, related_name = 'subsubsubgenre_pref')

我想创建两种表单:一种用于创建新的用户配置文件,另一种用于创建新的媒体。用户应该能够定义他们喜欢的流派、子流派等。媒体上传者应该能够以相同的方式对其媒体进行分类。

我们只有几个流派,但每个流派都有十几个或更多子流派。每个子流派有 20 多个子子流派。大多数子子流派有 20 多个子子子流派。我不能一次将所有这些选项都扔到页面上——这太可怕了。

这就是我希望发生的事情。以“新用户资料表格”为例。我不想让用户一次设置他们喜欢的流派、子流派、子子流派和子子子流派,我希望只有一个多选表单,用户可以在其中设置他们的首选流派。 Then, when the preferred genre is selected (but before the form is submitted), sub-genre choices specific to that genre appear.选择子流派后,以该子流派为父的子子流派将显示为选项等。

例如,假设我们在系统中定义了三种类型:音乐、书籍和播客。我们的新用户遇到了设置他们喜欢的类型的问题,并单击了音乐和书籍的复选框,但将 Podcast 留空。我希望该用户能够为 Music 和 Book 设置他们的首选子流派,并且 CheckboxSelectMultiple 字段仅填充以 Music 或 Book 作为父级的子流派(Music.subgenre_set .all() 或 Book.subgenre_set.all() 将返回适当的选择)。

再举一个例子,假设我们有一个上传播客的用户。在上传表单上,他们遇到了询问媒体属于什么类型的问题。用户选中“播客”框。现在我们想知道媒体属于哪些子类型。我们如何仅使用具有父“播客”的子类型填充 CheckboxSelectMultiple 字段?

在任一示例中,如果“Podcast”是选择的类型,则表单下一部分的适当子类型选择将由 Podcast.subgenre_set.all() 表示。如果该播客的子流派之一是“非小说类”,那么选择该子流派还应调出适当的子子流派选择:Nonfiction.subsubgenre_set.all()。我不能只是硬编码流派、子流派、子子流派和子子子流派名称,但是因为将来肯定会添加新的名称,这将造成扩展的噩梦。

我也觉得只是在幕后“加载”每一个可能的whatever_set,然后用javascript 技巧揭开它会给数据库带来很大的压力。

如果我将表单分散到多个页面(以及对数据库的多个查询),这很容易。但是如何在不破坏数据库的情况下以一种形式完成所有这些操作?

感谢您阅读所有这些内容!

【问题讨论】:

    标签: python django


    【解决方案1】:

    首先,我会重新考虑您如何为您的流派建模。我认为更好的解决方案是只有一个模型Genre,而不是为每个SubGenre 指定新模型。这还允许您扩展到任意深度,而无需更改模型结构。

    class Genre(models.Model):
        parent = models.ForeignKey(Genre, null=True, related_name='sub_genre')
        description = models.CharField(max_length=200, unique=True)
        slug = models.SlugField(unique=True)
    
        def __unicode__(self):
            return self.description
    
        @property
        def is_top_level(self):
            return bool(self.parent)
    

    然后我会使用 Form 而不是 ModelForm 像这样:

    class GenreForm(forms.Form):
        genre = forms.ModelChoiceField(
            queryset=Genre.objects.filter(parent__is_null=True))
        sub_genre = forms.ModelChoiceField(
            queryset=Genre.objects.filter(
                parent__is_null=False, parent__parent__is_null=True))
    

    对于前端,我认为最简单的解决方案是从给每个 sub_genre 选项一个 class='hidden' 开始,然后使用简单的 javascript sn-p 在选择父项时取消隐藏后续字段的选项。

    如果您有大量的流派,您可能会发现在页面加载时间方面最有效的方法是使用 ajax 填充后续字段。

    希望对你有帮助!

    【讨论】:

    • 我非常喜欢您关于设置流派模型的建议。我已经厌倦了到处输入 subsubsubsubsubsubsub 。它仍然允许我捕获我想要的层次结构,同时大大简化了事情。对于我们正在谈论的数字,我认为 ajax 是要走的路。老实说,我不知道关于 ajax 的第一件事,除了它似乎是我最终需要的解决方案,所以我需要开始学习。
    【解决方案2】:

    首先,我认为你的模型太多了。所有模型都具有完全相同的字段,因此最好只使用一个模型(称为分类),其中包含名称、描述、父级和级别:因此 Genre 将具有级别 0 并且没有父级,SubGenre 将具有级别 1 和级别 0 的父级,依此类推。 (您也可以使用 MPTT 之类的方法来执行此操作,但这可能有点矫枉过正,因为排序并不重要。)

    不过,要回答您的问题,我认为 Javascript 是要走的路,但不是通过预加载所有内容。相反,您应该使用 Ajax:在选择流派时,您向返回相关子流派的服务器发出 Ajax 请求。由于这是一个轻量级的请求,因此用户应该不会注意到延迟,因此您可以构建复选框或其他任何内容供用户选择。

    【讨论】:

    • 我希望我可以为这两个答案打勾。感谢您抽出宝贵时间阅读并回复此内容。 Ajax 对我来说是全新的东西,所以我还没有任何明智的问题。我需要好好学习。再次感谢您!
    猜你喜欢
    • 1970-01-01
    • 2021-06-23
    • 1970-01-01
    • 2021-06-15
    • 2012-07-30
    • 1970-01-01
    • 2015-10-03
    • 2012-06-15
    • 2013-11-13
    相关资源
    最近更新 更多