Fixed #10349 -- Modified ManyToManyFields to allow initial form values to be callables. Thanks to fas for the report and patch.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@10652 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
e43ace720c
commit
fbf5eaac94
|
@ -948,7 +948,10 @@ class ManyToManyField(RelatedField, Field):
|
||||||
# If initial is passed in, it's a list of related objects, but the
|
# If initial is passed in, it's a list of related objects, but the
|
||||||
# MultipleChoiceField takes a list of IDs.
|
# MultipleChoiceField takes a list of IDs.
|
||||||
if defaults.get('initial') is not None:
|
if defaults.get('initial') is not None:
|
||||||
defaults['initial'] = [i._get_pk_val() for i in defaults['initial']]
|
initial = defaults['initial']
|
||||||
|
if callable(initial):
|
||||||
|
initial = initial()
|
||||||
|
defaults['initial'] = [i._get_pk_val() for i in initial]
|
||||||
return super(ManyToManyField, self).formfield(**defaults)
|
return super(ManyToManyField, self).formfield(**defaults)
|
||||||
|
|
||||||
def db_type(self):
|
def db_type(self):
|
||||||
|
|
|
@ -613,6 +613,30 @@ Add some categories and test the many-to-many form output.
|
||||||
<option value="3">Third test</option>
|
<option value="3">Third test</option>
|
||||||
</select> Hold down "Control", or "Command" on a Mac, to select more than one.</li>
|
</select> Hold down "Control", or "Command" on a Mac, to select more than one.</li>
|
||||||
|
|
||||||
|
Initial values can be provided for model forms
|
||||||
|
>>> f = TestArticleForm(auto_id=False, initial={'headline': 'Your headline here', 'categories': ['1','2']})
|
||||||
|
>>> print f.as_ul()
|
||||||
|
<li>Headline: <input type="text" name="headline" value="Your headline here" maxlength="50" /></li>
|
||||||
|
<li>Slug: <input type="text" name="slug" maxlength="50" /></li>
|
||||||
|
<li>Pub date: <input type="text" name="pub_date" /></li>
|
||||||
|
<li>Writer: <select name="writer">
|
||||||
|
<option value="" selected="selected">---------</option>
|
||||||
|
<option value="1">Mike Royko</option>
|
||||||
|
<option value="2">Bob Woodward</option>
|
||||||
|
</select></li>
|
||||||
|
<li>Article: <textarea rows="10" cols="40" name="article"></textarea></li>
|
||||||
|
<li>Status: <select name="status">
|
||||||
|
<option value="" selected="selected">---------</option>
|
||||||
|
<option value="1">Draft</option>
|
||||||
|
<option value="2">Pending</option>
|
||||||
|
<option value="3">Live</option>
|
||||||
|
</select></li>
|
||||||
|
<li>Categories: <select multiple="multiple" name="categories">
|
||||||
|
<option value="1" selected="selected">Entertainment</option>
|
||||||
|
<option value="2" selected="selected">It's a test</option>
|
||||||
|
<option value="3">Third test</option>
|
||||||
|
</select> Hold down "Control", or "Command" on a Mac, to select more than one.</li>
|
||||||
|
|
||||||
>>> f = TestArticleForm({'headline': u'New headline', 'slug': u'new-headline', 'pub_date': u'1988-01-04',
|
>>> f = TestArticleForm({'headline': u'New headline', 'slug': u'new-headline', 'pub_date': u'1988-01-04',
|
||||||
... 'writer': u'1', 'article': u'Hello.', 'categories': [u'1', u'2']}, instance=new_art)
|
... 'writer': u'1', 'article': u'Hello.', 'categories': [u'1', u'2']}, instance=new_art)
|
||||||
>>> new_art = f.save()
|
>>> new_art = f.save()
|
||||||
|
|
|
@ -1146,37 +1146,63 @@ possible to specify callable data.
|
||||||
>>> class UserRegistration(Form):
|
>>> class UserRegistration(Form):
|
||||||
... username = CharField(max_length=10)
|
... username = CharField(max_length=10)
|
||||||
... password = CharField(widget=PasswordInput)
|
... password = CharField(widget=PasswordInput)
|
||||||
|
... options = MultipleChoiceField(choices=[('f','foo'),('b','bar'),('w','whiz')])
|
||||||
|
|
||||||
We need to define functions that get called later.
|
We need to define functions that get called later.
|
||||||
>>> def initial_django():
|
>>> def initial_django():
|
||||||
... return 'django'
|
... return 'django'
|
||||||
>>> def initial_stephane():
|
>>> def initial_stephane():
|
||||||
... return 'stephane'
|
... return 'stephane'
|
||||||
|
>>> def initial_options():
|
||||||
|
... return ['f','b']
|
||||||
|
>>> def initial_other_options():
|
||||||
|
... return ['b','w']
|
||||||
|
|
||||||
|
|
||||||
Here, we're not submitting any data, so the initial value will be displayed.
|
Here, we're not submitting any data, so the initial value will be displayed.
|
||||||
>>> p = UserRegistration(initial={'username': initial_django}, auto_id=False)
|
>>> p = UserRegistration(initial={'username': initial_django, 'options': initial_options}, auto_id=False)
|
||||||
>>> print p.as_ul()
|
>>> print p.as_ul()
|
||||||
<li>Username: <input type="text" name="username" value="django" maxlength="10" /></li>
|
<li>Username: <input type="text" name="username" value="django" maxlength="10" /></li>
|
||||||
<li>Password: <input type="password" name="password" /></li>
|
<li>Password: <input type="password" name="password" /></li>
|
||||||
|
<li>Options: <select multiple="multiple" name="options">
|
||||||
|
<option value="f" selected="selected">foo</option>
|
||||||
|
<option value="b" selected="selected">bar</option>
|
||||||
|
<option value="w">whiz</option>
|
||||||
|
</select></li>
|
||||||
|
|
||||||
The 'initial' parameter is meaningless if you pass data.
|
The 'initial' parameter is meaningless if you pass data.
|
||||||
>>> p = UserRegistration({}, initial={'username': initial_django}, auto_id=False)
|
>>> p = UserRegistration({}, initial={'username': initial_django, 'options': initial_options}, auto_id=False)
|
||||||
>>> print p.as_ul()
|
>>> print p.as_ul()
|
||||||
<li><ul class="errorlist"><li>This field is required.</li></ul>Username: <input type="text" name="username" maxlength="10" /></li>
|
<li><ul class="errorlist"><li>This field is required.</li></ul>Username: <input type="text" name="username" maxlength="10" /></li>
|
||||||
<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li>
|
<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li>
|
||||||
|
<li><ul class="errorlist"><li>This field is required.</li></ul>Options: <select multiple="multiple" name="options">
|
||||||
|
<option value="f">foo</option>
|
||||||
|
<option value="b">bar</option>
|
||||||
|
<option value="w">whiz</option>
|
||||||
|
</select></li>
|
||||||
>>> p = UserRegistration({'username': u''}, initial={'username': initial_django}, auto_id=False)
|
>>> p = UserRegistration({'username': u''}, initial={'username': initial_django}, auto_id=False)
|
||||||
>>> print p.as_ul()
|
>>> print p.as_ul()
|
||||||
<li><ul class="errorlist"><li>This field is required.</li></ul>Username: <input type="text" name="username" maxlength="10" /></li>
|
<li><ul class="errorlist"><li>This field is required.</li></ul>Username: <input type="text" name="username" maxlength="10" /></li>
|
||||||
<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li>
|
<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li>
|
||||||
>>> p = UserRegistration({'username': u'foo'}, initial={'username': initial_django}, auto_id=False)
|
<li><ul class="errorlist"><li>This field is required.</li></ul>Options: <select multiple="multiple" name="options">
|
||||||
|
<option value="f">foo</option>
|
||||||
|
<option value="b">bar</option>
|
||||||
|
<option value="w">whiz</option>
|
||||||
|
</select></li>
|
||||||
|
>>> p = UserRegistration({'username': u'foo', 'options':['f','b']}, initial={'username': initial_django}, auto_id=False)
|
||||||
>>> print p.as_ul()
|
>>> print p.as_ul()
|
||||||
<li>Username: <input type="text" name="username" value="foo" maxlength="10" /></li>
|
<li>Username: <input type="text" name="username" value="foo" maxlength="10" /></li>
|
||||||
<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li>
|
<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li>
|
||||||
|
<li>Options: <select multiple="multiple" name="options">
|
||||||
|
<option value="f" selected="selected">foo</option>
|
||||||
|
<option value="b" selected="selected">bar</option>
|
||||||
|
<option value="w">whiz</option>
|
||||||
|
</select></li>
|
||||||
|
|
||||||
A callable 'initial' value is *not* used as a fallback if data is not provided.
|
A callable 'initial' value is *not* used as a fallback if data is not provided.
|
||||||
In this example, we don't provide a value for 'username', and the form raises a
|
In this example, we don't provide a value for 'username', and the form raises a
|
||||||
validation error rather than using the initial value for 'username'.
|
validation error rather than using the initial value for 'username'.
|
||||||
>>> p = UserRegistration({'password': 'secret'}, initial={'username': initial_django})
|
>>> p = UserRegistration({'password': 'secret'}, initial={'username': initial_django, 'options': initial_options})
|
||||||
>>> p.errors['username']
|
>>> p.errors['username']
|
||||||
[u'This field is required.']
|
[u'This field is required.']
|
||||||
>>> p.is_valid()
|
>>> p.is_valid()
|
||||||
|
@ -1187,14 +1213,26 @@ then the latter will get precedence.
|
||||||
>>> class UserRegistration(Form):
|
>>> class UserRegistration(Form):
|
||||||
... username = CharField(max_length=10, initial=initial_django)
|
... username = CharField(max_length=10, initial=initial_django)
|
||||||
... password = CharField(widget=PasswordInput)
|
... password = CharField(widget=PasswordInput)
|
||||||
|
... options = MultipleChoiceField(choices=[('f','foo'),('b','bar'),('w','whiz')], initial=initial_other_options)
|
||||||
|
|
||||||
>>> p = UserRegistration(auto_id=False)
|
>>> p = UserRegistration(auto_id=False)
|
||||||
>>> print p.as_ul()
|
>>> print p.as_ul()
|
||||||
<li>Username: <input type="text" name="username" value="django" maxlength="10" /></li>
|
<li>Username: <input type="text" name="username" value="django" maxlength="10" /></li>
|
||||||
<li>Password: <input type="password" name="password" /></li>
|
<li>Password: <input type="password" name="password" /></li>
|
||||||
>>> p = UserRegistration(initial={'username': initial_stephane}, auto_id=False)
|
<li>Options: <select multiple="multiple" name="options">
|
||||||
|
<option value="f">foo</option>
|
||||||
|
<option value="b" selected="selected">bar</option>
|
||||||
|
<option value="w" selected="selected">whiz</option>
|
||||||
|
</select></li>
|
||||||
|
>>> p = UserRegistration(initial={'username': initial_stephane, 'options': initial_options}, auto_id=False)
|
||||||
>>> print p.as_ul()
|
>>> print p.as_ul()
|
||||||
<li>Username: <input type="text" name="username" value="stephane" maxlength="10" /></li>
|
<li>Username: <input type="text" name="username" value="stephane" maxlength="10" /></li>
|
||||||
<li>Password: <input type="password" name="password" /></li>
|
<li>Password: <input type="password" name="password" /></li>
|
||||||
|
<li>Options: <select multiple="multiple" name="options">
|
||||||
|
<option value="f" selected="selected">foo</option>
|
||||||
|
<option value="b" selected="selected">bar</option>
|
||||||
|
<option value="w">whiz</option>
|
||||||
|
</select></li>
|
||||||
|
|
||||||
# Help text ###################################################################
|
# Help text ###################################################################
|
||||||
|
|
||||||
|
|
|
@ -14,3 +14,17 @@ class Triple(models.Model):
|
||||||
|
|
||||||
class FilePathModel(models.Model):
|
class FilePathModel(models.Model):
|
||||||
path = models.FilePathField(path=os.path.dirname(__file__), match=".*\.py$", blank=True)
|
path = models.FilePathField(path=os.path.dirname(__file__), match=".*\.py$", blank=True)
|
||||||
|
|
||||||
|
class Publication(models.Model):
|
||||||
|
title = models.CharField(max_length=30)
|
||||||
|
date = models.DateField()
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.title
|
||||||
|
|
||||||
|
class Article(models.Model):
|
||||||
|
headline = models.CharField(max_length=100)
|
||||||
|
publications = models.ManyToManyField(Publication)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.headline
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
from django import db
|
from django import db
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.forms.models import modelform_factory
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from models import Person, Triple, FilePathModel
|
|
||||||
|
from models import Person, Triple, FilePathModel, Article, Publication
|
||||||
|
|
||||||
class ModelMultipleChoiceFieldTests(TestCase):
|
class ModelMultipleChoiceFieldTests(TestCase):
|
||||||
|
|
||||||
|
@ -59,3 +63,30 @@ class FilePathFieldTests(TestCase):
|
||||||
names = [p[1] for p in form['path'].field.choices]
|
names = [p[1] for p in form['path'].field.choices]
|
||||||
names.sort()
|
names.sort()
|
||||||
self.assertEqual(names, ['---------', '__init__.py', 'models.py', 'tests.py'])
|
self.assertEqual(names, ['---------', '__init__.py', 'models.py', 'tests.py'])
|
||||||
|
|
||||||
|
class ManyToManyCallableInitialTests(TestCase):
|
||||||
|
def test_callable(self):
|
||||||
|
"Regression for #10349: A callable can be provided as the initial value for an m2m field"
|
||||||
|
|
||||||
|
# Set up a callable initial value
|
||||||
|
def formfield_for_dbfield(db_field, **kwargs):
|
||||||
|
if db_field.name == 'publications':
|
||||||
|
kwargs['initial'] = lambda: Publication.objects.all().order_by('date')[:2]
|
||||||
|
return db_field.formfield(**kwargs)
|
||||||
|
|
||||||
|
# Set up some Publications to use as data
|
||||||
|
Publication(title="First Book", date=date(2007,1,1)).save()
|
||||||
|
Publication(title="Second Book", date=date(2008,1,1)).save()
|
||||||
|
Publication(title="Third Book", date=date(2009,1,1)).save()
|
||||||
|
|
||||||
|
# Create a ModelForm, instantiate it, and check that the output is as expected
|
||||||
|
ModelForm = modelform_factory(Article, formfield_callback=formfield_for_dbfield)
|
||||||
|
form = ModelForm()
|
||||||
|
self.assertEquals(form.as_ul(), u"""<li><label for="id_headline">Headline:</label> <input id="id_headline" type="text" name="headline" maxlength="100" /></li>
|
||||||
|
<li><label for="id_publications">Publications:</label> <select multiple="multiple" name="publications" id="id_publications">
|
||||||
|
<option value="1" selected="selected">First Book</option>
|
||||||
|
<option value="2" selected="selected">Second Book</option>
|
||||||
|
<option value="3">Third Book</option>
|
||||||
|
</select> Hold down "Control", or "Command" on a Mac, to select more than one.</li>""")
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue