diff options
Diffstat (limited to 'django')
| -rw-r--r-- | django/conf/project_template/urls.py | 2 | ||||
| -rw-r--r-- | django/contrib/sitemaps/templates/sitemap_index.xml | 2 | ||||
| -rw-r--r-- | django/core/handlers/base.py | 6 | ||||
| -rw-r--r-- | django/core/handlers/wsgi.py | 9 | ||||
| -rw-r--r-- | django/db/models/fields/__init__.py | 13 | ||||
| -rw-r--r-- | django/http/__init__.py | 2 | ||||
| -rw-r--r-- | django/newforms/__init__.py | 13 | ||||
| -rw-r--r-- | django/newforms/fields.py | 17 | ||||
| -rw-r--r-- | django/newforms/forms.py | 130 | ||||
| -rw-r--r-- | django/newforms/models.py | 13 | ||||
| -rw-r--r-- | django/newforms/widgets.py | 85 | ||||
| -rw-r--r-- | django/views/generic/list_detail.py | 2 |
12 files changed, 214 insertions, 80 deletions
diff --git a/django/conf/project_template/urls.py b/django/conf/project_template/urls.py index 4483014173..402dd6536b 100644 --- a/django/conf/project_template/urls.py +++ b/django/conf/project_template/urls.py @@ -2,7 +2,7 @@ from django.conf.urls.defaults import * urlpatterns = patterns('', # Example: - # (r'^{{ project_name }}/', include('{{ project_name }}.apps.foo.urls.foo')), + # (r'^{{ project_name }}/', include('{{ project_name }}.foo.urls')), # Uncomment this for admin: # (r'^admin/', include('django.contrib.admin.urls')), diff --git a/django/contrib/sitemaps/templates/sitemap_index.xml b/django/contrib/sitemaps/templates/sitemap_index.xml index 9f39bb879f..a2bcce85dc 100644 --- a/django/contrib/sitemaps/templates/sitemap_index.xml +++ b/django/contrib/sitemaps/templates/sitemap_index.xml @@ -1,4 +1,4 @@ <?xml version="1.0" encoding="UTF-8"?> -<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemapindex/0.9"> +<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> {% for location in sitemaps %}<sitemap><loc>{{ location|escape }}</loc></sitemap>{% endfor %} </sitemapindex> diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py index c1403ea4fa..85473a6353 100644 --- a/django/core/handlers/base.py +++ b/django/core/handlers/base.py @@ -84,7 +84,11 @@ class BaseHandler(object): # Complain if the view returned None (a common error). if response is None: - raise ValueError, "The view %s.%s didn't return an HttpResponse object." % (callback.__module__, callback.func_name) + try: + view_name = callback.func_name # If it's a function + except AttributeError: + view_name = callback.__class__.__name__ + '.__call__' # If it's a class + raise ValueError, "The view %s.%s didn't return an HttpResponse object." % (callback.__module__, view_name) return response except http.Http404, e: diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py index 7dc1a4a5eb..71cfecd9a0 100644 --- a/django/core/handlers/wsgi.py +++ b/django/core/handlers/wsgi.py @@ -62,7 +62,7 @@ def safe_copyfileobj(fsrc, fdst, length=16*1024, size=0): data in the body. """ if not size: - return copyfileobj(fsrc, fdst, length) + return while size > 0: buf = fsrc.read(min(length, size)) if not buf: @@ -157,8 +157,11 @@ class WSGIRequest(http.HttpRequest): return self._raw_post_data except AttributeError: buf = StringIO() - # CONTENT_LENGTH might be absent if POST doesn't have content at all (lighttpd) - content_length = int(self.environ.get('CONTENT_LENGTH', 0)) + try: + # CONTENT_LENGTH might be absent if POST doesn't have content at all (lighttpd) + content_length = int(self.environ.get('CONTENT_LENGTH', 0)) + except ValueError: # if CONTENT_LENGTH was empty string or not an integer + content_length = 0 safe_copyfileobj(self.environ['wsgi.input'], buf, size=content_length) self._raw_post_data = buf.getvalue() buf.close() diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index c0d7a727ec..33b3c6769e 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -457,9 +457,7 @@ class DateField(Field): def get_db_prep_save(self, value): # Casts dates into string format for entry into database. - if isinstance(value, datetime.datetime): - value = value.date().strftime('%Y-%m-%d') - elif isinstance(value, datetime.date): + if value is not None: value = value.strftime('%Y-%m-%d') return Field.get_db_prep_save(self, value) @@ -489,7 +487,7 @@ class DateTimeField(DateField): def pre_save(self, model_instance, add): value = super(DateField, self).pre_save(model_instance, add) - if isinstance(value, datetime.datetime): + if value is not None: # MySQL will throw a warning if microseconds are given, because it # doesn't support microseconds. settings = model_instance._default_manager.db.connection.settings @@ -501,13 +499,6 @@ class DateTimeField(DateField): # Casts dates into string format for entry into database. if value is not None: value = str(value) - elif isinstance(value, datetime.date): - # MySQL will throw a warning if microseconds are given, because it - # doesn't support microseconds. - if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'): - value = datetime.datetime(value.year, value.month, value.day, microsecond=0) - value = str(value) - return Field.get_db_prep_save(self, value) def get_db_prep_lookup(self, lookup_type, value): diff --git a/django/http/__init__.py b/django/http/__init__.py index bb0e973aae..48f10329fd 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -208,7 +208,7 @@ class HttpResponse(object): if path is not None: self.cookies[key]['path'] = path if domain is not None: - self.cookies[key]['domain'] = path + self.cookies[key]['domain'] = domain self.cookies[key]['expires'] = 0 self.cookies[key]['max-age'] = 0 diff --git a/django/newforms/__init__.py b/django/newforms/__init__.py index 2a472d7b39..a445a21bfb 100644 --- a/django/newforms/__init__.py +++ b/django/newforms/__init__.py @@ -14,15 +14,4 @@ from util import ValidationError from widgets import * from fields import * from forms import Form - -########################## -# DATABASE API SHORTCUTS # -########################## - -def form_for_model(model): - "Returns a Form instance for the given Django model class." - raise NotImplementedError - -def form_for_fields(field_list): - "Returns a Form instance for the given list of Django database field instances." - raise NotImplementedError +from models import * diff --git a/django/newforms/fields.py b/django/newforms/fields.py index 40fc18bd3e..b3d44c24ae 100644 --- a/django/newforms/fields.py +++ b/django/newforms/fields.py @@ -76,6 +76,8 @@ class IntegerField(Field): of int(). """ super(IntegerField, self).clean(value) + if not self.required and value in EMPTY_VALUES: + return u'' try: return int(value) except (ValueError, TypeError): @@ -170,6 +172,8 @@ class RegexField(Field): Field.clean(self, value) if value in EMPTY_VALUES: value = u'' value = smart_unicode(value) + if not self.required and value == u'': + return value if not self.regex.search(value): raise ValidationError(self.error_message) return value @@ -246,6 +250,8 @@ class ChoiceField(Field): value = Field.clean(self, value) if value in EMPTY_VALUES: value = u'' value = smart_unicode(value) + if not self.required and value == u'': + return value valid_values = set([str(k) for k, v in self.choices]) if value not in valid_values: raise ValidationError(u'Select a valid choice. %s is not one of the available choices.' % value) @@ -259,10 +265,12 @@ class MultipleChoiceField(ChoiceField): """ Validates that the input is a list or tuple. """ - if not isinstance(value, (list, tuple)): - raise ValidationError(u'Enter a list of values.') if self.required and not value: raise ValidationError(u'This field is required.') + elif not self.required and not value: + return [] + if not isinstance(value, (list, tuple)): + raise ValidationError(u'Enter a list of values.') new_value = [] for val in value: val = smart_unicode(val) @@ -277,6 +285,11 @@ class MultipleChoiceField(ChoiceField): class ComboField(Field): def __init__(self, fields=(), required=True, widget=None): Field.__init__(self, required, widget) + # Set 'required' to False on the individual fields, because the + # required validation will be handled by ComboField, not by those + # individual fields. + for f in fields: + f.required = False self.fields = fields def clean(self, value): diff --git a/django/newforms/forms.py b/django/newforms/forms.py index b8264fb691..4bc6173249 100644 --- a/django/newforms/forms.py +++ b/django/newforms/forms.py @@ -3,8 +3,9 @@ Form classes """ from django.utils.datastructures import SortedDict +from django.utils.html import escape from fields import Field -from widgets import TextInput, Textarea +from widgets import TextInput, Textarea, HiddenInput from util import ErrorDict, ErrorList, ValidationError NON_FIELD_ERRORS = '__all__' @@ -36,6 +37,7 @@ class Form(object): __metaclass__ = DeclarativeFieldsMetaclass def __init__(self, data=None, auto_id=False): # TODO: prefix stuff + self.ignore_errors = data is None self.data = data or {} self.auto_id = auto_id self.clean_data = None # Stores the data after clean() has been called. @@ -56,69 +58,78 @@ class Form(object): raise KeyError('Key %r not found in Form' % name) return BoundField(self, field, name) - def clean(self): - if self.__errors is None: - self.full_clean() - return self.clean_data - - def errors(self): + def _errors(self): "Returns an ErrorDict for self.data" if self.__errors is None: self.full_clean() return self.__errors + errors = property(_errors) def is_valid(self): """ - Returns True if the form has no errors. Otherwise, False. This exists - solely for convenience, so client code can use positive logic rather - than confusing negative logic ("if not form.errors()"). + Returns True if the form has no errors. Otherwise, False. If errors are + being ignored, returns False. """ - return not bool(self.errors()) + return not self.ignore_errors and not bool(self.errors) def as_table(self): "Returns this form rendered as HTML <tr>s -- excluding the <table></table>." - return u'\n'.join(['<tr><td>%s:</td><td>%s</td></tr>' % (pretty_name(name), BoundField(self, field, name)) for name, field in self.fields.items()]) - - def as_ul(self): - "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>." - return u'\n'.join(['<li>%s: %s</li>' % (pretty_name(name), BoundField(self, field, name)) for name, field in self.fields.items()]) - - def as_table_with_errors(self): - "Returns this form rendered as HTML <tr>s, with errors." output = [] - if self.errors().get(NON_FIELD_ERRORS): + if self.errors.get(NON_FIELD_ERRORS): # Errors not corresponding to a particular field are displayed at the top. - output.append('<tr><td colspan="2"><ul>%s</ul></td></tr>' % '\n'.join(['<li>%s</li>' % e for e in self.errors()[NON_FIELD_ERRORS]])) + output.append(u'<tr><td colspan="2">%s</td></tr>' % self.non_field_errors()) for name, field in self.fields.items(): bf = BoundField(self, field, name) - if bf.errors: - output.append('<tr><td colspan="2"><ul>%s</ul></td></tr>' % '\n'.join(['<li>%s</li>' % e for e in bf.errors])) - output.append('<tr><td>%s:</td><td>%s</td></tr>' % (pretty_name(name), bf)) + if bf.is_hidden: + if bf.errors: + new_errors = ErrorList(['(Hidden field %s) %s' % (name, e) for e in bf.errors]) + output.append(u'<tr><td colspan="2">%s</td></tr>' % new_errors) + output.append(str(bf)) + else: + if bf.errors: + output.append(u'<tr><td colspan="2">%s</td></tr>' % bf.errors) + output.append(u'<tr><td>%s</td><td>%s</td></tr>' % (bf.label_tag(escape(bf.verbose_name+':')), bf)) return u'\n'.join(output) - def as_ul_with_errors(self): - "Returns this form rendered as HTML <li>s, with errors." + def as_ul(self): + "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>." output = [] - if self.errors().get(NON_FIELD_ERRORS): + if self.errors.get(NON_FIELD_ERRORS): # Errors not corresponding to a particular field are displayed at the top. - output.append('<li><ul>%s</ul></li>' % '\n'.join(['<li>%s</li>' % e for e in self.errors()[NON_FIELD_ERRORS]])) + output.append(u'<li>%s</li>' % self.non_field_errors()) for name, field in self.fields.items(): bf = BoundField(self, field, name) - line = '<li>' - if bf.errors: - line += '<ul>%s</ul>' % '\n'.join(['<li>%s</li>' % e for e in bf.errors]) - line += '%s: %s</li>' % (pretty_name(name), bf) - output.append(line) + if bf.is_hidden: + if bf.errors: + new_errors = ErrorList(['(Hidden field %s) %s' % (name, e) for e in bf.errors]) + output.append(u'<li>%s</li>' % new_errors) + output.append(str(bf)) + else: + output.append(u'<li>%s%s %s</li>' % (bf.errors, bf.label_tag(escape(bf.verbose_name+':')), bf)) return u'\n'.join(output) + def non_field_errors(self): + """ + Returns an ErrorList of errors that aren't associated with a particular + field -- i.e., from Form.clean(). Returns an empty ErrorList if there + are none. + """ + return self.errors.get(NON_FIELD_ERRORS, ErrorList()) + def full_clean(self): """ Cleans all of self.data and populates self.__errors and self.clean_data. """ self.clean_data = {} errors = ErrorDict() + if self.ignore_errors: # Stop further processing. + self.__errors = errors + return for name, field in self.fields.items(): - value = self.data.get(name, None) + # value_from_datadict() gets the data from the dictionary. + # Each widget type knows how to retrieve its own data, because some + # widgets split data over several HTML fields. + value = field.widget.value_from_datadict(self.data, name) try: value = field.clean(value) self.clean_data[name] = value @@ -138,7 +149,9 @@ class Form(object): def clean(self): """ Hook for doing any extra form-wide cleaning after Field.clean() been - called on every field. + called on every field. Any ValidationError raised by this method will + not be associated with a particular field; it will have a special-case + association with the field named '__all__'. """ return self.clean_data @@ -153,7 +166,13 @@ class BoundField(object): "Renders this field as an HTML widget." # Use the 'widget' attribute on the field to determine which type # of HTML widget to use. - return self.as_widget(self._field.widget) + value = self.as_widget(self._field.widget) + if not isinstance(value, basestring): + # Some Widget render() methods -- notably RadioSelect -- return a + # "special" object rather than a string. Call the __str__() on that + # object to get its rendered value. + value = value.__str__() + return value def _errors(self): """ @@ -161,7 +180,7 @@ class BoundField(object): if there are none. """ try: - return self._form.errors()[self._name] + return self._form.errors[self._name] except KeyError: return ErrorList() errors = property(_errors) @@ -169,9 +188,9 @@ class BoundField(object): def as_widget(self, widget, attrs=None): attrs = attrs or {} auto_id = self.auto_id - if not attrs.has_key('id') and not widget.attrs.has_key('id') and auto_id: + if auto_id and not attrs.has_key('id') and not widget.attrs.has_key('id'): attrs['id'] = auto_id - return widget.render(self._name, self._form.data.get(self._name, None), attrs=attrs) + return widget.render(self._name, self.data, attrs=attrs) def as_text(self, attrs=None): """ @@ -183,6 +202,39 @@ class BoundField(object): "Returns a string of HTML for representing this as a <textarea>." return self.as_widget(Textarea(), attrs) + def as_hidden(self, attrs=None): + """ + Returns a string of HTML for representing this as an <input type="hidden">. + """ + return self.as_widget(HiddenInput(), attrs) + + def _data(self): + "Returns the data for this BoundField, or None if it wasn't given." + return self._form.data.get(self._name, None) + data = property(_data) + + def _verbose_name(self): + return pretty_name(self._name) + verbose_name = property(_verbose_name) + + def label_tag(self, contents=None): + """ + Wraps the given contents in a <label>, if the field has an ID attribute. + Does not HTML-escape the contents. If contents aren't given, uses the + field's HTML-escaped verbose_name. + """ + contents = contents or escape(self.verbose_name) + widget = self._field.widget + id_ = widget.attrs.get('id') or self.auto_id + if id_: + contents = '<label for="%s">%s</label>' % (widget.id_for_label(id_), contents) + return contents + + def _is_hidden(self): + "Returns True if this BoundField's widget is hidden." + return self._field.widget.is_hidden + is_hidden = property(_is_hidden) + def _auto_id(self): """ Calculates and returns the ID attribute for this BoundField, if the diff --git a/django/newforms/models.py b/django/newforms/models.py new file mode 100644 index 0000000000..d99619c44a --- /dev/null +++ b/django/newforms/models.py @@ -0,0 +1,13 @@ +""" +Helper functions for creating Forms from Django models and database field objects. +""" + +__all__ = ('form_for_model', 'form_for_fields') + +def form_for_model(model): + "Returns a Form instance for the given Django model class." + raise NotImplementedError + +def form_for_fields(field_list): + "Returns a Form instance for the given list of Django database field instances." + raise NotImplementedError diff --git a/django/newforms/widgets.py b/django/newforms/widgets.py index 318c76e55d..274ba01225 100644 --- a/django/newforms/widgets.py +++ b/django/newforms/widgets.py @@ -5,7 +5,7 @@ HTML Widget classes __all__ = ( 'Widget', 'TextInput', 'PasswordInput', 'HiddenInput', 'FileInput', 'Textarea', 'CheckboxInput', - 'Select', 'SelectMultiple', 'RadioSelect', + 'Select', 'SelectMultiple', 'RadioSelect', 'CheckboxSelectMultiple', ) from util import smart_unicode @@ -23,6 +23,8 @@ flatatt = lambda attrs: u''.join([u' %s="%s"' % (k, escape(v)) for k, v in attrs class Widget(object): requires_data_list = False # Determines whether render()'s 'value' argument should be a list. + is_hidden = False # Determines whether this corresponds to an <input type="hidden">. + def __init__(self, attrs=None): self.attrs = attrs or {} @@ -30,11 +32,32 @@ class Widget(object): raise NotImplementedError def build_attrs(self, extra_attrs=None, **kwargs): + "Helper function for building an attribute dictionary." attrs = dict(self.attrs, **kwargs) if extra_attrs: attrs.update(extra_attrs) return attrs + def value_from_datadict(self, data, name): + """ + Given a dictionary of data and this widget's name, returns the value + of this widget. Returns None if it's not provided. + """ + return data.get(name, None) + + def id_for_label(self, id_): + """ + Returns the HTML ID attribute of this Widget for use by a <label>, + given the ID of the field. Returns None if no ID is available. + + This hook is necessary because some widgets have multiple HTML + elements and, thus, multiple IDs. In that case, this method should + return an ID value that corresponds to the first ID in the widget's + tags. + """ + return id_ + id_for_label = classmethod(id_for_label) + class Input(Widget): """ Base class for all <input> widgets (except type='checkbox' and @@ -55,6 +78,7 @@ class PasswordInput(Input): class HiddenInput(Input): input_type = 'hidden' + is_hidden = True class FileInput(Input): input_type = 'file' @@ -67,9 +91,22 @@ class Textarea(Widget): return u'<textarea%s>%s</textarea>' % (flatatt(final_attrs), escape(value)) class CheckboxInput(Widget): + def __init__(self, attrs=None, check_test=bool): + # check_test is a callable that takes a value and returns True + # if the checkbox should be checked for that value. + self.attrs = attrs or {} + self.check_test = check_test + def render(self, name, value, attrs=None): final_attrs = self.build_attrs(attrs, type='checkbox', name=name) - if value: final_attrs['checked'] = 'checked' + try: + result = self.check_test(value) + except: # Silently catch exceptions + result = False + if result: + final_attrs['checked'] = 'checked' + if value not in ('', True, False, None): + final_attrs['value'] = smart_unicode(value) # Only add the 'value' attribute if a value is non-empty. return u'<input%s />' % flatatt(final_attrs) class Select(Widget): @@ -111,10 +148,11 @@ class SelectMultiple(Widget): class RadioInput(object): "An object used by RadioFieldRenderer that represents a single <input type='radio'>." - def __init__(self, name, value, attrs, choice): + def __init__(self, name, value, attrs, choice, index): self.name, self.value = name, value - self.attrs = attrs or {} + self.attrs = attrs self.choice_value, self.choice_label = choice + self.index = index def __str__(self): return u'<label>%s %s</label>' % (self.tag(), self.choice_label) @@ -123,6 +161,8 @@ class RadioInput(object): return self.value == smart_unicode(self.choice_value) def tag(self): + if self.attrs.has_key('id'): + self.attrs['id'] = '%s_%s' % (self.attrs['id'], self.index) final_attrs = dict(self.attrs, type='radio', name=self.name, value=self.choice_value) if self.is_checked(): final_attrs['checked'] = 'checked' @@ -135,8 +175,8 @@ class RadioFieldRenderer(object): self.choices = choices def __iter__(self): - for choice in self.choices: - yield RadioInput(self.name, self.value, self.attrs, choice) + for i, choice in enumerate(self.choices): + yield RadioInput(self.name, self.value, self.attrs.copy(), choice, i) def __str__(self): "Outputs a <ul> for this set of radio fields." @@ -147,7 +187,36 @@ class RadioSelect(Select): "Returns a RadioFieldRenderer instance rather than a Unicode string." if value is None: value = '' str_value = smart_unicode(value) # Normalize to string. + attrs = attrs or {} return RadioFieldRenderer(name, str_value, attrs, list(chain(self.choices, choices))) -class CheckboxSelectMultiple(Widget): - pass + def id_for_label(self, id_): + # RadioSelect is represented by multiple <input type="radio"> fields, + # each of which has a distinct ID. The IDs are made distinct by a "_X" + # suffix, where X is the zero-based index of the radio field. Thus, + # the label for a RadioSelect should reference the first one ('_0'). + if id_: + id_ += '_0' + return id_ + id_for_label = classmethod(id_for_label) + +class CheckboxSelectMultiple(SelectMultiple): + def render(self, name, value, attrs=None, choices=()): + if value is None: value = [] + final_attrs = self.build_attrs(attrs, name=name) + output = [u'<ul>'] + str_values = set([smart_unicode(v) for v in value]) # Normalize to strings. + cb = CheckboxInput(final_attrs, check_test=lambda value: value in str_values) + for option_value, option_label in chain(self.choices, choices): + option_value = smart_unicode(option_value) + rendered_cb = cb.render(name, option_value) + output.append(u'<li><label>%s %s</label></li>' % (rendered_cb, escape(smart_unicode(option_label)))) + output.append(u'</ul>') + return u'\n'.join(output) + + def id_for_label(self, id_): + # See the comment for RadioSelect.id_for_label() + if id_: + id_ += '_0' + return id_ + id_for_label = classmethod(id_for_label) diff --git a/django/views/generic/list_detail.py b/django/views/generic/list_detail.py index bd0f17c56a..1836ce4a9f 100644 --- a/django/views/generic/list_detail.py +++ b/django/views/generic/list_detail.py @@ -84,7 +84,7 @@ def object_detail(request, queryset, object_id=None, slug=None, context_processors=None, template_object_name='object', mimetype=None): """ - Generic list of objects. + Generic detail of an object. Templates: ``<app_label>/<model_name>_detail.html`` Context: |
