Виджет Django Form 'readonly' делает недействительным 'initial'
Вкратце: у меня есть приложение, в котором пользователь вводит тренировки. Прежде чем добавить упражнения в тренировку, пользователь должен заполнить анкету "готовности", состоящую из 5-10 вопросов, оценивая каждый из них как низкий-высокий.
Модели: Модель с набором вопросов (ReadinessQuestion), и модель, хранящая readiness_question, рейтинг и тренировку, к которой он принадлежит (WorkoutReadiness).
Форма: Я создал ModelForm в formset_factory. Когда я устанавливаю виджету поля формы 'readiness_question' значение 'readonly', это делает мою форму недействительной и игнорирует мои 'начальные' данные
Представление: Я хочу создать представление, в котором 5-10 вопросов из ReadinessQuestion находятся в списке, с выпадающими списками для ответов.
- I have set initial data for my form (each different question)
- the user selects the 'rating' they want for each readiness_question
- a new Workout is instantiated
- the WorkoutReadiness is saved, with ForeignKey to the new Workout
Как выглядит моя страница сейчас (почти так, как я хочу, чтобы она выглядела): https://imgur.com/a/HD1l3oe
Ошибка при отправке формы:
Cannot assign "'Sleep'": "WorkoutReadiness.readiness_question" must be a "ReadinessQuestion" instance.
Документация Django здесь очень плохая, widget=forms.TextInput(attrs={'readonly':'readonly'})
нет даже на официальном сайте Django. Если я не сделаю вопрос readiness_question 'readonly', у пользователя будут выпадающие поля, которые он может изменить.
#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)
Мне удалось решить эту проблему (используя альтернативу №1). Я не уверен, что это лучший способ, но пока он работает:
- #models.py - Добавили
blank=True
кreadiness_question
вWorkoutReadiness
модели .
- #forms.py - Удален виджет с
attrs={'readonly':'readonly'}
- я не думаю, что Django поддерживает это больше, он определенно не поощряет это .
class WorkoutReadinessForm(forms.ModelForm):
class Meta:
model = WorkoutReadiness
fields = ['readiness_question','rating']
WRFormSet = forms.formset_factory(WorkoutReadinessForm, extra=0)
- #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)
- #html/template - я отображаю
form.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>
- Как это выглядит - требует приведения в порядок, но функциональность работает