mirror of
https://github.com/django/django.git
synced 2025-07-04 01:39:20 +00:00
newforms-admin: Fixed #5733. Added formset_for_queryset. This is backwards incompatble for anyone using formset_for_model or BaseModelFormSet.
git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@6655 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
99e8171a61
commit
6f15904669
@ -15,8 +15,8 @@ from widgets import Select, SelectMultiple, HiddenInput, MultipleHiddenInput
|
||||
|
||||
__all__ = (
|
||||
'save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields',
|
||||
'ModelChoiceField', 'ModelMultipleChoiceField', 'formset_for_model',
|
||||
'inline_formset'
|
||||
'formset_for_model', 'formset_for_queryset', 'inline_formset',
|
||||
'ModelChoiceField', 'ModelMultipleChoiceField',
|
||||
)
|
||||
|
||||
def save_instance(form, instance, fields=None, fail_message='saved', commit=True):
|
||||
@ -237,17 +237,20 @@ def initial_data(instance, fields=None):
|
||||
|
||||
class BaseModelFormSet(BaseFormSet):
|
||||
"""
|
||||
A ``FormSet`` attatched to a particular model or sequence of model instances.
|
||||
A ``FormSet`` for editing a queryset and/or adding new objects to it.
|
||||
"""
|
||||
model = None
|
||||
queryset = None
|
||||
|
||||
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, instances=None):
|
||||
self.instances = instances
|
||||
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None):
|
||||
kwargs = {'data': data, 'files': files, 'auto_id': auto_id, 'prefix': prefix}
|
||||
if instances:
|
||||
kwargs['initial'] = [initial_data(instance) for instance in instances]
|
||||
if self.queryset:
|
||||
kwargs['initial'] = [initial_data(obj) for obj in self.get_queryset()]
|
||||
super(BaseModelFormSet, self).__init__(**kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
return self.queryset._clone()
|
||||
|
||||
def save_new(self, form, commit=True):
|
||||
"""Saves and returns a new model instance for the given form."""
|
||||
return save_instance(form, self.model(), commit=commit)
|
||||
@ -260,25 +263,36 @@ class BaseModelFormSet(BaseFormSet):
|
||||
"""Saves model instances for every form, adding and changing instances
|
||||
as necessary, and returns the list of instances.
|
||||
"""
|
||||
return self.save_existing_objects(commit) + self.save_new_objects(commit)
|
||||
|
||||
def save_existing_objects(self, commit=True):
|
||||
if not self.queryset:
|
||||
return []
|
||||
# Put the objects from self.get_queryset into a dict so they are easy to lookup by pk
|
||||
existing_objects = {}
|
||||
for obj in self.get_queryset():
|
||||
existing_objects[obj._get_pk_val()] = obj
|
||||
saved_instances = []
|
||||
# put self.instances into a dict so they are easy to lookup by pk
|
||||
instances = {}
|
||||
for instance in self.instances:
|
||||
instances[instance._get_pk_val()] = instance
|
||||
if self.instances:
|
||||
# update/save existing instances
|
||||
for form in self.change_forms:
|
||||
instance = instances[form.cleaned_data[self.model._meta.pk.attname]]
|
||||
if self.deletable and form.cleaned_data[DELETION_FIELD_NAME]:
|
||||
instance.delete()
|
||||
else:
|
||||
saved_instances.append(self.save_instance(form, instance, commit=commit))
|
||||
# create/save new instances
|
||||
for form in self.change_forms:
|
||||
obj = existing_objects[form.cleaned_data[self.model._meta.pk.attname]]
|
||||
if self.deletable and form.cleaned_data[DELETION_FIELD_NAME]:
|
||||
obj.delete()
|
||||
else:
|
||||
saved_instances.append(self.save_instance(form, obj, commit=commit))
|
||||
return saved_instances
|
||||
|
||||
def save_new_objects(self, commit=True):
|
||||
new_objects = []
|
||||
for form in self.add_forms:
|
||||
if form.is_empty():
|
||||
continue
|
||||
saved_instances.append(self.save_new(form, commit=commit))
|
||||
return saved_instances
|
||||
# If someone has marked an add form for deletion, don't save the
|
||||
# object. At some point it would be nice if we didn't display
|
||||
# the deletion widget for add forms.
|
||||
if self.deletable and form.cleaned_data[DELETION_FIELD_NAME]:
|
||||
continue
|
||||
new_objects.append(self.save_new(form, commit=commit))
|
||||
return new_objects
|
||||
|
||||
def add_fields(self, form, index):
|
||||
"""Add a hidden field for the object's primary key."""
|
||||
@ -286,25 +300,61 @@ class BaseModelFormSet(BaseFormSet):
|
||||
form.fields[self._pk_field_name] = IntegerField(required=False, widget=HiddenInput)
|
||||
super(BaseModelFormSet, self).add_fields(form, index)
|
||||
|
||||
def formset_for_model(model, form=BaseForm, formfield_callback=lambda f: f.formfield(), formset=BaseModelFormSet, extra=1, orderable=False, deletable=False, fields=None):
|
||||
form = form_for_model(model, form=form, fields=fields, formfield_callback=formfield_callback)
|
||||
def formset_for_queryset(queryset, form=BaseForm, formfield_callback=lambda f: f.formfield(),
|
||||
formset=BaseModelFormSet, extra=1, orderable=False, deletable=False, fields=None):
|
||||
"""
|
||||
Returns a FormSet class for the given QuerySet. This FormSet will contain
|
||||
change forms for every instance in the QuerySet as well as the number of
|
||||
add forms specified by ``extra``.
|
||||
|
||||
Provide ``extra`` to determine the number of add forms to display.
|
||||
|
||||
Provide ``deletable`` if you want to allow the formset to delete any
|
||||
objects in the given queryset.
|
||||
|
||||
Provide ``form`` if you want to use a custom BaseForm subclass.
|
||||
|
||||
Provide ``formfield_callback`` if you want to define different logic for
|
||||
determining the formfield for a given database field. It's a callable that
|
||||
takes a database Field instance and returns a form Field instance.
|
||||
|
||||
Provide ``formset`` if you want to use a custom BaseModelFormSet subclass.
|
||||
"""
|
||||
form = form_for_model(queryset.model, form=form, fields=fields, formfield_callback=formfield_callback)
|
||||
FormSet = formset_for_form(form, formset, extra, orderable, deletable)
|
||||
FormSet.model = model
|
||||
FormSet.model = queryset.model
|
||||
FormSet.queryset = queryset
|
||||
return FormSet
|
||||
|
||||
def formset_for_model(model, form=BaseForm, formfield_callback=lambda f: f.formfield(),
|
||||
formset=BaseModelFormSet, extra=1, orderable=False, deletable=False, fields=None):
|
||||
"""
|
||||
Returns a FormSet class for the given Django model class. This FormSet
|
||||
will contain change forms for every instance of the given model as well
|
||||
as the number of add forms specified by ``extra``.
|
||||
|
||||
This is essentially the same as ``formset_for_queryset``, but automatically
|
||||
uses the model's default manager to determine the queryset.
|
||||
"""
|
||||
qs = model._default_manager.all()
|
||||
return formset_for_queryset(qs, form, formfield_callback, formset, extra, orderable, deletable, fields)
|
||||
|
||||
class InlineFormset(BaseModelFormSet):
|
||||
"""A formset for child objects related to a parent."""
|
||||
def __init__(self, instance=None, data=None, files=None):
|
||||
def __init__(self, instance, data=None, files=None):
|
||||
from django.db.models.fields.related import RelatedObject
|
||||
self.instance = instance
|
||||
# is there a better way to get the object descriptor?
|
||||
self.rel_name = RelatedObject(self.fk.rel.to, self.model, self.fk).get_accessor_name()
|
||||
super(InlineFormset, self).__init__(data, files, instances=self.get_inline_objects(), prefix=self.rel_name)
|
||||
super(InlineFormset, self).__init__(data, files, prefix=self.rel_name)
|
||||
|
||||
def get_inline_objects(self):
|
||||
if self.instance is None:
|
||||
return []
|
||||
return getattr(self.instance, self.rel_name).all()
|
||||
def get_queryset(self):
|
||||
"""
|
||||
Returns this FormSet's queryset, but restricted to children of
|
||||
self.instance
|
||||
"""
|
||||
kwargs = {self.fk.name: self.instance}
|
||||
return self.queryset.filter(**kwargs)
|
||||
|
||||
def save_new(self, form, commit=True):
|
||||
kwargs = {self.fk.get_attname(): self.instance._get_pk_val()}
|
||||
|
0
tests/modeltests/model_formsets/__init__.py
Normal file
0
tests/modeltests/model_formsets/__init__.py
Normal file
204
tests/modeltests/model_formsets/models.py
Normal file
204
tests/modeltests/model_formsets/models.py
Normal file
@ -0,0 +1,204 @@
|
||||
from django.db import models
|
||||
|
||||
class Author(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
class Book(models.Model):
|
||||
author = models.ForeignKey(Author)
|
||||
title = models.CharField(max_length=100)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.title
|
||||
|
||||
|
||||
__test__ = {'API_TESTS': """
|
||||
|
||||
>>> from django.newforms.models import formset_for_queryset, formset_for_model
|
||||
|
||||
>>> qs = Author.objects.all()
|
||||
>>> AuthorFormSet = formset_for_model(Author, extra=3)
|
||||
|
||||
>>> formset = AuthorFormSet()
|
||||
>>> for form in formset.forms:
|
||||
... print form.as_p()
|
||||
<p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" maxlength="100" /><input type="hidden" name="form-0-id" id="id_form-0-id" /></p>
|
||||
<p><label for="id_form-1-name">Name:</label> <input id="id_form-1-name" type="text" name="form-1-name" maxlength="100" /><input type="hidden" name="form-1-id" id="id_form-1-id" /></p>
|
||||
<p><label for="id_form-2-name">Name:</label> <input id="id_form-2-name" type="text" name="form-2-name" maxlength="100" /><input type="hidden" name="form-2-id" id="id_form-2-id" /></p>
|
||||
|
||||
>>> data = {
|
||||
... 'form-COUNT': '3',
|
||||
... 'form-0-name': 'Charles Baudelaire',
|
||||
... 'form-1-name': 'Arthur Rimbaud',
|
||||
... 'form-2-name': '',
|
||||
... }
|
||||
|
||||
>>> formset = AuthorFormSet(data=data)
|
||||
>>> formset.is_valid()
|
||||
True
|
||||
|
||||
>>> formset.save()
|
||||
[<Author: Charles Baudelaire>, <Author: Arthur Rimbaud>]
|
||||
|
||||
>>> for author in Author.objects.order_by('name'):
|
||||
... print author.name
|
||||
Arthur Rimbaud
|
||||
Charles Baudelaire
|
||||
|
||||
|
||||
Gah! We forgot Paul Verlaine. Let's create a formset to edit the existing
|
||||
authors with an extra form to add him. This time we'll use formset_for_queryset.
|
||||
We *could* use formset_for_queryset to restrict the Author objects we edit,
|
||||
but in that case we'll use it to display them in alphabetical order by name.
|
||||
|
||||
>>> qs = Author.objects.order_by('name')
|
||||
>>> AuthorFormSet = formset_for_queryset(qs, extra=1, deletable=False)
|
||||
|
||||
>>> formset = AuthorFormSet()
|
||||
>>> for form in formset.forms:
|
||||
... print form.as_p()
|
||||
<p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" value="Arthur Rimbaud" maxlength="100" /><input type="hidden" name="form-0-id" value="2" id="id_form-0-id" /></p>
|
||||
<p><label for="id_form-1-name">Name:</label> <input id="id_form-1-name" type="text" name="form-1-name" value="Charles Baudelaire" maxlength="100" /><input type="hidden" name="form-1-id" value="1" id="id_form-1-id" /></p>
|
||||
<p><label for="id_form-2-name">Name:</label> <input id="id_form-2-name" type="text" name="form-2-name" maxlength="100" /><input type="hidden" name="form-2-id" id="id_form-2-id" /></p>
|
||||
|
||||
|
||||
>>> data = {
|
||||
... 'form-COUNT': '3',
|
||||
... 'form-0-id': '2',
|
||||
... 'form-0-name': 'Arthur Rimbaud',
|
||||
... 'form-1-id': '1',
|
||||
... 'form-1-name': 'Charles Baudelaire',
|
||||
... 'form-2-name': 'Paul Verlaine',
|
||||
... }
|
||||
|
||||
>>> formset = AuthorFormSet(data=data)
|
||||
>>> formset.is_valid()
|
||||
True
|
||||
|
||||
>>> formset.save()
|
||||
[<Author: Arthur Rimbaud>, <Author: Charles Baudelaire>, <Author: Paul Verlaine>]
|
||||
|
||||
>>> for author in Author.objects.order_by('name'):
|
||||
... print author.name
|
||||
Arthur Rimbaud
|
||||
Charles Baudelaire
|
||||
Paul Verlaine
|
||||
|
||||
|
||||
This probably shouldn't happen, but it will. If an add form was marked for
|
||||
deltetion, make sure we don't save that form.
|
||||
|
||||
>>> qs = Author.objects.order_by('name')
|
||||
>>> AuthorFormSet = formset_for_queryset(qs, extra=1, deletable=True)
|
||||
|
||||
>>> formset = AuthorFormSet()
|
||||
>>> for form in formset.forms:
|
||||
... print form.as_p()
|
||||
<p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" value="Arthur Rimbaud" maxlength="100" /></p>
|
||||
<p><label for="id_form-0-DELETE">Delete:</label> <input type="checkbox" name="form-0-DELETE" id="id_form-0-DELETE" /><input type="hidden" name="form-0-id" value="2" id="id_form-0-id" /></p>
|
||||
<p><label for="id_form-1-name">Name:</label> <input id="id_form-1-name" type="text" name="form-1-name" value="Charles Baudelaire" maxlength="100" /></p>
|
||||
<p><label for="id_form-1-DELETE">Delete:</label> <input type="checkbox" name="form-1-DELETE" id="id_form-1-DELETE" /><input type="hidden" name="form-1-id" value="1" id="id_form-1-id" /></p>
|
||||
<p><label for="id_form-2-name">Name:</label> <input id="id_form-2-name" type="text" name="form-2-name" value="Paul Verlaine" maxlength="100" /></p>
|
||||
<p><label for="id_form-2-DELETE">Delete:</label> <input type="checkbox" name="form-2-DELETE" id="id_form-2-DELETE" /><input type="hidden" name="form-2-id" value="3" id="id_form-2-id" /></p>
|
||||
<p><label for="id_form-3-name">Name:</label> <input id="id_form-3-name" type="text" name="form-3-name" maxlength="100" /></p>
|
||||
<p><label for="id_form-3-DELETE">Delete:</label> <input type="checkbox" name="form-3-DELETE" id="id_form-3-DELETE" /><input type="hidden" name="form-3-id" id="id_form-3-id" /></p>
|
||||
|
||||
>>> data = {
|
||||
... 'form-COUNT': '4',
|
||||
... 'form-0-id': '2',
|
||||
... 'form-0-name': 'Arthur Rimbaud',
|
||||
... 'form-1-id': '1',
|
||||
... 'form-1-name': 'Charles Baudelaire',
|
||||
... 'form-2-id': '3',
|
||||
... 'form-2-name': 'Paul Verlaine',
|
||||
... 'form-3-name': 'Walt Whitman',
|
||||
... 'form-3-DELETE': 'on',
|
||||
... }
|
||||
|
||||
>>> formset = AuthorFormSet(data=data)
|
||||
>>> formset.is_valid()
|
||||
True
|
||||
|
||||
>>> formset.save()
|
||||
[<Author: Arthur Rimbaud>, <Author: Charles Baudelaire>, <Author: Paul Verlaine>]
|
||||
|
||||
>>> for author in Author.objects.order_by('name'):
|
||||
... print author.name
|
||||
Arthur Rimbaud
|
||||
Charles Baudelaire
|
||||
Paul Verlaine
|
||||
|
||||
|
||||
We can also create a formset that is tied to a parent model. This is how the
|
||||
admin system's edit inline functionality works.
|
||||
|
||||
>>> from django.newforms.models import inline_formset
|
||||
|
||||
>>> AuthorBooksFormSet = inline_formset(Author, Book, deletable=False, extra=3)
|
||||
>>> author = Author.objects.get(name='Charles Baudelaire')
|
||||
|
||||
>>> formset = AuthorBooksFormSet(author)
|
||||
>>> for form in formset.forms:
|
||||
... print form.as_p()
|
||||
<p><label for="id_book_set-0-title">Title:</label> <input id="id_book_set-0-title" type="text" name="book_set-0-title" maxlength="100" /><input type="hidden" name="book_set-0-id" id="id_book_set-0-id" /></p>
|
||||
<p><label for="id_book_set-1-title">Title:</label> <input id="id_book_set-1-title" type="text" name="book_set-1-title" maxlength="100" /><input type="hidden" name="book_set-1-id" id="id_book_set-1-id" /></p>
|
||||
<p><label for="id_book_set-2-title">Title:</label> <input id="id_book_set-2-title" type="text" name="book_set-2-title" maxlength="100" /><input type="hidden" name="book_set-2-id" id="id_book_set-2-id" /></p>
|
||||
|
||||
>>> data = {
|
||||
... 'book_set-COUNT': '3',
|
||||
... 'book_set-0-title': 'Les Fleurs du Mal',
|
||||
... 'book_set-1-title': '',
|
||||
... 'book_set-2-title': '',
|
||||
... }
|
||||
|
||||
>>> formset = AuthorBooksFormSet(author, data=data)
|
||||
>>> formset.is_valid()
|
||||
True
|
||||
|
||||
>>> formset.save()
|
||||
[<Book: Les Fleurs du Mal>]
|
||||
|
||||
>>> for book in author.book_set.all():
|
||||
... print book.title
|
||||
Les Fleurs du Mal
|
||||
|
||||
|
||||
Now that we've added a book to Charles Baudelaire, let's try adding another
|
||||
one. This time though, an edit form will be available for every existing
|
||||
book.
|
||||
|
||||
>>> AuthorBooksFormSet = inline_formset(Author, Book, deletable=False, extra=2)
|
||||
>>> author = Author.objects.get(name='Charles Baudelaire')
|
||||
|
||||
>>> formset = AuthorBooksFormSet(author)
|
||||
>>> for form in formset.forms:
|
||||
... print form.as_p()
|
||||
<p><label for="id_book_set-0-title">Title:</label> <input id="id_book_set-0-title" type="text" name="book_set-0-title" value="Les Fleurs du Mal" maxlength="100" /><input type="hidden" name="book_set-0-id" value="1" id="id_book_set-0-id" /></p>
|
||||
<p><label for="id_book_set-1-title">Title:</label> <input id="id_book_set-1-title" type="text" name="book_set-1-title" maxlength="100" /><input type="hidden" name="book_set-1-id" id="id_book_set-1-id" /></p>
|
||||
<p><label for="id_book_set-2-title">Title:</label> <input id="id_book_set-2-title" type="text" name="book_set-2-title" maxlength="100" /><input type="hidden" name="book_set-2-id" id="id_book_set-2-id" /></p>
|
||||
|
||||
>>> data = {
|
||||
... 'book_set-COUNT': '3',
|
||||
... 'book_set-0-id': '1',
|
||||
... 'book_set-0-title': 'Les Fleurs du Mal',
|
||||
... 'book_set-1-title': 'Le Spleen de Paris',
|
||||
... 'book_set-2-title': '',
|
||||
... }
|
||||
|
||||
>>> formset = AuthorBooksFormSet(author, data=data)
|
||||
>>> formset.is_valid()
|
||||
True
|
||||
|
||||
>>> formset.save()
|
||||
[<Book: Les Fleurs du Mal>, <Book: Le Spleen de Paris>]
|
||||
|
||||
As you can see, 'Le Spleen de Paris' is now a book belonging to Charles Baudelaire.
|
||||
|
||||
>>> for book in author.book_set.order_by('title'):
|
||||
... print book.title
|
||||
Le Spleen de Paris
|
||||
Les Fleurs du Mal
|
||||
|
||||
"""}
|
Loading…
x
Reference in New Issue
Block a user