无法使用 django formsets 保存数据

问题描述 投票:0回答:2

在表格中输入的数据没有保存在数据库中,也没有显示任何错误。 我正在尝试使用 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 %}

django python-3.x django-forms formset
2个回答
0
投票

需要换线

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()

0
投票

无法使用 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)),

        ],
    ),
© www.soinside.com 2019 - 2024. All rights reserved.