如果我有这样的模型:
from django.db import models
class Publisher(models.Model):
name = models.CharField(max_length=255)
class Book(models.Model):
publisher = models.ForeignKey('Publisher')
title = models.CharField(max_length=255)
class BookImage(models.Model):
book = models.ForeignKey('Book')
file = models.ImageField(max_length=255)
title = models.CharField(max_length=255)
我想制作一个页面:
Book.objects.filter(publisher=34)
)。我不需要编辑书籍的细节 - 表格仅适用于BookImages。
我和modelformset_factory
和inlineformset_factory
纠缠在一起,但没有一个是正确的......我觉得我做得太复杂了。有任何想法吗?
更新:
以下是我尝试过的一些方向,但是我不确定它们会有什么帮助:
# Forms for multiple Books for this Publisher
# But I don't think I need forms for the Books in my situation?
my_publisher = Publisher.objects.get(pk=37)
BookFormSet = modelformset_factory(Book, fields=(['title']))
formset = BookFormSet(queryset=Book.objects.filter(publisher=my_publisher))
# Multiple BookImages on one Book:
# Good, but how do I do this for all of a Publisher's Books, and display existing BookImages?
my_book = Book.objects.get(pk=42)
BookImageFormSet = inlineformset_factory(Book, BookImage, fields=('file', 'title'))
formset = BookImageFormSet(instance=my_book)
所有formset_factory方法都需要一个可以多次生成的表单。因此,您需要为BookImage创建一个表单(因为您正在使用Models,您需要创建一个模型表单)。
forms.朋友
class BookImageForm(ModelForm):
class Meta:
model = BookImage
# book field wont be generated in the template if it is excluded
exclude = ['book',]
views.朋友
from django.forms.formsets import formset_factory
def your_view(request):
# I'm just including the formset code which you need, im assuming you have the remaining code in working condition
# TO HANDLE NORMAL RENDERING OF FORM WHEN USER OPENS THE WEBPAGE
if request.method == "GET":
bookimage_form = BookImageForm()
bookimage_formset = formset_factory(bookimage_form, max_num=3)
return render(request, 'index.html', {'bookimage_formset': bookimage_formset})
# WHEN USER SUBMITS THE FORM
if request.method == "POST"
# Consider BookImageFormSet as a placeholder which will be able to contain the formset which will come through POST
BookImageFormSet = modelformset_factory(BookImage, BookImageForm, max_num=3)
# bookimage_formset is a variable which stores the Formset of the type BookImageFormSet which in turn is populated by the data received from POST
bookimage_formset = BookImageFormSet(request.POST)
# HIDDEN AUTO GENERATED FIELDS ARE CREATED WHEN THE FORMSET IS RENDERED IN A TEMPLATE, THE FOLLOWING VALIDATION CHECKS IF THE VALUE OF THE HIDDEN FIELDS ARE OKAY OR NOT
if bookimage_formset.is_valid():
# EACH FORM HAS TO BE INDIVIDUALLY VALIDATED, THIS IS NORMAL FORM VALIDATION. ONLY DIFFERENCE IS THAT THIS IS INSIDE A FOR LOOP
for bookimage_form in bookimage_formset:
if bookimage_form.is_valid:
bookimage_form.save()
return HttpResponse("Form saved!")
return HttpResponseRedirect('/')
PS:你可以从request.POST中获取同一视图中其他模型的数据来处理其他数据(例如Books的数据)
我找到了一个如何做这个in this blog post的例子。下面我使用我的Publisher / Book / BookImage模型和基于类的通用视图重写了示例,以供将来参考。
该表单还允许用户编辑书籍的标题,这不是我最初想要的,但这似乎比不这样做更容易;内联图书表格每个至少需要一个字段,因此我们也可以包含图书的标题。
另外,为了看看它是如何工作的,我已经使用这个代码整理了一个小的Django项目,还有更多细节,available on GitHub。
models.py
:
from django.db import models
class Publisher(models.Model):
name = models.CharField(max_length=255)
class Book(models.Model):
title = models.CharField(max_length=255)
publisher = models.ForeignKey('Publisher', on_delete=models.CASCADE)
class BookImage(models.Model):
book = models.ForeignKey('Book', on_delete=models.CASCADE)
image = models.ImageField(max_length=255)
alt_text = models.CharField(max_length=255)
forms.py
:
from django.forms.models import BaseInlineFormSet, inlineformset_factory
from .models import Publisher, Book, BookImage
# The formset for editing the BookImages that belong to a Book.
BookImageFormset = inlineformset_factory(
Book,
BookImage,
fields=('image', 'alt_text')),
extra=1)
class BaseBooksWithImagesFormset(BaseInlineFormSet):
"""
The base formset for editing Books belonging to a Publisher, and the
BookImages belonging to those Books.
"""
def add_fields(self, form, index):
super().add_fields(form, index)
# Save the formset for a Book's Images in a custom `nested` property.
form.nested = BookImageFormset(
instance=form.instance,
data=form.data if form.is_bound else None,
files=form.files if form.is_bound else None,
prefix='bookimage-%s-%s' % (
form.prefix,
BookImageFormset.get_default_prefix()),
)
def is_valid(self):
"Also validate the `nested` formsets."
result = super().is_valid()
if self.is_bound:
for form in self.forms:
if hasattr(form, 'nested'):
result = result and form.nested.is_valid()
return result
def save(self, commit=True):
"Also save the `nested` formsets."
result = super().save(commit=commit)
for form in self.forms:
if hasattr(form, 'nested'):
if not self._should_delete_form(form):
form.nested.save(commit=commit)
return result
# This is the formset for the Books belonging to a Publisher and the
# BookImages belonging to those Books.
PublisherBooksWithImagesFormset = inlineformset_factory(
Publisher,
Book,
formset=BaseBooksWithImagesFormset,
fields=('title',),
extra=1)
views.py
:
from django.http import HttpResponseRedirect
from django.views.generic import FormView
from django.views.generic.detail import SingleObjectMixin
from .forms import PublisherBooksWithImagesFormset
from .models import Publisher, Book, BookImage
class PublisherUpdateView(SingleObjectMixin, FormView):
model = Publisher
success_url = 'publishers/updated/'
template_name = 'publisher_update.html'
def get(self, request, *args, **kwargs):
# The Publisher whose Books we're editing:
self.object = self.get_object(queryset=Publisher.objects.all())
return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
# The Publisher whose Books we're editing:
self.object = self.get_object(queryset=Publisher.objects.all())
return super().post(request, *args, **kwargs)
def get_form(self, form_class=None):
"Use our formset of formsets, and pass in the Publisher object."
return PublisherBooksWithImagesFormset(
**self.get_form_kwargs(), instance=self.object)
def form_valid(self, form):
form.save()
return HttpResponseRedirect(self.get_success_url())
templates/publisher_update.html
:
{% extends 'base.html' %}
{% block content %}
<form action="" method="post" enctype="multipart/form-data">
{% for hidden_field in form.hidden_fields %}
{{ hidden_field.errors }}
{{ hidden_field }}
{% endfor %}
{% csrf_token %}
{{ form.management_form }}
{{ form.non_form_errors }}
{% for book_form in form.forms %}
{# Output a Book form. #}
{% for hidden_field in book_form.hidden_fields %}
{{ hidden_field.errors }}
{% endfor %}
<table>
{{ book_form.as_table }}
</table>
{# Check if our `nested` property exists, with BookImage forms in it. #}
{% if book_form.nested %}
{{ book_form.nested.management_form }}
{{ book_form.nested.non_form_errors }}
{% for bookimage_form in book_form.nested.forms %}
{# Output the BookImage forms for this Book. #}
{% for hidden_field in bookimage_form.hidden_fields %}
{{ hidden_field.errors }}
{% endfor %}
<table>
{{ bookimage_form.as_table }}
</table>
{% endfor %}
{% endif %}
{% endfor %}
<input type="submit" value="Update books">
</form>
{% endblock content %}