Django表格集用于回复调查/问卷

2024-07-05 14:21:41 发布

您现在位置:Python中文网/ 问答频道 /正文

简而言之:我有一个用户输入训练的应用程序。在他们将练习添加到训练中之前,他们必须提交一份“准备就绪”问卷,其中包含5-10个问题,每个问题的评分都为低-高。我不知道如何在视图中列出所有的ReadinessQuestions,并让用户在表单集中提交每个问题的答案

模型:具有问题集的模型(ReadinessQuestion),以及存储问题、评分和它所属的训练的模型(WorkoutReadiness)

表单:我已将一个模型表单制作成一个formset_工厂。当我将“准备就绪问题”表单字段小部件设置为“只读”时,这会使我的表单无效并忽略我的“初始”数据

视图:我想创建一个视图,其中ReadinesQuestion中的5-10个问题位于一个列表中,并带有答案的下拉列表

  1. 我已经为我的表格设置了初始数据(每个不同的问题)
  2. 用户为每个问题选择他们想要的“评分”
  3. 将实例化一个新的训练
  4. 使用新训练的ForeignKey保存训练的领先性

我的页面现在看起来如何(几乎是我想要的样子):https://imgur.com/a/HD1l3oe

提交表格时出现错误:

Cannot assign "'Sleep'": "WorkoutReadiness.readiness_question" must be a "ReadinessQuestion" instance.

Django文档在这里非常糟糕,widget=forms.TextInput(attrs={'readonly':'readonly'})甚至不在Django官方网站上。如果我没有将准备就绪问题设置为“只读”,用户可以更改下拉列表(这是不可取的)

#models.py
class Workout(models.Model):
    user         = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
    date         = models.DateField(auto_now_add=True, null=True)
    date_created = models.DateTimeField(auto_now_add=True, null=True)
    def __str__(self):
        return f'{self.date}'


class ReadinessQuestion(models.Model):
    name = models.CharField(max_length=40, unique=True)
    def __str__(self):
        return f'{self.name}'


class WorkoutReadiness(models.Model):
    class Rating(models.IntegerChoices):
        LOWEST  = 1
        LOWER   = 2
        MEDIUM  = 3
        HIGHER  = 4
        HIGHEST = 5

    workout            = models.ForeignKey(Workout, on_delete=models.CASCADE, null=True, blank=True)
    readiness_question = models.ForeignKey(ReadinessQuestion, on_delete=models.CASCADE, null=True)
    rating             = models.IntegerField(choices=Rating.choices)
#forms.py
class WorkoutReadinessForm(forms.ModelForm):
    class Meta:
        model  = WorkoutReadiness
        fields = ['readiness_question', 'rating',]
    
    readiness_question = forms.CharField(
        widget=forms.TextInput(attrs={'readonly':'readonly'})
    )

WRFormSet = forms.formset_factory(WorkoutReadinessForm, extra=0)
#views.py
def create_workoutreadiness(request):
    questions = ReadinessQuestion.objects.all()
    
    initial_data = [{'readiness_question':q} for q in questions]
    
    if request.method == 'POST':
        formset = WRFormSet(request.POST)
        workout = Workout.objects.create(user=request.user)
        if formset.is_valid():
            for form in formset:
                cd = form.cleaned_data
                readiness_question = cd.get('readiness_question')
                rating = cd.get('rating')
                w_readiness = WorkoutReadiness(
                    workout=workout,
                    readiness_question=readiness_question,
                    rating=rating
                )
                w_readiness.save()

        return HttpResponseRedirect(reverse_lazy('routines:workout_exercise_list', kwargs={'pk':workout.id}))
    else:
        formset = WRFormSet(initial=initial_data)
    
                
    context = {
        'questions':questions,
        'formset':formset,
    }
    return render(request, 'routines/workout_readiness/_form.html', context)

或者:

我可以删除'readonly'属性并更改HTML的呈现方式。但是,这会导致formset.is_valid()为false,因为初始数据字段未填充screenshot of option 2

#forms.py
class WorkoutReadinessForm(forms.ModelForm):
    class Meta:
        model  = WorkoutReadiness
        fields = ['readiness_question', 'rating',]

WRFormSet = forms.formset_factory(WorkoutReadinessForm, extra=0)
#views.py
if request.method == 'POST':
        formset = WRFormSet(request.POST, initial=initial_data) ###CHANGED THIS####
        workout = Workout.objects.create(user=request.user)
        print(formset)
        if formset.is_valid():
#form.html
<table>
  <form action="" method="post">
    {% csrf_token %}
    {{ formset.management_form }}
    {% for form in formset %}
      <tr>
        <td>{{ form.initial.readiness_question }}</td> 
        <td>{{ form.rating }}</td> 
      </tr>
    {% endfor %}
    <button type="submit" class="btn btn-outline-primary">Submit</button>
  </form>
</table>

备选方案2:

理论上,我可以将我的训练主导性模型作为一个更广泛的模型,以5-10个问题作为列,每次训练1行。我知道这违背了整洁的数据建模原则,但我目前在Django看不到解决这一问题的方法。表单集限制了我的项目


Tags: 模型formtrue表单modelsrequestformsclass
1条回答
网友
1楼 · 发布于 2024-07-05 14:21:41

我设法解决了这个问题(使用备选方案1)。我不确定这是最好的方式,但它现在起作用了:

  1. #models.py-在WorkoutReadiness模型中的readiness_question中添加了blank=True
  2. #py-删除了带有attrs={'readonly':'readonly'}的小部件-我认为Django不再支持它,它当然不鼓励它
class WorkoutReadinessForm(forms.ModelForm):
    class Meta:
        model  = WorkoutReadiness
        fields = ['readiness_question','rating']

WRFormSet = forms.formset_factory(WorkoutReadinessForm, extra=0)
  1. #views.py-除了预先用initial_data填充表单集之外,我还确保在验证后将其单独添加回每个表单中。form.cleaned_data.readiness_question字段是None,直到我用initial_data中的每个字段替换它
#CBV
class WorkoutReadinessCreateView(CreateView):
    model         = WorkoutReadiness
    fields        = ['readiness_question','rating']
    template_name = 'routines/workout_readiness/_form.html'
    initial_data = [{'readiness_question':q} for q in ReadinessQuestion.objects.all()]

    def post(self, request, *args, **kwargs):
        formset = WRFormSet(request.POST)
        if formset.is_valid():            
            return self.form_valid(formset)
        return super().post(request, *args, **kwargs)
    
    def form_valid(self, formset):
        workout = Workout.objects.create(user=self.request.user)
        for idx, form in enumerate(formset):
            cd = form.cleaned_data
            readiness_question = self.initial_data[idx]['readiness_question']
            rating = cd.get('rating')
            w_readiness = WorkoutReadiness(
                workout=workout,
                readiness_question=readiness_question,
                rating=rating
            )
            w_readiness.save()
        return HttpResponseRedirect(
            reverse_lazy(
                'routines:workout_exercise_list', 
                kwargs={'pk':workout.id})
            )

    def get_context_data(self, **kwargs):
        """Render initial form"""
        context = super().get_context_data(**kwargs)
        context['formset'] = WRFormSet(
            initial =self.initial_data
        )
        return context

#FBV
def create_workoutreadiness(request):
    questions = ReadinessQuestion.objects.all()
    initial_data = [{'readiness_question':q} for q in questions]
    
    if request.method == 'POST':
        formset = WRFormSet(request.POST, initial=initial_data)
        workout = Workout.objects.create(user=request.user)
        if formset.is_valid():
            for idx, form in enumerate(formset):
                cd = form.cleaned_data
                readiness_question = initial_data[idx]['readiness_question']
                rating = cd.get('rating')
                w_readiness = WorkoutReadiness(
                    workout=workout,
                    readiness_question=readiness_question,
                    rating=rating
                )
                w_readiness.save()

        return HttpResponseRedirect(reverse_lazy('routines:workout_exercise_list', kwargs={'pk':workout.id}))
    else:
        formset = WRFormSet(initial=initial_data)
          
    context = {
        'questions':questions,
        'formset':formset,
    }
    return render(request, 'routines/workout_readiness/_form.html', context)
  1. #html/template-I renderform.initial.readiness_question,这意味着用户不能在下拉列表中编辑它
<div class="form-group my-5">
  <form action="" method="post">
    <table class="mb-3">
      {% csrf_token %}
      {{ formset.management_form }}
      {% for form in formset %}
        <tr>
          <td>{{ form.initial.readiness_question }}</td> 
          <td>{{ form.rating }}</td> 
        </tr>
      {% endfor %}
    </table>
    <button type="submit" class="btn btn-outline-primary">Submit</button>
  </form>
</div>
  1. How it looks-需要整理,但功能正常

相关问题 更多 >