在表格中输入的数据没有保存在数据库中,也没有显示任何错误。 我正在尝试使用 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,
})
表格.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'
}),
}
)
模型.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 %}
需要换线
exam = examform.save()
与
examform.save()
保存问题时,写
for form in formset:
# so that `question` instance can be attached.
question = form.save(commit=False)
question.exam = examform
question.save()
无法使用 Django 表单集填充初始数据和保存数据。 这项工作完全取自 django fucnky sheets formset、extar views sets 及其 html,除了模型提交的查询已更改。
***view.py*** from django.forms import CheckboxSelectMultiple, CheckboxInput, DateInput from django.http import HttpResponseRedirect from django.urls import reverse, reverse_lazy from funky_sheets.formsets import HotView from .models import Assembly def index(request): return HttpResponseRedirect(reverse('update')) class CreateAssemblyView(HotView): model = Assembly template_name = 'boards/create.html' prefix = 'table' checkbox_checked = 'yes' checkbox_unchecked = 'no' success_url = reverse_lazy('update') fields = ( 'id', 'AssMake', 'AssModelType', 'AssFleetSize', 'AssDateEdit', ) factory_kwargs = { 'widgets': { 'AssDateEdit': DateInput(attrs={'type': 'date'}), # 'genre': CheckboxSelectMultiple(), # 'parents_guide': CheckboxInput(), } } hot_settings = { # 'columnSorting': 'true', 'contextMenu': 'true', 'autoWrapRow': 'true', 'rowHeaders': 'true', 'contextMenu': 'true', 'search': 'true', 'licenseKey': 'non-commercial-and-evaluation', } class UpdateAssemblyView(CreateAssemblyView): template_name = 'boards/update.html' action = 'update' button_text = 'Update' hot_settings = { # 'columnSorting': 'true', 'contextMenu': 'true', 'autoWrapRow': 'true', 'rowHeaders': 'true', 'contextMenu': 'true', 'search': 'true', 'licenseKey': 'non-commercial-and-evaluation', } ***models.py*** from django.db import models from django.utils import timezone from django.contrib.auth.models import User class Assembly(models.Model): AssMake = models.CharField(max_length=12, null=True) AssModelType = models.CharField(max_length=12, null=True) AssFleetSize = models.IntegerField(null=True) AssDateEdit = models.DateTimeField(default=timezone.now, null=True) def __str__(self): return self.AssModelType class Aircraft(models.Model): arcMsn = models.IntegerField(unique=True, null=True) arcVar = models.CharField(max_length=8, null=True) arcRegName = models.CharField(max_length=6, unique=True, null=True) assembly = models.ForeignKey(Assembly, on_delete=models.CASCADE, null=True) updated_by = models.ForeignKey(User, on_delete=models.CASCADE, null=True) def __str__(self): return self.arcMsn ***hot.html*** {% load static %} {% load funky_sheets_tags %} <link href="{% static 'css/handsontable.full.min.css' %}" rel="stylesheet"> <form id="form_hot" method="post" action="" onsubmit="return constructFormset();"> {% csrf_token %} {{ formset.management_form }} {% if formset.errors %}{% endif %} {% include errors_template %} <div id="table_hot"></div> <p> <button type="submit" class="btn btn-primary" id="btn-submit">{{ button_text }}</button> </p> </form> <script src="{% static 'js/jquery-3.3.1.min.js' %}"></script> <script src="{% static 'js/handsontable.full.min.js' %}"></script> <script type="text/javascript"> /* 1. Construct initial data for table from the formset */ var colHeaders = new Array(); var fieldTypes = new Array(); var primaryKeys = new Array(); var data = new Array(); // Variables supporting "Django Select widget" / "Handsontable dropdown" logic var temp = []; // Iterate through the forms of the formset {% for form in formset %} var row = new Array(); // Iterate through the fields of the form {% for field in form %} /* CheckboxSelectMultiple widget support for ManyToManyField */ // TODO: Change to switch & function as in constructFormField()? {% if field|widget_type == "CheckboxSelectMultiple" %} {% for choice in field.field.choices %} // Only when iterating through the first form in formset {% if forloop.parentloop.parentloop.counter == 1 %} colHeaders.push("{{ choice.1 }}"); // Add label to the colHeaders // Extra values needed for construction of m2m checkboxes in constructFormField() fieldTypes.push([ // Add field type to the fieldTypes "m2m {{ field.field.widget.input_type }}", "{{ field.label }}", "{{ forloop.counter0 }}", "{{ choice.0 }}" ]); {% endif %} // field.value is a list of 'ids' of related objects in m2m field // choice.0 is an 'id' of single related object {% if choice.0 in field.value|int_list %} row.push("{{ checkbox_checked }}"); {% else %} row.push("{{ checkbox_unchecked }}"); {% endif %} {% endfor %} /* Select widget */ // TODO: Add comments {% elif field|widget_type == "Select" %} var choiceValues = []; var choiceStrings = []; var cellWidgetChoiceValues = []; var cellWidgetChoiceStrings = []; {% if forloop.parentloop.counter == 1 %} colHeaders.push("{{ field.label }}"); if (temp.length === 0) { temp.push("{{ field.field.widget.input_type }}"); } {% for choice in field.field.choices %} cellWidgetChoiceValues.push("{{ choice.0 }}"); cellWidgetChoiceStrings.push("{{ choice.1 }}"); {% endfor %} temp.push([cellWidgetChoiceValues, cellWidgetChoiceStrings]); fieldTypes.push(temp); {% endif %} {% for choice in field.field.choices %} choiceValues.push("{{ choice.0 }}"); choiceStrings.push("{{ choice.1 }}"); {% endfor %} {% if field.value %} // Get index of selected choice in choiceValues var index = choiceValues.indexOf("{{ field.value }}"); // Push string at index in choiceStrings to the row row.push(choiceStrings[index]); {% else %} row.push(""); {% endif %} /* Other widgets: TextInput, NumberInput, URLInput, DateInput, CheckboxInput */ {% else %} if ("{{ field.label }}" != "Id" && "{{ field.label }}" != "Delete"){ // Only when iterating through the first form in formset {% if forloop.parentloop.counter == 1 %} colHeaders.push("{{ field.label }}"); // Add label to the colHeaders fieldTypes.push([ // Add field type to the fieldTypes "{{ field.field.widget.input_type }}" ]); {% endif %} // Add value to the row // Input with value {% if field.value %} // URLInput {% if field.field.widget.input_type == "url" %} row.push("<a href={{ field.value }} target='_blank'>{{ field.value }}</a>"); // CheckboxInput {% elif field.field.widget.input_type == "checkbox" %} row.push("{{ checkbox_checked }}"); // Other widgets {% else %} row.push("{{ field.value }}"); {% endif %} // Inputs without value {% else %} // CheckboxInput {% if field.field.widget.input_type == "checkbox" %} row.push("{{ checkbox_unchecked }}"); // Other widgets {% else %} row.push(""); {% endif %} {% endif %} } else if ("{{ field.label }}" == "Id") { // TODO: Why? // Add value to the primaryKeys primaryKeys.push("{{ field.value }}"); }; {% endif %} {% endfor %} // Add row to the data data.push(row); // Debug logs // console.log(row); {% endfor %} /* 2. Construct & fill in formset fields */ var total_non_empty_rows = 0; function constructFormset() { // Get data from instantiated Handsontable var tableData = hot.getData(); // Iterate through tableData // Construct form from each row (i=row number) for(var i = 0; i < tableData.length; i++) { var rowTemp = tableData[i]; var row = rowTemp.map(setBooleansArray); var pk = ""; // Get primary key if (i < primaryKeys.length){ pk = primaryKeys[i] } // Add primary key value or empty string {% if action == "update" %} var input_id = "{{ formset.prefix }}-" + i + "-id"; $("#form_hot").append( '<input id="id_' + input_id + '" name="' + input_id + '" type="hidden" value="' + pk +'" />' ); {% endif %} // Set empty rows for deletion if (row.isNull()){ var input_id = "{{formset.prefix}}-" + i + "-DELETE"; $('#form_hot').append( '<input id="id_' + input_id + '" name="' + input_id + '" type="hidden" value="on" />' ); } // Add fields to the form of a formset for each element of a row in tableData // (i=row number, j=column number) else { for(var j = 0; j < row.length; j++) { constructFormField(i, j, fieldTypes[j], row); } total_non_empty_rows += 1; } } // Set total number of forms var input_id = "id_{{ formset.prefix }}-TOTAL_FORMS"; $('#' + input_id).val(total_non_empty_rows); return true; } /* 3. Helper functions */ // 3.1. Check if an array contains only empty strings Array.prototype.isNull = function (){ return this.join().replace(/,/g,"").length === 0; }; // 3.2. Construct form field (i=row number, j=column number) function constructFormField(i, j, fieldType, row) { var partialName = colHeaders[j].toLowerCase().replace(/ /g,"_"); var fieldName = "{{ formset.prefix }}-" + i + "-" + partialName; var fieldString = ""; var value = row[j]; if (value == null) { value = ""; } switch(fieldType[0]) { case "select": // select field // TODO: Add comments // Get index of selected choice in choiceStrings var index = fieldType[j][1].indexOf(value); // Get value at index in choiceValues value = fieldType[j][0][index]; fieldString = "<select id='id_" + fieldName + "' name='" + fieldName + "' style='visibility: hidden;'>" + "<option value='" + value +"' selected></option>" + "</select>"; break; case "m2m checkbox": // m2m checkbox field if (value == true) { var fieldLabel, count = ""; var tablePrefix = "{{ formset.prefix }}-" + i + "-"; var fieldLabel = fieldType[1].toLowerCase().replace(/ /g,"_"); var count = fieldType[2]; value = fieldType[3]; fieldString = "<input id=id_'" + tablePrefix + fieldLabel + "_" + count + "' name='" + tablePrefix + fieldLabel + "' type='hidden' value='" + value + "' />"; } break; case "url": // url field if (value.indexOf("href=") != -1) { value = $("<div>").append(value).find("a").attr("href"); } fieldString = "<input id=id_'" + fieldName + "' name='" + fieldName + "' type='hidden' value='" + value + "' />"; break; default: // text, number, checkbox fields fieldString = "<input id=id_'" + fieldName + "' name='" + fieldName + "' type='hidden' value='" + value + "' />"; } return $('#form_hot').append(fieldString); } // 3.3. Custom Handsontable HtmlRendered, which removes null from empty url inputs function cleanHtmlRenderer(instance, td, row, col, prop, value, cellProperties){ Handsontable.renderers.HtmlRenderer.apply(this, arguments); if (td.innerHTML === "null") { td.innerHTML = ""; } } Handsontable.renderers.registerRenderer("cleanHtmlRenderer", cleanHtmlRenderer); // 3.4. Construct Handsontable columns based on fieldTypes var cellWidget = function(item, index) { if (item[0] == "checkbox" || item[0] == "m2m checkbox") { return { type: "checkbox", checkedTemplate: "{{ checkbox_checked }}", uncheckedTemplate: "{{ checkbox_unchecked }}", }; } else if (item[0] == "select") { return { type: "dropdown", source: item[index][1] }; } else if (item[0] == "date") { return { type: "date", dateFormat: "{{ date_format }}" }; } else if (item[0] == "url") { return { renderer: "cleanHtmlRenderer" }; } else { return {}; } } // 3.5. Change string 'true' or 'false' to js booleans // On paste from clipboard var setBooleansArray = function(item, index) { if (item == "{{ checkbox_checked }}") { return true; } else if (item == "{{ checkbox_unchecked }}") { return false; } else { return item; } } // On getting hotSettings passed in from HotView var setBooleansObject = function(obj) { for (var key in obj) { if (obj[key] == "true") { obj[key] = true; } else if (obj[key] == "false") { obj[key] = false; } } } /* 4. Construct Handsontable */ var container = document.getElementById("table_hot"); // Construct columns & cell types based on fieldTypes var columns = fieldTypes.map(cellWidget); // Debug logs // console.log(colHeaders); // console.log(fieldTypes); // console.log("{{ date_format }}"); // console.log(columns); // Default hotSettings var hotSettings = { data: data, colHeaders: colHeaders, columns: columns }; // Get hotSettings passed in from HotView var extraHotSettings = {{ hot_settings|safe }}; setBooleansObject(extraHotSettings); // Extend hotSettings with extraHotSettings var hotSettings = $.extend(hotSettings, extraHotSettings); var hot = new Handsontable(container, hotSettings); $("#HandsontableCopyPaste").css("visibility", "hidden"); </script>
迁移
从 django.conf 导入设置 从 django.db 导入迁移, 模型导入 django.db.models.deletion 导入 django.utils.timezone
从 boards.models 导入 Assembly
def add_assembly(apps, schema_editor): assembly_one = Assembly.objects.create( 屁股='B767', AssModelType='波音', AssFleetSize = 3, AssDateEdit='2023-01-16',
) assembly_two = Assembly.objects.create( AssMake='A350', AssModelType='Airbus', AssFleetSize=19, AssDateEdit='2023-02-16', ) assembly_three = Assembly.objects.create( AssMake='B777', AssModelType='Boeing', AssFleetSize=19, AssDateEdit='2023-03-16', )
类迁移(迁移。迁移):
initial = True dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] migrations.CreateModel( name='Assembly', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('AssMake', models.CharField(max_length=12, null=True)), ('AssModelType', models.CharField(max_length=12, null=True)), ('AssFleetSize', models.IntegerField(null=True)), ('AssDateEdit', models.DateTimeField(default=django.utils.timezone.now, null=True)), ], ),
迁移.CreateModel( name='飞机', 字段=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('arcMsn', models.IntegerField(null=True, unique=True)), ('arcVar', models.CharField(max_length=8, null=True)), ('arcRegName', models.CharField(max_length=6, null=True, unique=True)),
], ),