You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django中ModelMultipleChoiceField数据保存至数据库失败问题

图书馆管理系统添加新书数据未保存问题解决方案

问题背景

开发Django图书馆管理系统时,添加新书功能出现异常:提交表单后数据无法保存到Books或BookAuth模型,bookform.is_valid()始终返回False,日志仅显示302重定向和200请求成功,但无数据入库。

模型代码(models.py)

class Authors(models.Model):
    firstName = models.CharField(max_length=20)
    lastName = models.CharField(max_length=20)
    
    def __str__(self):
        return f"{self.firstName.title()} {self.lastName.title()}"
    
    def save(self):
        self.firstName = self.firstName.casefold()
        self.lastName = self.lastName.casefold()
        super().save()
    
class Books(models.Model):
    isbn = models.BigIntegerField(primary_key=True, validators=[
        MinValueValidator(9780000000000),
        MaxValueValidator(9799999999999),
    ])
    bookName = models.CharField(max_length=50)
    authors = models.ManyToManyField(Authors, through='BookAuth')
    pubName = models.CharField(max_length=20)
    inventory = models.IntegerField(default=0)
    
    def __str__(self):
        return f"{self.pk} || {self.getName()}"
    
    def getName(self):
        name, bname = self.bookName.split(), ""
        for x in name:
            bname += x[0].upper() + x[1:] + " "
        return bname[:-1]
    
    def getAuthors(self):
        bookAuths = BookAuth.objects.filter(isbn=self.isbn)
        auth = ""
        for bookAuth in bookAuths:
            auth += f"{bookAuth.getAuth()}, "
        return auth[:-2]
    
    def save(self):
        self.bookName = self.bookName.casefold()
        self.pubName = self.pubName.casefold()
        super().save()

class BookAuth(models.Model):
    isbn = models.ForeignKey(Books, on_delete=models.CASCADE)
    auth = models.ForeignKey(Authors, on_delete=models.CASCADE)
    
    def getAuth(self):
        return Authors.objects.get(id=self.auth.id)
    
    def __str__(self):
        return f"{self.auth} - {self.isbn}"

表单代码(forms.py)

class AddAuthors(forms.ModelForm):
    class Meta:
        model = Authors
        fields = ("__all__")

class AddBooks(forms.ModelForm):
    author = forms.ModelMultipleChoiceField(
        queryset=Authors.objects.all(),
        widget=CheckboxSelectMultiple,
        )
    class Meta:
        model = Books
        fields = ('isbn', 'bookName', 'authors', 'pubName', 'inventory')

视图代码(views.py)

def books(request):
    if request.method == "GET":
        cont1 = footer_counter() # 返回包含会员总数、书籍总数及总库存的字典
        cont2 = {'bookform': AddBooks(), 'authform': AddAuthors(), 'books': Books.objects.all()}
        return render(request, 'books.html', {**cont1,**cont2})
    if request.method == "POST":
        authData = AddAuthors(request.POST)
        bookData = AddBooks(request.POST)
        if authData.is_valid():
            authData.save()
        elif bookData.is_valid():
            bookData.save()
        return redirect(books)

模板代码(books.html)

<div class="col-md-6 text-center">
  <form method="post" autocomplete="off">
  {% csrf_token %}
    <table class="table text-center">
      <thead class="table-dark">
        <tr><th colspan="2">Add a new Book</th></tr>
      </thead>
      <tbody>
        <tr>
          <td class="col-4"><label for="{{ bookform.isbn.id_for_label }}" class="form-label text-white">ISBN</label></td>
          <td class="col-8">{{ bookform.isbn }}</td>
        </tr>
        <tr>
          <td><label for="{{ bookform.bookName.id_for_label }}" class="form-label text-white">Book Name</label></td>
          <td>{{ bookform.bookName }}</td>
        </tr>
        <tr>
          <td><label for="{{ bookform.author.id_for_label }}" class="form-label text-white control-label">Authors</label>
          </td>
          <td>{{ bookform.author }}</td>
        </tr>
        <tr>
          <td><label for="{{ bookform.pubName.id_for_label }}" class="form-label text-white">Publisher Name</label></td>
          <td>{{ bookform.pubName }}</td>
        </tr>
        <tr>
          <td><label for="{{ bookform.inventory.id_for_label }}" class="form-label text-white">Inventory</label></td>
          <td>{{ bookform.inventory }}</td>
        </tr>
      </tbody>
    </table>
    <button type="submit" class="btn btn-primary mx-auto">Add Book</button>
  </form>
</div>

问题原因及修复步骤

1. 表单字段冲突导致验证失败

表单中自定义了author字段处理多选,但Meta类里还保留了模型的authors多对多字段,两者冲突导致验证不通过。

修复代码(forms.py):

class AddBooks(forms.ModelForm):
    author = forms.ModelMultipleChoiceField(
        queryset=Authors.objects.all(),
        widget=CheckboxSelectMultiple,
    )
    class Meta:
        model = Books
        fields = ('isbn', 'bookName', 'pubName', 'inventory')  # 移除authors字段

2. 自定义中间表需手动关联多对多关系

由于使用了自定义的BookAuth中间表,Django的ModelForm无法自动保存多对多关联,必须手动处理。

修复代码(views.py):

def books(request):
    if request.method == "GET":
        cont1 = footer_counter()
        cont2 = {'bookform': AddBooks(), 'authform': AddAuthors(), 'books': Books.objects.all()}
        return render(request, 'books.html', {**cont1,**cont2})
    if request.method == "POST":
        # 区分添加作者和添加书籍的提交
        if 'add_author' in request.POST:
            authData = AddAuthors(request.POST)
            if authData.is_valid():
                authData.save()
                return redirect('books')
            # 验证失败返回表单并显示错误
            cont1 = footer_counter()
            cont2 = {'bookform': AddBooks(), 'authform': authData, 'books': Books.objects.all()}
            return render(request, 'books.html', {**cont1,**cont2})
        
        elif 'add_book' in request.POST:
            bookData = AddBooks(request.POST)
            if bookData.is_valid():
                # 先保存书籍实例(commit=False不立即入库)
                book = bookData.save(commit=False)
                book.save()
                # 关联选中的作者到中间表
                selected_authors = bookData.cleaned_data['author']
                for author in selected_authors:
                    BookAuth.objects.create(isbn=book, auth=author)
                return redirect('books')
            # 验证失败返回表单并显示错误
            cont1 = footer_counter()
            cont2 = {'bookform': bookData, 'authform': AddAuthors(), 'books': Books.objects.all()}
            return render(request, 'books.html', {**cont1,**cont2})
        
        return redirect('books')

3. 区分两个表单的提交动作

同一页面的两个表单需要通过提交按钮的name属性区分,避免逻辑混乱。

修改模板代码:

  • 添加作者的表单按钮:
<button type="submit" name="add_author" class="btn btn-primary mx-auto">Add Author</button>
  • 添加书籍的表单按钮:
<button type="submit" name="add_book" class="btn btn-primary mx-auto">Add Book</button>

4. 显示表单错误信息排查问题

在模板中添加错误提示,方便定位验证失败原因:

{% if bookform.errors %}
    <div class="alert alert-danger">
        {% for field in bookform %}
            {% for error in field.errors %}
                <p>{{ field.label }}: {{ error }}</p>
            {% endfor %}
        {% endfor %}
        {% for error in bookform.non_field_errors %}
            <p>{{ error }}</p>
        {% endfor %}
    </div>
{% endif %}

内容的提问来源于stack exchange,提问作者Dr. Pegasus

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 14:47:10