diff --git a/AUTHORS b/AUTHORS index d6aa7fb2a1..f5d274ebc5 100644 --- a/AUTHORS +++ b/AUTHORS @@ -78,6 +78,7 @@ answer newbie questions, and generally made Django that much better: flavio.curella@gmail.com Jure Cuhalev dackze+django@gmail.com + David Danier Dirk Datzert Jonathan Daugherty (cygnus) dave@thebarproject.com diff --git a/django/conf/locale/zh_CN/LC_MESSAGES/django.mo b/django/conf/locale/zh_CN/LC_MESSAGES/django.mo index 0d6bf90c0e..3b04920654 100644 Binary files a/django/conf/locale/zh_CN/LC_MESSAGES/django.mo and b/django/conf/locale/zh_CN/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/zh_CN/LC_MESSAGES/django.po b/django/conf/locale/zh_CN/LC_MESSAGES/django.po index 2552b677bd..1f30a08b15 100644 --- a/django/conf/locale/zh_CN/LC_MESSAGES/django.po +++ b/django/conf/locale/zh_CN/LC_MESSAGES/django.po @@ -1344,7 +1344,7 @@ msgstr "四月" #: utils/dates.py:19 msgid "may" -msgstr "三月" +msgstr "五月" #: utils/dates.py:19 msgid "jun" diff --git a/django/conf/locale/zh_CN/LC_MESSAGES/djangojs.mo b/django/conf/locale/zh_CN/LC_MESSAGES/djangojs.mo index ec7580a862..f0127a55a0 100644 Binary files a/django/conf/locale/zh_CN/LC_MESSAGES/djangojs.mo and b/django/conf/locale/zh_CN/LC_MESSAGES/djangojs.mo differ diff --git a/django/conf/locale/zh_CN/LC_MESSAGES/djangojs.po b/django/conf/locale/zh_CN/LC_MESSAGES/djangojs.po index 610e61d17a..03abe3708c 100644 --- a/django/conf/locale/zh_CN/LC_MESSAGES/djangojs.po +++ b/django/conf/locale/zh_CN/LC_MESSAGES/djangojs.po @@ -46,7 +46,7 @@ msgstr "清除全部" #: contrib/admin/media/js/dateparse.js:32 #: contrib/admin/media/js/calendar.js:24 msgid "January February March April May June July August September October November December" -msgstr "一月 二月 三月 四月 五月 六月 六月 七月 八月 九月 十月 十一月 十二月" +msgstr "一月 二月 三月 四月 五月 六月 七月 八月 九月 十月 十一月 十二月" #: contrib/admin/media/js/dateparse.js:33 msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday" diff --git a/django/contrib/localflavor/is_/forms.py b/django/contrib/localflavor/is_/forms.py index d052acf579..41727d8fa3 100644 --- a/django/contrib/localflavor/is_/forms.py +++ b/django/contrib/localflavor/is_/forms.py @@ -41,7 +41,7 @@ class ISIdNumberField(RegexField): method is modulo 11. """ check = [3, 2, 7, 6, 5, 4, 3, 2, 1, 0] - return sum(int(value[i]) * check[i] for i in range(10)) % 11 == 0 + return sum([int(value[i]) * check[i] for i in range(10)]) % 11 == 0 def _format(self, value): """ diff --git a/django/contrib/webdesign/lorem_ipsum.py b/django/contrib/webdesign/lorem_ipsum.py index b5a3d3b5db..6226bbef1b 100644 --- a/django/contrib/webdesign/lorem_ipsum.py +++ b/django/contrib/webdesign/lorem_ipsum.py @@ -1,66 +1,69 @@ -""" -Utility functions for generating "lorem ipsum" Latin text. -""" - -import random - -COMMON_P = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.' -WORDS = ('exercitationem', 'perferendis', 'perspiciatis', 'laborum', 'eveniet', 'sunt', 'iure', 'nam', 'nobis', 'eum', 'cum', 'officiis', 'excepturi', 'odio', 'consectetur', 'quasi', 'aut', 'quisquam', 'vel', 'eligendi', 'itaque', 'non', 'odit', 'tempore', 'quaerat', 'dignissimos', 'facilis', 'neque', 'nihil', 'expedita', 'vitae', 'vero', 'ipsum', 'nisi', 'animi', 'cumque', 'pariatur', 'velit', 'modi', 'natus', 'iusto', 'eaque', 'sequi', 'illo', 'sed', 'ex', 'et', 'voluptatibus', 'tempora', 'veritatis', 'ratione', 'assumenda', 'incidunt', 'nostrum', 'placeat', 'aliquid', 'fuga', 'provident', 'praesentium', 'rem', 'necessitatibus', 'suscipit', 'adipisci', 'quidem', 'possimus', 'voluptas', 'debitis', 'sint', 'accusantium', 'unde', 'sapiente', 'voluptate', 'qui', 'aspernatur', 'laudantium', 'soluta', 'amet', 'quo', 'aliquam', 'saepe', 'culpa', 'libero', 'ipsa', 'dicta', 'reiciendis', 'nesciunt', 'doloribus', 'autem', 'impedit', 'minima', 'maiores', 'repudiandae', 'ipsam', 'obcaecati', 'ullam', 'enim', 'totam', 'delectus', 'ducimus', 'quis', 'voluptates', 'dolores', 'molestiae', 'harum', 'dolorem', 'quia', 'voluptatem', 'molestias', 'magni', 'distinctio', 'omnis', 'illum', 'dolorum', 'voluptatum', 'ea', 'quas', 'quam', 'corporis', 'quae', 'blanditiis', 'atque', 'deserunt', 'laboriosam', 'earum', 'consequuntur', 'hic', 'cupiditate', 'quibusdam', 'accusamus', 'ut', 'rerum', 'error', 'minus', 'eius', 'ab', 'ad', 'nemo', 'fugit', 'officia', 'at', 'in', 'id', 'quos', 'reprehenderit', 'numquam', 'iste', 'fugiat', 'sit', 'inventore', 'beatae', 'repellendus', 'magnam', 'recusandae', 'quod', 'explicabo', 'doloremque', 'aperiam', 'consequatur', 'asperiores', 'commodi', 'optio', 'dolor', 'labore', 'temporibus', 'repellat', 'veniam', 'architecto', 'est', 'esse', 'mollitia', 'nulla', 'a', 'similique', 'eos', 'alias', 'dolore', 'tenetur', 'deleniti', 'porro', 'facere', 'maxime', 'corrupti') -COMMON_WORDS = ('lorem', 'ipsum', 'dolor', 'sit', 'amet', 'consectetur', 'adipisicing', 'elit', 'sed', 'do', 'eiusmod', 'tempor', 'incididunt', 'ut', 'labore', 'et', 'dolore', 'magna', 'aliqua') - -def sentence(): - """ - Returns a randomly generated sentence of lorem ipsum text. - - The first word is capitalized, and the sentence ends in either a period or - question mark. Commas are added at random. - """ - # Determine the number of comma-separated sections and number of words in - # each section for this sentence. - sections = [' '.join(random.sample(WORDS, random.randint(3, 12))) for i in range(random.randint(1, 5))] - s = ', '.join(sections) - # Convert to sentence case and add end punctuation. - return '%s%s%s' % (s[0].upper(), s[1:], random.choice('?.')) - -def paragraph(): - """ - Returns a randomly generated paragraph of lorem ipsum text. - - The paragraph consists of between 1 and 4 sentences, inclusive. - """ - return ' '.join([sentence() for i in range(random.randint(1, 4))]) - -def paragraphs(count, common=True): - """ - Returns a list of paragraphs as returned by paragraph(). - - If `common` is True, then the first paragraph will be the standard - 'lorem ipsum' paragraph. Otherwise, the first paragraph will be random - Latin text. Either way, subsequent paragraphs will be random Latin text. - """ - paras = [] - for i in range(count): - if common and i == 0: - paras.append(COMMON_P) - else: - paras.append(paragraph()) - return paras - -def words(count, common=True): - """ - Returns a string of `count` lorem ipsum words separated by a single space. - - If `common` is True, then the first 19 words will be the standard - 'lorem ipsum' words. Otherwise, all words will be selected randomly. - """ - if common: - word_list = list(COMMON_WORDS) - else: - word_list = [] - c = len(word_list) - if count > c: - count = min(count - c, len(WORDS)) - word_list += random.sample(WORDS, count - c) - else: - word_list = word_list[:count] - return ' '.join(word_list) +""" +Utility functions for generating "lorem ipsum" Latin text. +""" + +import random + +COMMON_P = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.' +WORDS = ('exercitationem', 'perferendis', 'perspiciatis', 'laborum', 'eveniet', 'sunt', 'iure', 'nam', 'nobis', 'eum', 'cum', 'officiis', 'excepturi', 'odio', 'consectetur', 'quasi', 'aut', 'quisquam', 'vel', 'eligendi', 'itaque', 'non', 'odit', 'tempore', 'quaerat', 'dignissimos', 'facilis', 'neque', 'nihil', 'expedita', 'vitae', 'vero', 'ipsum', 'nisi', 'animi', 'cumque', 'pariatur', 'velit', 'modi', 'natus', 'iusto', 'eaque', 'sequi', 'illo', 'sed', 'ex', 'et', 'voluptatibus', 'tempora', 'veritatis', 'ratione', 'assumenda', 'incidunt', 'nostrum', 'placeat', 'aliquid', 'fuga', 'provident', 'praesentium', 'rem', 'necessitatibus', 'suscipit', 'adipisci', 'quidem', 'possimus', 'voluptas', 'debitis', 'sint', 'accusantium', 'unde', 'sapiente', 'voluptate', 'qui', 'aspernatur', 'laudantium', 'soluta', 'amet', 'quo', 'aliquam', 'saepe', 'culpa', 'libero', 'ipsa', 'dicta', 'reiciendis', 'nesciunt', 'doloribus', 'autem', 'impedit', 'minima', 'maiores', 'repudiandae', 'ipsam', 'obcaecati', 'ullam', 'enim', 'totam', 'delectus', 'ducimus', 'quis', 'voluptates', 'dolores', 'molestiae', 'harum', 'dolorem', 'quia', 'voluptatem', 'molestias', 'magni', 'distinctio', 'omnis', 'illum', 'dolorum', 'voluptatum', 'ea', 'quas', 'quam', 'corporis', 'quae', 'blanditiis', 'atque', 'deserunt', 'laboriosam', 'earum', 'consequuntur', 'hic', 'cupiditate', 'quibusdam', 'accusamus', 'ut', 'rerum', 'error', 'minus', 'eius', 'ab', 'ad', 'nemo', 'fugit', 'officia', 'at', 'in', 'id', 'quos', 'reprehenderit', 'numquam', 'iste', 'fugiat', 'sit', 'inventore', 'beatae', 'repellendus', 'magnam', 'recusandae', 'quod', 'explicabo', 'doloremque', 'aperiam', 'consequatur', 'asperiores', 'commodi', 'optio', 'dolor', 'labore', 'temporibus', 'repellat', 'veniam', 'architecto', 'est', 'esse', 'mollitia', 'nulla', 'a', 'similique', 'eos', 'alias', 'dolore', 'tenetur', 'deleniti', 'porro', 'facere', 'maxime', 'corrupti') +COMMON_WORDS = ('lorem', 'ipsum', 'dolor', 'sit', 'amet', 'consectetur', 'adipisicing', 'elit', 'sed', 'do', 'eiusmod', 'tempor', 'incididunt', 'ut', 'labore', 'et', 'dolore', 'magna', 'aliqua') + +def sentence(): + """ + Returns a randomly generated sentence of lorem ipsum text. + + The first word is capitalized, and the sentence ends in either a period or + question mark. Commas are added at random. + """ + # Determine the number of comma-separated sections and number of words in + # each section for this sentence. + sections = [' '.join(random.sample(WORDS, random.randint(3, 12))) for i in range(random.randint(1, 5))] + s = ', '.join(sections) + # Convert to sentence case and add end punctuation. + return '%s%s%s' % (s[0].upper(), s[1:], random.choice('?.')) + +def paragraph(): + """ + Returns a randomly generated paragraph of lorem ipsum text. + + The paragraph consists of between 1 and 4 sentences, inclusive. + """ + return ' '.join([sentence() for i in range(random.randint(1, 4))]) + +def paragraphs(count, common=True): + """ + Returns a list of paragraphs as returned by paragraph(). + + If `common` is True, then the first paragraph will be the standard + 'lorem ipsum' paragraph. Otherwise, the first paragraph will be random + Latin text. Either way, subsequent paragraphs will be random Latin text. + """ + paras = [] + for i in range(count): + if common and i == 0: + paras.append(COMMON_P) + else: + paras.append(paragraph()) + return paras + +def words(count, common=True): + """ + Returns a string of `count` lorem ipsum words separated by a single space. + + If `common` is True, then the first 19 words will be the standard + 'lorem ipsum' words. Otherwise, all words will be selected randomly. + """ + if common: + word_list = list(COMMON_WORDS) + else: + word_list = [] + c = len(word_list) + if count > c: + count -= c + while count > 0: + c = min(count, len(WORDS)) + count -= c + word_list += random.sample(WORDS, c) + else: + word_list = word_list[:count] + return ' '.join(word_list) diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py index 867ddd0148..ef0d06f6a1 100644 --- a/django/db/backends/postgresql/base.py +++ b/django/db/backends/postgresql/base.py @@ -233,7 +233,7 @@ def get_sql_sequence_reset(style, model_list): if isinstance(f, models.AutoField): output.append("%s setval('%s', (%s max(%s) %s %s));" % \ (style.SQL_KEYWORD('SELECT'), - style.SQL_FIELD('%s_%s_seq' % (model._meta.db_table, f.column)), + style.SQL_FIELD(quote_name('%s_%s_seq' % (model._meta.db_table, f.column))), style.SQL_KEYWORD('SELECT'), style.SQL_FIELD(quote_name(f.column)), style.SQL_KEYWORD('FROM'), @@ -242,7 +242,7 @@ def get_sql_sequence_reset(style, model_list): for f in model._meta.many_to_many: output.append("%s setval('%s', (%s max(%s) %s %s));" % \ (style.SQL_KEYWORD('SELECT'), - style.SQL_FIELD('%s_id_seq' % f.m2m_db_table()), + style.SQL_FIELD(quote_name('%s_id_seq' % f.m2m_db_table())), style.SQL_KEYWORD('SELECT'), style.SQL_FIELD(quote_name('id')), style.SQL_KEYWORD('FROM'), diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py index ed810504dd..d9a850957c 100644 --- a/django/db/backends/postgresql_psycopg2/base.py +++ b/django/db/backends/postgresql_psycopg2/base.py @@ -184,7 +184,7 @@ def get_sql_sequence_reset(style, model_list): if isinstance(f, models.AutoField): output.append("%s setval('%s', (%s max(%s) %s %s));" % \ (style.SQL_KEYWORD('SELECT'), - style.SQL_FIELD('%s_%s_seq' % (model._meta.db_table, f.column)), + style.SQL_FIELD(quote_name('%s_%s_seq' % (model._meta.db_table, f.column))), style.SQL_KEYWORD('SELECT'), style.SQL_FIELD(quote_name(f.column)), style.SQL_KEYWORD('FROM'), @@ -193,7 +193,7 @@ def get_sql_sequence_reset(style, model_list): for f in model._meta.many_to_many: output.append("%s setval('%s', (%s max(%s) %s %s));" % \ (style.SQL_KEYWORD('SELECT'), - style.SQL_FIELD('%s_id_seq' % f.m2m_db_table()), + style.SQL_FIELD(quote_name('%s_id_seq' % f.m2m_db_table())), style.SQL_KEYWORD('SELECT'), style.SQL_FIELD(quote_name('id')), style.SQL_KEYWORD('FROM'), diff --git a/django/http/__init__.py b/django/http/__init__.py index 4361796dcd..4c667e02af 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -108,6 +108,10 @@ class QueryDict(MultiValueDict): self._assert_mutable() MultiValueDict.__setitem__(self, key, value) + def __delitem__(self, key): + self._assert_mutable() + super(QueryDict, self).__delitem__(key) + def get(self, key, default=None): return str_to_unicode(MultiValueDict.get(self, key, default), self.encoding) diff --git a/django/newforms/forms.py b/django/newforms/forms.py index 9fafd20787..edd1992cf4 100644 --- a/django/newforms/forms.py +++ b/django/newforms/forms.py @@ -255,6 +255,8 @@ class BoundField(StrAndUnicode): attrs['id'] = auto_id if not self.form.is_bound: data = self.form.initial.get(self.name, self.field.initial) + if callable(data): + data = data() else: data = self.data return widget.render(self.html_name, data, attrs=attrs) diff --git a/django/newforms/models.py b/django/newforms/models.py index a60002b705..ea99ca9ea0 100644 --- a/django/newforms/models.py +++ b/django/newforms/models.py @@ -12,17 +12,7 @@ from widgets import Select, SelectMultiple, MultipleHiddenInput __all__ = ('save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields', 'ModelChoiceField', 'ModelMultipleChoiceField') -def model_save(self, commit=True): - """ - Creates and returns model instance according to self.clean_data. - - This method is created for any form_for_model Form. - """ - if self.errors: - raise ValueError("The %s could not be created because the data didn't validate." % self._model._meta.object_name) - return save_instance(self, self._model(), commit) - -def save_instance(form, instance, commit=True): +def save_instance(form, instance, fields=None, fail_message='saved', commit=True): """ Saves bound Form ``form``'s clean_data into model instance ``instance``. @@ -33,15 +23,19 @@ def save_instance(form, instance, commit=True): from django.db import models opts = instance.__class__._meta if form.errors: - raise ValueError("The %s could not be changed because the data didn't validate." % opts.object_name) + raise ValueError("The %s could not be %s because the data didn't validate." % (opts.object_name, fail_message)) clean_data = form.clean_data for f in opts.fields: if not f.editable or isinstance(f, models.AutoField) or not f.name in clean_data: continue + if fields and f.name not in fields: + continue setattr(instance, f.name, clean_data[f.name]) if commit: instance.save() for f in opts.many_to_many: + if fields and f.name not in fields: + continue if f.name in clean_data: setattr(instance, f.attname, clean_data[f.name]) # GOTCHA: If many-to-many data is given and commit=False, the many-to-many @@ -50,13 +44,19 @@ def save_instance(form, instance, commit=True): # exception in that case. return instance -def make_instance_save(instance): - "Returns the save() method for a form_for_instance Form." +def make_model_save(model, fields, fail_message): + "Returns the save() method for a Form." def save(self, commit=True): - return save_instance(self, instance, commit) + return save_instance(self, model(), fields, fail_message, commit) + return save + +def make_instance_save(instance, fields, fail_message): + "Returns the save() method for a Form." + def save(self, commit=True): + return save_instance(self, instance, fields, fail_message, commit) return save -def form_for_model(model, form=BaseForm, formfield_callback=lambda f: f.formfield()): +def form_for_model(model, form=BaseForm, fields=None, formfield_callback=lambda f: f.formfield()): """ Returns a Form class for the given Django model class. @@ -71,13 +71,16 @@ def form_for_model(model, form=BaseForm, formfield_callback=lambda f: f.formfiel for f in opts.fields + opts.many_to_many: if not f.editable: continue + if fields and not f.name in fields: + continue formfield = formfield_callback(f) if formfield: field_list.append((f.name, formfield)) - fields = SortedDictFromList(field_list) - return type(opts.object_name + 'Form', (form,), {'base_fields': fields, '_model': model, 'save': model_save}) + base_fields = SortedDictFromList(field_list) + return type(opts.object_name + 'Form', (form,), + {'base_fields': base_fields, '_model': model, 'save': make_model_save(model, fields, 'created')}) -def form_for_instance(instance, form=BaseForm, formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)): +def form_for_instance(instance, form=BaseForm, fields=None, formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)): """ Returns a Form class for the given Django model instance. @@ -94,13 +97,15 @@ def form_for_instance(instance, form=BaseForm, formfield_callback=lambda f, **kw for f in opts.fields + opts.many_to_many: if not f.editable: continue + if fields and not f.name in fields: + continue current_value = f.value_from_object(instance) formfield = formfield_callback(f, initial=current_value) if formfield: field_list.append((f.name, formfield)) - fields = SortedDictFromList(field_list) + base_fields = SortedDictFromList(field_list) return type(opts.object_name + 'InstanceForm', (form,), - {'base_fields': fields, '_model': model, 'save': make_instance_save(instance)}) + {'base_fields': base_fields, '_model': model, 'save': make_instance_save(instance, fields, 'changed')}) def form_for_fields(field_list): "Returns a Form class for the given list of Django database field instances." diff --git a/django/test/testcases.py b/django/test/testcases.py index 7c7a976641..45b7a2331d 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -1,4 +1,5 @@ import re, doctest, unittest +import sys from urlparse import urlparse from django.db import transaction from django.core import management, mail @@ -45,16 +46,16 @@ class TestCase(unittest.TestCase): if hasattr(self, 'fixtures'): management.load_data(self.fixtures, verbosity=0) mail.outbox = [] - - def run(self, result=None): - """Wrapper around default run method to perform common Django test set up. - This means that user-defined Test Cases aren't required to include a call - to super().setUp(). - + + def __call__(self, result=None): + """ + Wrapper around default __call__ method to perform common Django test + set up. This means that user-defined Test Cases aren't required to + include a call to super().setUp(). """ self.client = Client() self._pre_setup() - super(TestCase, self).run(result) + super(TestCase, self).__call__(result) def assertRedirects(self, response, expected_path, status_code=302, target_status_code=200): """Assert that a response redirected to a specific URL, and that the @@ -108,7 +109,7 @@ class TestCase(unittest.TestCase): for err in errors: if field: if field in context[form].errors: - self.assertTrue(err in context[form].errors[field], + self.failUnless(err in context[form].errors[field], "The field '%s' on form '%s' in context %d does not contain the error '%s' (actual errors: %s)" % (field, form, i, err, list(context[form].errors[field]))) elif field in context[form].fields: @@ -117,7 +118,7 @@ class TestCase(unittest.TestCase): else: self.fail("The form '%s' in context %d does not contain the field '%s'" % (form, i, field)) else: - self.assertTrue(err in context[form].non_field_errors(), + self.failUnless(err in context[form].non_field_errors(), "The form '%s' in context %d does not contain the non-field error '%s' (actual errors: %s)" % (form, i, err, list(context[form].non_field_errors()))) if not found_form: @@ -127,7 +128,7 @@ class TestCase(unittest.TestCase): "Assert that the template with the provided name was used in rendering the response" if isinstance(response.template, list): template_names = [t.name for t in response.template] - self.assertTrue(template_name in template_names, + self.failUnless(template_name in template_names, u"Template '%s' was not one of the templates used to render the response. Templates used: %s" % (template_name, u', '.join(template_names))) elif response.template: @@ -140,9 +141,9 @@ class TestCase(unittest.TestCase): def assertTemplateNotUsed(self, response, template_name): "Assert that the template with the provided name was NOT used in rendering the response" if isinstance(response.template, list): - self.assertFalse(template_name in [t.name for t in response.template], + self.failIf(template_name in [t.name for t in response.template], u"Template '%s' was used unexpectedly in rendering the response" % template_name) elif response.template: self.assertNotEqual(template_name, response.template.name, u"Template '%s' was used unexpectedly in rendering the response" % template_name) - + diff --git a/docs/i18n.txt b/docs/i18n.txt index 1d7a0063b2..27abadacc9 100644 --- a/docs/i18n.txt +++ b/docs/i18n.txt @@ -236,7 +236,7 @@ To pluralize, specify both the singular and plural forms with the ``{% plural %}`` tag, which appears within ``{% blocktrans %}`` and ``{% endblocktrans %}``. Example:: - {% blocktrans count list|count as counter %} + {% blocktrans count list|length as counter %} There is only one {{ name }} object. {% plural %} There are {{ counter }} {{ name }} objects. diff --git a/docs/install.txt b/docs/install.txt index 112a92d2f5..6be52aab0d 100644 --- a/docs/install.txt +++ b/docs/install.txt @@ -56,7 +56,7 @@ installed. either ``postgresql`` [for version 1] or ``postgresql_psycopg2`` [for version 2].) If you're on Windows, check out the unofficial `compiled Windows version`_. - + * If you're using MySQL, you'll need MySQLdb_, version 1.2.1p2 or higher. You will also want to read the database-specific notes for the `MySQL backend`_. @@ -75,16 +75,16 @@ installed. Remove any old versions of Django ================================= -If you are upgrading your installation of Django from a previous version, -you will need to uninstall the old Django version before installing the -new version. +If you are upgrading your installation of Django from a previous version, +you will need to uninstall the old Django version before installing the +new version. If you installed Django using ``setup.py install``, uninstalling -is as simple as deleting the ``django`` directory from your Python +is as simple as deleting the ``django`` directory from your Python ``site-packages``. -If you installed Django from a Python Egg, remove the Django ``.egg` file, -and remove the reference to the egg in the file named ``easy-install.pth``. +If you installed Django from a Python Egg, remove the Django ``.egg`` file, +and remove the reference to the egg in the file named ``easy-install.pth``. This file should also be located in your ``site-packages`` directory. .. admonition:: Where are my ``site-packages`` stored? @@ -92,7 +92,7 @@ This file should also be located in your ``site-packages`` directory. The location of the ``site-packages`` directory depends on the operating system, and the location in which Python was installed. However, the following locations are common: - + * If you're using Linux: ``/usr/lib/python2.X/site-packages`` * If you're using Windows: ``C:\Python2.X\lib\site-packages`` diff --git a/docs/newforms.txt b/docs/newforms.txt index ddb850f54c..123b554bde 100644 --- a/docs/newforms.txt +++ b/docs/newforms.txt @@ -870,6 +870,308 @@ custom ``Field`` classes. To do this, just create a subclass of mentioned above (``required``, ``label``, ``initial``, ``widget``, ``help_text``). +Generating forms for models +=========================== + +If you're building a database-driven app, chances are you'll have forms that +map closely to Django models. For instance, you might have a ``BlogComment`` +model, and you want to create a form that lets people submit comments. In this +case, it would be redundant to define the field types in your form, because +you've already defined the fields in your model. + +For this reason, Django provides a few helper functions that let you create a +``Form`` class from a Django model. + +``form_for_model()`` +-------------------- + +The method ``django.newforms.form_for_model()`` creates a form based on the +definition of a specific model. Pass it the model class, and it will return a +``Form`` class that contains a form field for each model field. + +For example:: + + >>> from django.newforms import form_for_model + + # Create the form class. + >>> ArticleForm = form_for_model(Article) + + # Create an empty form instance. + >>> f = ArticleForm() + +It bears repeating that ``form_for_model()`` takes the model *class*, not a +model instance, and it returns a ``Form`` *class*, not a ``Form`` instance. + +Field types +~~~~~~~~~~~ + +The generated ``Form`` class will have a form field for every model field. Each +model field has a corresponding default form field. For example, a +``CharField`` on a model is represented as a ``CharField`` on a form. A +model ``ManyToManyField`` is represented as a ``MultipleChoiceField``. Here is +the full list of conversions: + + =============================== ======================================== + Model field Form field + =============================== ======================================== + ``AutoField`` Not represented in the form + ``BooleanField`` ``BooleanField`` + ``CharField`` ``CharField`` with ``max_length`` set to + the model field's ``maxlength`` + ``CommaSeparatedIntegerField`` ``CharField`` + ``DateField`` ``DateField`` + ``DateTimeField`` ``DateTimeField`` + ``EmailField`` ``EmailField`` + ``FileField`` ``CharField`` + ``FilePathField`` ``CharField`` + ``FloatField`` ``CharField`` + ``ForeignKey`` ``ModelChoiceField`` (see below) + ``ImageField`` ``CharField`` + ``IntegerField`` ``IntegerField`` + ``IPAddressField`` ``CharField`` + ``ManyToManyField`` ``ModelMultipleChoiceField`` (see + below) + ``NullBooleanField`` ``CharField`` + ``PhoneNumberField`` ``USPhoneNumberField`` + (from ``django.contrib.localflavor.us``) + ``PositiveIntegerField`` ``IntegerField`` + ``PositiveSmallIntegerField`` ``IntegerField`` + ``SlugField`` ``CharField`` + ``SmallIntegerField`` ``IntegerField`` + ``TextField`` ``CharField`` with ``widget=Textarea`` + ``TimeField`` ``TimeField`` + ``URLField`` ``URLField`` with ``verify_exists`` set + to the model field's ``verify_exists`` + ``USStateField`` ``CharField`` with + ``widget=USStateSelect`` + (``USStateSelect`` is from + ``django.contrib.localflavor.us``) + ``XMLField`` ``CharField`` with ``widget=Textarea`` + =============================== ======================================== + +As you might expect, the ``ForeignKey`` and ``ManyToManyField`` model field +types are special cases: + + * ``ForeignKey`` is represented by ``django.newforms.ModelChoiceField``, + which is a ``ChoiceField`` whose choices are a model ``QuerySet``. + + * ``ManyToManyField`` is represented by + ``django.newforms.ModelMultipleChoiceField``, which is a + ``MultipleChoiceField`` whose choices are a model ``QuerySet``. + +In addition, each generated form field has attributes set as follows: + + * If the model field has ``blank=True``, then ``required`` is set to + ``False`` on the form field. Otherwise, ``required=True``. + + * The form field's ``label`` is set to the ``verbose_name`` of the model + field, with the first character capitalized. + + * The form field's ``help_text`` is set to the ``help_text`` of the model + field. + + * If the model field has ``choices`` set, then the form field's ``widget`` + will be set to ``Select``, with choices coming from the model field's + ``choices``. + +Finally, note that you can override the form field used for a given model +field. See "Overriding the default field types" below. + +A full example +~~~~~~~~~~~~~~ + +Consider this set of models:: + + from django.db import models + + TITLE_CHOICES = ( + ('MR', 'Mr.'), + ('MRS', 'Mrs.'), + ('MS', 'Ms.'), + ) + + class Author(models.Model): + name = models.CharField(maxlength=100) + title = models.CharField(maxlength=3, choices=TITLE_CHOICES) + birth_date = models.DateField(blank=True, null=True) + + def __str__(self): + return self.name + + class Book(models.Model): + name = models.CharField(maxlength=100) + authors = models.ManyToManyField(Author) + +With these models, a call to ``form_for_model(Author)`` would return a ``Form`` +class equivalent to this:: + + class AuthorForm(forms.Form): + name = forms.CharField(max_length=100) + title = forms.CharField(max_length=3, + widget=forms.Select(choices=TITLE_CHOICES)) + birth_date = forms.DateField(required=False) + +A call to ``form_for_model(Book)`` would return a ``Form`` class equivalent to +this:: + + class BookForm(forms.Form): + name = forms.CharField(max_length=100) + authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all()) + +The ``save()`` method +~~~~~~~~~~~~~~~~~~~~~ + +Every form produced by ``form_for_model()`` also has a ``save()`` method. This +method creates and saves a database object from the data bound to the form. For +example:: + + # Create a form instance from POST data. + >>> f = ArticleForm(request.POST) + + # Save a new Article object from the form's data. + >>> new_article = f.save() + +Note that ``save()`` will raise a ``ValueError`` if the data in the form +doesn't validate -- i.e., ``if form.errors``. + +Using an alternate base class +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you want to add custom methods to the form generated by +``form_for_model()``, write a class that extends ``django.newforms.BaseForm`` +and contains your custom methods. Then, use the ``form`` argument to +``form_for_model()`` to tell it to use your custom form as its base class. +For example:: + + # Create the new base class. + >>> class MyBase(BaseForm): + ... def my_method(self): + ... # Do whatever the method does + + # Create the form class with a different base class. + >>> ArticleForm = form_for_model(Article, form=MyBase) + + # Instantiate the form. + >>> f = ArticleForm() + + # Use the base class method. + >>> f.my_method() + +Using a subset of fields on the form +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**New in Django development version** + +In some cases, you may not want all the model fields to appear on the generated +form. There are two ways of telling ``form_for_model()`` to use only a subset +of the model fields: + + 1. Set ``editable=False`` on the model field. As a result, *any* form + created from the model via ``form_for_model()`` will not include that + field. + + 2. Use the ``fields`` argument to ``form_for_model()``. This argument, if + given, should be a list of field names to include in the form. + + For example, if you want a form for the ``Author`` model (defined above) + that includes only the ``name`` and ``title`` fields, you would specify + ``fields`` like this:: + + PartialArticleForm = form_for_model(Author, fields=('name', 'title')) + +.. note:: + + If you specify ``fields`` when creating a form with ``form_for_model()``, + make sure that the fields that are *not* specified can provide default + values, or are allowed to have a value of ``None``. If a field isn't + specified on a form, the object created from the form can't provide + a value for that attribute, which will prevent the new instance from + being saved. + +Overriding the default field types +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The default field types, as described in the "Field types" table above, are +sensible defaults; if you have a ``DateField`` in your model, chances are you'd +want that to be represented as a ``DateField`` in your form. But +``form_for_model()`` gives you the flexibility of changing the form field type +for a given model field. You do this by specifying a **formfield callback**. + +A formfield callback is a function that, when provided with a model field, +returns a form field instance. When constructing a form, ``form_for_model()`` +asks the formfield callback to provide form field types. + +By default, ``form_for_model()`` calls the ``formfield()`` method on the model +field:: + + def default_callback(field, **kwargs): + return field.formfield(**kwargs) + +The ``kwargs`` are any keyword arguments that might be passed to the form +field, such as ``required=True`` or ``label='Foo'``. + +For example, if you wanted to use ``MyDateFormField`` for any ``DateField`` +field on the model, you could define the callback:: + + >>> def my_callback(field, **kwargs): + ... if isinstance(field, models.DateField): + ... return MyDateFormField(**kwargs) + ... else: + ... return field.formfield(**kwargs) + + >>> ArticleForm = form_for_model(formfield_callback=my_callback) + +Note that your callback needs to handle *all* possible model field types, not +just the ones that you want to behave differently to the default. That's why +this example has an ``else`` clause that implements the default behavior. + +Finding the model associated with a form +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The model class that was used to construct the form is available +using the ``_model`` property of the generated form:: + + >>> ArticleForm = form_for_model(Article) + >>> ArticleForm._model + + +``form_for_instance()`` +----------------------- + +``form_for_instance()`` is like ``form_for_model()``, but it takes a model +instance instead of a model class:: + + # Create an Author. + >>> a = Author(name='Joe Smith', title='MR', birth_date=None) + >>> a.save() + + # Create a form for this particular Author. + >>> AuthorForm = form_for_instance(a) + + # Instantiate the form. + >>> f = AuthorForm() + +When a form created by ``form_for_instance()`` is created, the initial +data values for the form fields are drawn from the instance. However, +this data is not bound to the form. You will need to bind data to the +form before the form can be saved. + +When you call ``save()`` on a form created by ``form_for_instance()``, +the database instance will be updated. As in ``form_for_model()``, ``save()`` +will raise ``ValueError`` if the data doesn't validate. + +``form_for_instance()`` has ``form``, ``fields`` and ``formfield_callback`` +arguments that behave the same way as they do for ``form_for_model()``. + +When should you use ``form_for_model()`` and ``form_for_instance()``? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``form_for_model()`` and ``form_for_instance()`` functions are meant to be +shortcuts for the common case. If you want to create a form whose fields map to +more than one model, or a form that contains fields that *aren't* on a model, +you shouldn't use these shortcuts. Creating a ``Form`` class the "long" way +isn't that difficult, after all. + More coming soon ================ @@ -880,6 +1182,3 @@ what's possible. If you're really itching to learn and use this library, please be patient. We're working hard on finishing both the code and documentation. - -Widgets -======= diff --git a/docs/templates_python.txt b/docs/templates_python.txt index 853707f58c..08a287f572 100644 --- a/docs/templates_python.txt +++ b/docs/templates_python.txt @@ -717,7 +717,7 @@ object:: # split_contents() knows not to split quoted strings. tag_name, format_string = token.split_contents() except ValueError: - raise template.TemplateSyntaxError, "%r tag requires a single argument" % token.contents[0] + raise template.TemplateSyntaxError, "%r tag requires a single argument" % token.contents.split()[0] if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")): raise template.TemplateSyntaxError, "%r tag's argument should be in quotes" % tag_name return CurrentTimeNode(format_string[1:-1]) @@ -846,7 +846,7 @@ Now your tag should begin to look like this:: # split_contents() knows not to split quoted strings. tag_name, date_to_be_formatted, format_string = token.split_contents() except ValueError: - raise template.TemplateSyntaxError, "%r tag requires exactly two arguments" % token.contents[0] + raise template.TemplateSyntaxError, "%r tag requires exactly two arguments" % token.contents.split()[0] if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")): raise template.TemplateSyntaxError, "%r tag's argument should be in quotes" % tag_name return FormatTimeNode(date_to_be_formatted, format_string[1:-1]) @@ -1080,7 +1080,7 @@ class, like so:: # Splitting by None == splitting by spaces. tag_name, arg = token.contents.split(None, 1) except ValueError: - raise template.TemplateSyntaxError, "%r tag requires arguments" % token.contents[0] + raise template.TemplateSyntaxError, "%r tag requires arguments" % token.contents.split()[0] m = re.search(r'(.*?) as (\w+)', arg) if not m: raise template.TemplateSyntaxError, "%r tag had invalid arguments" % tag_name diff --git a/docs/testing.txt b/docs/testing.txt index b44b67c20a..dd4e9e17b2 100644 --- a/docs/testing.txt +++ b/docs/testing.txt @@ -177,7 +177,7 @@ tools that can be used to establish tests and test conditions. * `Test Client`_ * `TestCase`_ -* `Email services`_ +* `E-mail services`_ Test Client ----------- @@ -459,9 +459,9 @@ Emptying the test outbox **New in Django development version** At the start of each test case, in addition to installing fixtures, -Django clears the contents of the test email outbox. +Django clears the contents of the test e-mail outbox. -For more detail on email services during tests, see `Email services`_. +For more detail on e-mail services during tests, see `E-mail services`_. Assertions ~~~~~~~~~~ @@ -502,16 +502,17 @@ that can be useful in testing the behavior of web sites. Assert that the template with the given name was used in rendering the response. -Email services +E-mail services -------------- + **New in Django development version** -If your view makes use of the `Django email services`_, you don't really -want email to be sent every time you run a test using that view. +If your view makes use of the `Django e-mail services`_, you don't really +want e-mail to be sent every time you run a test using that view. When the Django test framework is initialized, it transparently replaces the normal `SMTPConnection`_ class with a dummy implementation that redirects all -email to a dummy outbox. This outbox, stored as ``django.core.mail.outbox``, +e-mail to a dummy outbox. This outbox, stored as ``django.core.mail.outbox``, is a simple list of all `EmailMessage`_ instances that have been sent. For example, during test conditions, it would be possible to run the following code:: @@ -541,7 +542,7 @@ to mail.outbox:: # Empty the test outbox mail.outbox = [] -.. _`Django email services`: ../email/ +.. _`Django e-mail services`: ../email/ .. _`SMTPConnection`: ../email/#the-emailmessage-and-smtpconnection-classes .. _`EmailMessage`: ../email/#the-emailmessage-and-smtpconnection-classes .. _`previously`: #emptying-the-test-outbox @@ -669,7 +670,7 @@ a number of utility methods in the ``django.test.utils`` module. ``teardown_test_environment()`` Performs any global post-test teardown, such as removing the instrumentation - of the template rendering system and restoring normal email services. + of the template rendering system and restoring normal e-mail services. ``create_test_db(verbosity=1, autoclobber=False)`` Creates a new test database, and run ``syncdb`` against it. diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py index 6855be0580..409cf83fcb 100644 --- a/tests/modeltests/model_forms/models.py +++ b/tests/modeltests/model_forms/models.py @@ -179,6 +179,18 @@ fields with the 'choices' attribute are represented by a ChoiceField.
Hold down "Control", or "Command" on a Mac, to select more than one. +You can restrict a form to a subset of the complete list of fields +by providing a 'fields' argument. If you try to save a +model created with such a form, you need to ensure that the fields +that are _not_ on the form have default values, or are allowed to have +a value of None. If a field isn't specified on a form, the object created +from the form can't provide a value for that field! +>>> PartialArticleForm = form_for_model(Article, fields=('headline','pub_date')) +>>> f = PartialArticleForm(auto_id=False) +>>> print f +Headline: +Pub date: + You can pass a custom Form class to form_for_model. Make sure it's a subclass of BaseForm, not Form. >>> class CustomForm(BaseForm): @@ -224,7 +236,23 @@ current values are inserted as 'initial' data in each Field. Hold down "Control", or "Command" on a Mac, to select more than one. ->>> f = TestArticleForm({'headline': u'New headline', 'pub_date': u'1988-01-04', 'writer': u'1', 'article': 'Hello.'}) +>>> f = TestArticleForm({'headline': u'Test headline', 'pub_date': u'1984-02-06', 'writer': u'1', 'article': 'Hello.'}) +>>> f.is_valid() +True +>>> test_art = f.save() +>>> test_art.id +1 +>>> test_art = Article.objects.get(id=1) +>>> test_art.headline +u'Test headline' + +You can create a form over a subset of the available fields +by specifying a 'fields' argument to form_for_instance. +>>> PartialArticleForm = form_for_instance(art, fields=('headline','pub_date')) +>>> f = PartialArticleForm({'headline': u'New headline', 'pub_date': u'1988-01-04'}, auto_id=False) +>>> print f.as_ul() +
  • Headline:
  • +
  • Pub date:
  • >>> f.is_valid() True >>> new_art = f.save() diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py index 0d3a65277c..d6b08a0cd3 100644 --- a/tests/regressiontests/forms/tests.py +++ b/tests/regressiontests/forms/tests.py @@ -2752,6 +2752,64 @@ then the latter will get precedence.
  • Username:
  • Password:
  • +# Callable initial data ######################################################## + +The previous technique dealt with raw values as initial data, but it's also +possible to specify callable data. + +>>> class UserRegistration(Form): +... username = CharField(max_length=10) +... password = CharField(widget=PasswordInput) + +We need to define functions that get called later. +>>> def initial_django(): +... return 'django' +>>> def initial_stephane(): +... return 'stephane' + +Here, we're not submitting any data, so the initial value will be displayed. +>>> p = UserRegistration(initial={'username': initial_django}, auto_id=False) +>>> print p.as_ul() +
  • Username:
  • +
  • Password:
  • + +The 'initial' parameter is meaningless if you pass data. +>>> p = UserRegistration({}, initial={'username': initial_django}, auto_id=False) +>>> print p.as_ul() +
    • This field is required.
    Username:
  • +
    • This field is required.
    Password:
  • +>>> p = UserRegistration({'username': u''}, initial={'username': initial_django}, auto_id=False) +>>> print p.as_ul() +
    • This field is required.
    Username:
  • +
    • This field is required.
    Password:
  • +>>> p = UserRegistration({'username': u'foo'}, initial={'username': initial_django}, auto_id=False) +>>> print p.as_ul() +
  • Username:
  • +
    • This field is required.
    Password:
  • + +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.errors +{'username': [u'This field is required.']} +>>> p.is_valid() +False + +If a Form defines 'initial' *and* 'initial' is passed as a parameter to Form(), +then the latter will get precedence. +>>> class UserRegistration(Form): +... username = CharField(max_length=10, initial=initial_django) +... password = CharField(widget=PasswordInput) +>>> p = UserRegistration(auto_id=False) +>>> print p.as_ul() +
  • Username:
  • +
  • Password:
  • +>>> p = UserRegistration(initial={'username': initial_stephane}, auto_id=False) +>>> print p.as_ul() +
  • Username:
  • +
  • Password:
  • + # Help text ################################################################### You can specify descriptive text for a field by using the 'help_text' argument diff --git a/tests/regressiontests/httpwrappers/tests.py b/tests/regressiontests/httpwrappers/tests.py index f0a7ba5fef..e5d873c2c6 100644 --- a/tests/regressiontests/httpwrappers/tests.py +++ b/tests/regressiontests/httpwrappers/tests.py @@ -96,6 +96,12 @@ MultiValueDictKeyError: "Key 'foo' not found in " >>> q['name'] u'john' +>>> del q['name'] +>>> 'name' in q +False + +>>> q['name'] = 'john' + >>> q.get('foo', 'default') u'default' @@ -367,6 +373,11 @@ AttributeError: This QueryDict instance is immutable >>> q.urlencode() 'vote=yes&vote=no' +>>> del q['vote'] +Traceback (most recent call last): +... +AttributeError: This QueryDict instance is immutable + # QueryDicts must be able to handle invalid input encoding (in this case, bad # UTF-8 encoding). >>> q = QueryDict('foo=bar&foo=\xff')