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
相关产品推荐
相关产品推荐

