我有以下型号:
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) 几乎正是我要找的,但它是针对管理区域的,我在前端需要它。>
答案 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)
鉴于类别相当静态,您不希望您的用户选择类别。类别本身应该是标签,而不是供用户选择的字段。
您在评论中提到,标签有时会改变。我想在决定如何在这里进行之前,我会问两个问题:
如果更改标签的人对 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()
这有很多优点:
verbose_name
和 help_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 点(以上)很重要,请想象以下情况:
如果 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