From 7c7ad041b358a9819b3bd9f93d4834df4a5b5d57 Mon Sep 17 00:00:00 2001 From: Brian Rosner Date: Mon, 1 Sep 2008 21:28:32 +0000 Subject: [PATCH] Fixed #7975 -- Callable defaults in inline model formsets now work correctly. Based on patch from msaelices. Thanks for your hard work msaelices. git-svn-id: http://code.djangoproject.com/svn/django/trunk@8816 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/fields/__init__.py | 8 +-- django/forms/fields.py | 8 ++- django/forms/forms.py | 43 ++++++++---- django/forms/widgets.py | 14 +++- tests/modeltests/model_formsets/models.py | 81 +++++++++++++++++++++++ tests/regressiontests/forms/widgets.py | 17 ++++- 6 files changed, 149 insertions(+), 22 deletions(-) diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 4b8beb4341..7e56542024 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -231,7 +231,7 @@ class Field(object): def get_default(self): "Returns the default value for this field." - if self.default is not NOT_PROVIDED: + if self.has_default(): if callable(self.default): return self.default() return force_unicode(self.default, strings_only=True) @@ -306,7 +306,8 @@ class Field(object): defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text} if self.has_default(): defaults['initial'] = self.get_default() - + if callable(self.default): + defaults['show_hidden_initial'] = True if self.choices: # Fields with choices get special treatment. include_blank = self.blank or not (self.has_default() or 'initial' in kwargs) @@ -314,9 +315,7 @@ class Field(object): defaults['coerce'] = self.to_python if self.null: defaults['empty_value'] = None - form_class = forms.TypedChoiceField - # Many of the subclass-specific formfield arguments (min_value, # max_value) don't apply for choice fields, so be sure to only pass # the values that TypedChoiceField will understand. @@ -325,7 +324,6 @@ class Field(object): 'widget', 'label', 'initial', 'help_text', 'error_messages'): del kwargs[k] - defaults.update(kwargs) return form_class(**defaults) diff --git a/django/forms/fields.py b/django/forms/fields.py index e267498808..b20beb939f 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -28,7 +28,7 @@ from django.utils.translation import ugettext_lazy as _ from django.utils.encoding import smart_unicode, smart_str from util import ErrorList, ValidationError -from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput, TimeInput +from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput, TimeInput, SplitHiddenDateTimeWidget from django.core.files.uploadedfile import SimpleUploadedFile as UploadedFile __all__ = ( @@ -59,7 +59,7 @@ class Field(object): creation_counter = 0 def __init__(self, required=True, widget=None, label=None, initial=None, - help_text=None, error_messages=None): + help_text=None, error_messages=None, show_hidden_initial=False): # required -- Boolean that specifies whether the field is required. # True by default. # widget -- A Widget class, or instance of a Widget class, that should @@ -73,9 +73,12 @@ class Field(object): # initial -- A value to use in this Field's initial display. This value # is *not* used as a fallback if data isn't given. # help_text -- An optional string to use as "help text" for this Field. + # show_hidden_initial -- Boolean that specifies if it is needed to render a + # hidden widget with initial value after widget. if label is not None: label = smart_unicode(label) self.required, self.label, self.initial = required, label, initial + self.show_hidden_initial = show_hidden_initial if help_text is None: self.help_text = u'' else: @@ -840,6 +843,7 @@ class FilePathField(ChoiceField): self.widget.choices = self.choices class SplitDateTimeField(MultiValueField): + hidden_widget = SplitHiddenDateTimeWidget default_error_messages = { 'invalid_date': _(u'Enter a valid date.'), 'invalid_time': _(u'Enter a valid time.'), diff --git a/django/forms/forms.py b/django/forms/forms.py index 7d1254c5ea..2d4f4462d0 100644 --- a/django/forms/forms.py +++ b/django/forms/forms.py @@ -128,6 +128,12 @@ class BaseForm(StrAndUnicode): """ return self.prefix and ('%s-%s' % (self.prefix, field_name)) or field_name + def add_initial_prefix(self, field_name): + """ + Add a 'initial' prefix for checking dynamic initial values + """ + return u'initial-%s' % self.add_prefix(field_name) + def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row): "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()." top_errors = self.non_field_errors() # Errors that should be displayed above all fields. @@ -245,7 +251,7 @@ class BaseForm(StrAndUnicode): Returns True if data differs from initial. """ return bool(self.changed_data) - + def _get_changed_data(self): if self._changed_data is None: self._changed_data = [] @@ -258,7 +264,13 @@ class BaseForm(StrAndUnicode): for name, field in self.fields.items(): prefixed_name = self.add_prefix(name) data_value = field.widget.value_from_datadict(self.data, self.files, prefixed_name) - initial_value = self.initial.get(name, field.initial) + if not field.show_hidden_initial: + initial_value = self.initial.get(name, field.initial) + else: + initial_prefixed_name = self.add_initial_prefix(name) + hidden_widget = field.hidden_widget() + initial_value = hidden_widget.value_from_datadict( + self.data, self.files, initial_prefixed_name) if field.widget._has_changed(initial_value, data_value): self._changed_data.append(name) return self._changed_data @@ -300,6 +312,7 @@ class BoundField(StrAndUnicode): self.field = field self.name = name self.html_name = form.add_prefix(name) + self.html_initial_name = form.add_initial_prefix(name) if self.field.label is None: self.label = pretty_name(name) else: @@ -308,6 +321,8 @@ class BoundField(StrAndUnicode): def __unicode__(self): """Renders this field as an HTML widget.""" + if self.field.show_hidden_initial: + return self.as_widget() + self.as_hidden(only_initial=True) return self.as_widget() def _errors(self): @@ -318,7 +333,7 @@ class BoundField(StrAndUnicode): return self.form.errors.get(self.name, self.form.error_class()) errors = property(_errors) - def as_widget(self, widget=None, attrs=None): + def as_widget(self, widget=None, attrs=None, only_initial=False): """ Renders the field by rendering the passed widget, adding any HTML attributes passed as attrs. If no widget is specified, then the @@ -330,29 +345,33 @@ class BoundField(StrAndUnicode): auto_id = self.auto_id if auto_id and 'id' not in attrs and 'id' not in widget.attrs: attrs['id'] = auto_id - if not self.form.is_bound: + if not self.form.is_bound or only_initial: 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) - - def as_text(self, attrs=None): + if not only_initial: + name = self.html_name + else: + name = self.html_initial_name + return widget.render(name, data, attrs=attrs) + + def as_text(self, attrs=None, **kwargs): """ Returns a string of HTML for representing this as an . """ - return self.as_widget(TextInput(), attrs) + return self.as_widget(TextInput(), attrs, **kwargs) - def as_textarea(self, attrs=None): + def as_textarea(self, attrs=None, **kwargs): "Returns a string of HTML for representing this as a