diff options
| author | Russell Keith-Magee <russell@keith-magee.com> | 2009-05-02 07:03:33 +0000 |
|---|---|---|
| committer | Russell Keith-Magee <russell@keith-magee.com> | 2009-05-02 07:03:33 +0000 |
| commit | fbf5eaac9460a0f97187b80c9ac16b9aa023802e (patch) | |
| tree | 94569c7763cebdeafd3564e87f88a9a146387a16 | |
| parent | e43ace720cad6e0f000332e9dc4cba8af4be3bf6 (diff) | |
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
| -rw-r--r-- | django/db/models/fields/related.py | 5 | ||||
| -rw-r--r-- | tests/modeltests/model_forms/models.py | 24 | ||||
| -rw-r--r-- | tests/regressiontests/forms/forms.py | 48 | ||||
| -rw-r--r-- | tests/regressiontests/model_forms_regress/models.py | 14 | ||||
| -rw-r--r-- | tests/regressiontests/model_forms_regress/tests.py | 43 |
5 files changed, 122 insertions, 12 deletions
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index f2723a18c7..679b37cfca 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -948,7 +948,10 @@ class ManyToManyField(RelatedField, Field): # If initial is passed in, it's a list of related objects, but the # MultipleChoiceField takes a list of IDs. 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) def db_type(self): diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py index 16b50145d7..c5795b7e56 100644 --- a/tests/modeltests/model_forms/models.py +++ b/tests/modeltests/model_forms/models.py @@ -613,6 +613,30 @@ Add some categories and test the many-to-many form output. <option value="3">Third test</option> </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', ... 'writer': u'1', 'article': u'Hello.', 'categories': [u'1', u'2']}, instance=new_art) >>> new_art = f.save() diff --git a/tests/regressiontests/forms/forms.py b/tests/regressiontests/forms/forms.py index 642cd203ca..bf9623fe77 100644 --- a/tests/regressiontests/forms/forms.py +++ b/tests/regressiontests/forms/forms.py @@ -1146,37 +1146,63 @@ possible to specify callable data. >>> class UserRegistration(Form): ... username = CharField(max_length=10) ... password = CharField(widget=PasswordInput) +... options = MultipleChoiceField(choices=[('f','foo'),('b','bar'),('w','whiz')]) We need to define functions that get called later. >>> def initial_django(): ... return 'django' >>> def initial_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. ->>> 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() <li>Username: <input type="text" name="username" value="django" maxlength="10" /></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. ->>> 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() <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>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) >>> 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>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() <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>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. 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'. ->>> p = UserRegistration({'password': 'secret'}, initial={'username': initial_django}) +>>> p = UserRegistration({'password': 'secret'}, initial={'username': initial_django, 'options': initial_options}) >>> p.errors['username'] [u'This field is required.'] >>> p.is_valid() @@ -1187,14 +1213,26 @@ then the latter will get precedence. >>> class UserRegistration(Form): ... username = CharField(max_length=10, initial=initial_django) ... password = CharField(widget=PasswordInput) +... options = MultipleChoiceField(choices=[('f','foo'),('b','bar'),('w','whiz')], initial=initial_other_options) + >>> p = UserRegistration(auto_id=False) >>> print p.as_ul() <li>Username: <input type="text" name="username" value="django" maxlength="10" /></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() <li>Username: <input type="text" name="username" value="stephane" maxlength="10" /></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 ################################################################### diff --git a/tests/regressiontests/model_forms_regress/models.py b/tests/regressiontests/model_forms_regress/models.py index 50a2d61531..8c3f97e947 100644 --- a/tests/regressiontests/model_forms_regress/models.py +++ b/tests/regressiontests/model_forms_regress/models.py @@ -14,3 +14,17 @@ class Triple(models.Model): class FilePathModel(models.Model): 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 diff --git a/tests/regressiontests/model_forms_regress/tests.py b/tests/regressiontests/model_forms_regress/tests.py index 6547367812..56d04af061 100644 --- a/tests/regressiontests/model_forms_regress/tests.py +++ b/tests/regressiontests/model_forms_regress/tests.py @@ -1,18 +1,22 @@ +from datetime import date + from django import db from django import forms +from django.forms.models import modelform_factory from django.conf import settings from django.test import TestCase -from models import Person, Triple, FilePathModel + +from models import Person, Triple, FilePathModel, Article, Publication class ModelMultipleChoiceFieldTests(TestCase): - + def setUp(self): self.old_debug = settings.DEBUG settings.DEBUG = True - + def tearDown(self): settings.DEBUG = self.old_debug - + def test_model_multiple_choice_number_of_queries(self): """ Test that ModelMultipleChoiceField does O(1) queries instead of @@ -20,11 +24,11 @@ class ModelMultipleChoiceFieldTests(TestCase): """ for i in range(30): Person.objects.create(name="Person %s" % i) - + db.reset_queries() f = forms.ModelMultipleChoiceField(queryset=Person.objects.all()) selected = f.clean([1, 3, 5, 7, 9]) - self.assertEquals(len(db.connection.queries), 1) + self.assertEquals(len(db.connection.queries), 1) class TripleForm(forms.ModelForm): class Meta: @@ -59,3 +63,30 @@ class FilePathFieldTests(TestCase): names = [p[1] for p in form['path'].field.choices] names.sort() 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>""") + + |
