Django 形式的多个模型

时间:2021-01-24 12:23:18

标签: python django

我有以下型号:

class Category(models.Model):
    label = models.CharField(max_length=40)
    description = models.TextField()


class Rating(models.Model):
    review = models.ForeignKey(Review, on_delete=models.CASCADE)
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    rating = models.SmallIntegerField()


class Review(models.Model):
    author = models.ForeignKey(User, related_name="%(class)s_author", on_delete=models.CASCADE)
    coach = models.ForeignKey(User, related_name="%(class)s_coach", on_delete=models.CASCADE)
    comments = models.TextField()

我想创建一个前端表单,允许用户评论教练,包括对一些预填充类别的评级。

在我的脑海中,表单看起来像:

 Coach: _______________         # Selection of all coach users from DB, this works as standard

 Category: "Professionalism"    # These would be DB entries from the Category model
 Rating: _ / 5

 Category: "Friendliness"
 Rating: _ / 5

 Category: "Value"
 Rating: _ / 5

 Comments:
 _________________________________
 _________________________________

 Submit

我在文档中看到了 Django Formsets,但这些似乎存在用于从同一模型创建多个表单作为单个表单?

不是在寻找完整的答案,但如果有人能指出我正确的方向,我将不胜感激。

编辑:Vineet 的答案 (https://stackoverflow.com/a/65883875/864245) 几乎正是我要找的,但它是针对管理区域的,我在前端需要它。

3 个答案:

答案 0 :(得分:1)

在您应用的 admin.py 文件中使用它

from django.contrib import admin

from .models import Review, Rating, Category

class RatingInline(admin.TabularInline):
    model = Rating
    fieldsets = [
        ('XYZ', {'fields': ('category', 'rating',)})
    ]
    extra = 0
    readonly_fields = ('category',)
    show_change_link = True

    def has_change_permission(self, request, obj=None):
        return True


class ReviewAdmin(admin.ModelAdmin):
    fields = ('author', 'coach', 'comments')
    inlines = [RatingInline]


admin.site.register(Review, ReviewAdmin)
admin.site.register(Rating)
admin.site.register(Category)

您的管理页面将如下所示: Best practice for reading data from Kafka to AWS Redshift

答案 1 :(得分:1)

鉴于类别相当静态,您不希望您的用户选择类别。类别本身应该是标签,而不是供用户选择的字段。

您在评论中提到,标签有时会改变。我想在决定如何在这里进行之前,我会问两个问题:

  1. 谁将继续更新标签(他们是否具有基本的编码能力,或者他们是否依赖于使用管理员之类的工具)。
  2. 当标签改变时,它们的基本含义会改变还是只是措辞

考虑 1

如果更改标签的人对 django 有基本的了解,并拥有适当的权限(或者可以要求开发人员为他们进行更改),那么首先硬编码这 5 件事可能是最好的方法:

class Review(models.Model):
    author = models.ForeignKey(User, related_name="%(class)s_author", on_delete=models.CASCADE)
    coach = models.ForeignKey(User, related_name="%(class)s_coach", on_delete=models.CASCADE)
    comments = models.TextField()
    # Categories go here...
    damage = models.SmallIntegerField(
        help_text="description can go here", 
        verbose_name="label goes here"
    )
    style = models.SmallIntegerField()
    control = models.SmallIntegerField()
    aggression = models.SmallIntegerField()

这有很多优点:

  • 这是一张非常简单易懂的表,而不是 3 个带连接的表。
  • 这将使您的代码库上下的一切变得更简单。它会使当前情况(管理表单)更容易,但也会使您编写的每个查询、视图、模板、报表、管理命令等更容易,向前推进。
  • 您可以在需要时使用 verbose_namehelp_text 编辑标签和说明。

如果像这样更改代码不是一个选项,并且必须通过 django admin-app 之类的东西设置标签,那么外键是您前进的唯一途径。

同样,您并不真的希望您的用户选择类别,所以我只是将它们动态添加为字段,而不是使用表单集:

class Category(models.Model):
    # the field name will need to be a valid field-name, no space etc.
    field_name = models.CharField(max_length=40, unique=True)
    label = models.CharField(max_length=40)
    description = models.TextField()
class ReviewForm.forms(forms.Form):
    coach = forms.ModelChoiceField()

    def __init__(self, *args, **kwargs):
        return_value = super().__init__(*args, **kwargs)

        # Here we dynamically add the category fields
        categories = Categories.objects.filter(id__in=[1,2,3,4,5])
        for category in categories:
            self.fields[category.field_name] = forms.IntegerField(
                help_text=category.description,
                label=category.label,
                required=True,
                min_value=1,
                max_value=5
            )

        self.fields['comment'] = forms.CharField(widget=forms.Textarea)

        return return_value

由于(我假设)当前用户将是 review.author,您将需要访问 request.user,因此我们应该将所有新对象保存在视图中而不是表单中.您的观点:

def add_review(request):
    if request.method == "POST":
        review_form = ReviewForm(request.POST)
        if review_form.is_valid():
            data = review_form.cleaned_data
            # Save the review
            review = Review.objects.create(
                author=request.user,
                coach=data['coach']
                comment=data['comment']
            )
            # Save the ratings
            for category in Category.objects.filter(id__in=[1,2,3,4,5]):
                Rating.objects.create(
                    review=review
                    category=category
                    rating=data[category.field_name]
                )

            # potentially return to a confirmation view at this point
    if request.method == "GET":
        review_form = ReviewForm()

    return render(
        request, 
        "add_review.html", 
        {
             "review_form": review_form
        }
    )

考虑 2

要了解为什么第 2 点(以上)很重要,请想象以下情况:

  1. 您从 4 个类别开始:伤害、风格、控制和侵略。
  2. 您的网站上线并收到一些评论。假设教练 Tim McCurrach 获得的评论分别为 2、1、3、5。
  3. 几个月后,我们意识到“风格”不是一个非常有用的类别,因此我们将标签更改为“有效性”。
  4. 现在,Tim McCurrach 的评分为“1”,该类别以前带有“风格”标签,但现在带有“有效性”标签,这完全不是评论作者的意思。
  5. 您所有的旧数据都毫无意义。

如果 Style 只会改变为与 style 非常相似的东西,我们就不必太担心。

如果您确实需要更改标签的基本性质,我会在您的 active 模型中添加一个 Category 字段:

class Category(models.Model):
    field_name = models.CharField(max_length=40, unique=True)
    label = models.CharField(max_length=40)
    description = models.TextField()
    active = models.BooleanField()

然后在上面的代码中,我会写 Category.objects.filter(id__in=[1,2,3,4,5]) 而不是 Category.objects.filter(active=True)。老实说,我想无论哪种方式我都会这样做。在你的代码中硬编码 id 是不好的做法,而且很容易出错。无论如何,第二种方法更灵活。

答案 2 :(得分:0)

您可以“嵌入”inline formset 您的评论表单。然后您可以调用 :target { margin-top: 300px; } 一次性保存评论和所有相关评分。这是一个工作示例:

form.save()

如您所见,# forms.py from django import forms from . import models class ReviewForm(forms.ModelForm): class Meta: model = models.Review fields = '__all__' def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.ratings = forms.inlineformset_factory( parent_model=models.Review, model=models.Rating, extra=5, min_num=5, )( data=self.data if self.is_bound else None, files=self.files if self.is_bound else None, instance=self.instance, ) def is_valid(self): return super().is_valid() and self.ratings.is_valid() # AND def has_changed(self): return super().has_changed() or self.ratings.has_changed() # OR def save(self): review = super().save() self.ratings.save() return review 方法设置了属性 __init__(),您以后可以像这样在模板中调用它:

self.ratings

最后,您的 <form method="post"> {% csrf_token %} <div class="review"> {{ form.as_p }} </div> <div class="ratings"> {{ form.ratings.management_form }} {% for rating_form in form.ratings %} <div class="single_rating"> {{ rating_form.as_p }} </div> {% endfor %} </div> <button>Save</button> </form> 可能看起来像这样(使用 Django 的 class-based views):

views.py
相关问题