Данные, введенные в форму, не сохраняются в базе данных и не показывают ошибок. Я пытаюсь создать экзамен по крайней мере с одним вопросом, используя ExamModelForm и QuestionFormset. После ввода сведений об экзамене и вопросов в create_exam_with_questions. html кнопка создания не сохраняет данные в базу данных
views.py
from django.shortcuts import render
from django.shortcuts import redirect
from django.http import HttpResponseRedirect, HttpResponse
from .forms import ExamModelForm, ExamFormset, QuestionFormset
from .models import Question
def create_exam_with_questions(request):
template_name = 'school/create_exam_with_questions.html'
if request.method == 'GET':
examform = ExamModelForm(request.GET or None)
formset = QuestionFormset(queryset=Question.objects.none())
elif request.method == 'POST':
examform = ExamModelForm(request.POST)
formset = QuestionFormset(request.POST)
if examform.is_valid() and formset.is_valid():
# first save this exam, as its reference will be used in `Question`
exam = examform.save()
for form in formset:
# so that `question` instance can be attached.
question = form.save(commit=False)
question.exam = exam
question.save()
return redirect("map:map-home")
return render(request, template_name, {
'examform': examform,
'formset': formset,
})
forms.py
from django import forms
from django.forms import (formset_factory, modelformset_factory)
from .models import Exam, Question
class ExamModelForm(forms.ModelForm):
class Meta:
model = Exam
fields = ['name', 'section', 'duration', 'subject', 'start', 'teacher']
QuestionFormset = modelformset_factory(
Question,
fields=('question', 'ans', 'op2', 'op3', 'op4', ),
extra=1,
widgets={'question': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Enter Question here'
}),
'ans': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Enter answer here'
}),
'op2': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Enter option2 here'
}),
'op3': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Enter option3 here'
}),
'op4': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Enter option4 here'
}),
}
)
models.py
class Section(models.Model):
name = models.CharField(max_length = 20)
school = models.ForeignKey(School, on_delete = models.CASCADE)
def __str__(self):
return self.name
class Subject(models.Model):
name = models.CharField(max_length = 20)
section = models.ForeignKey(Section, on_delete = models.CASCADE)
def __str__(self):
return self.name
class Exam(models.Model):
name = models.CharField(max_length = 20)
section = models.ForeignKey(Section, on_delete = models.CASCADE)
subject = models.ForeignKey(Subject, on_delete = models.CASCADE)
duration = models.TimeField()
start = models.DateTimeField()
teacher = models.CharField(max_length = 20)
def __str__(self):
return self.name
class Question(models.Model):
question = models.TextField(max_length = 20)
ans = models.CharField(max_length = 20)
op2 = models.CharField(max_length = 20)
op3 = models.CharField(max_length = 20)
op4 = models.CharField(max_length = 20)
exam = models.ForeignKey(Exam, on_delete = models.CASCADE)
def __str__(self):
return self.question
create_exam_with_questions. html
{% extends "school/base.html" %}
{% block container %}
{% if heading %}
<h3>{{heading}}</h3>
{% endif %}
<form class="form-horizontal" method="POST" action="">
{% csrf_token %}
<div class="row spacer">
<div class="col-2">
<label>{{examform.name.label}}</label>
</div>
<div class="col-4">
<div class="input-group">
{{examform.name}}
</div>
</div>
</div>
<div class="row spacer">
<div class="col-2">
<label>{{examform.subject.label}}</label>
</div>
<div class="col-4">
<div class="input-group">
{{examform.subject}}
</div>
</div>
</div>
<div class="row spacer">
<div class="col-2">
<label>{{examform.duration.label}}</label>
</div>
<div class="col-4">
<div class="input-group">
{{examform.duration}}
</div>
</div>
</div>
<div class="row spacer">
<div class="col-2">
<label>{{examform.teacher.label}}</label>
</div>
<div class="col-4">
<div class="input-group">
{{examform.teacher}}
</div>
</div>
</div>
<div class="row spacer">
<div class="col-2">
<label>{{examform.start.label}}</label>
</div>
<div class="col-4">
<div class="input-group">
{{examform.start}}
</div>
</div>
</div>
<div class="row spacer">
<div class="col-2">
<label>{{examform.section.label}}</label>
</div>
<div class="col-4">
<div class="input-group">
{{examform.section}}
</div>
</div>
</div>
{{ formset.management_form }}
{% for form in formset %}
<div class="row form-row spacer">
<div class="col-2">
<label>{{form.question.label}}</label>
</div>
<div class="col-4">
<div class="input-group">
{{form.question}}
</div>
<div class="input-group">
{{form.ans}}
</div>
<div class="input-group">
{{form.op2}}
</div>
<div class="input-group">
{{form.op3}}
</div>
<div class="input-group">
{{form.op4}}
</div>
</div>
<div class="input-group-append">
<button class="btn btn-success add-form-row">+</button>
</div>
</div>
{% endfor %}
<div class="row spacer">
<div class="col-4 offset-2">
<button type="submit" class="btn btn-block btn-primary">Create</button>
</div>
</div>
</form>
{% endblock %}
{% block custom_js %}
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script type="text/javascript">
function updateElementIndex(el, prefix, ndx) {
var id_regex = new RegExp('(' + prefix + '-\\d+)');
var replacement = prefix + '-' + ndx;
if ($(el).attr("for")) $(el).attr("for", $(el).attr("for").replace(id_regex, replacement));
if (el.id) el.id = el.id.replace(id_regex, replacement);
if (el.name) el.name = el.name.replace(id_regex, replacement);
}
function cloneMore(selector, prefix) {
var newElement = $(selector).clone(true);
var total = $('#id_' + prefix + '-TOTAL_FORMS').val();
newElement.find(':input:not([type=button]):not([type=submit]):not([type=reset])').each(function() {
var name = $(this).attr('name')
if(name) {
name = name.replace('-' + (total-1) + '-', '-' + total + '-');
var id = 'id_' + name;
$(this).attr({'name': name, 'id': id}).val('').removeAttr('checked');
}
});
newElement.find('label').each(function() {
var forValue = $(this).attr('for');
if (forValue) {
forValue = forValue.replace('-' + (total-1) + '-', '-' + total + '-');
$(this).attr({'for': forValue});
}
});
total++;
$('#id_' + prefix + '-TOTAL_FORMS').val(total);
$(selector).after(newElement);
var conditionRow = $('.form-row:not(:last)');
conditionRow.find('.btn.add-form-row')
.removeClass('btn-success').addClass('btn-danger')
.removeClass('add-form-row').addClass('remove-form-row')
.html('-');
return false;
}
function deleteForm(prefix, btn) {
var total = parseInt($('#id_' + prefix + '-TOTAL_FORMS').val());
if (total > 1){
btn.closest('.form-row').remove();
var forms = $('.form-row');
$('#id_' + prefix + '-TOTAL_FORMS').val(forms.length);
for (var i=0, formCount=forms.length; i<formCount; i++) {
$(forms.get(i)).find(':input').each(function() {
updateElementIndex(this, prefix, i);
});
}
}
return false;
}
$(document).on('click', '.add-form-row', function(e){
e.preventDefault();
cloneMore('.form-row:last', 'form');
return false;
});
$(document).on('click', '.remove-form-row', function(e){
e.preventDefault();
deleteForm('form', $(this));
return false;
});
</script>
{% endblock %}