diff --git a/AUTHORS b/AUTHORS index dbbf6e7bad32f..645913e7c46d5 100644 --- a/AUTHORS +++ b/AUTHORS @@ -118,10 +118,12 @@ answer newbie questions, and generally made Django that much better: Manuzhai Petar Marić mark@junklight.com + Yasushi Masuda mattycakes@gmail.com Jason McBrayer mccutchen@gmail.com michael.mcewan@gmail.com + mitakummaa@gmail.com mmarshall Eric Moritz Robin Munn @@ -160,6 +162,7 @@ answer newbie questions, and generally made Django that much better: Tom Insam Joe Topjian Karen Tracey + Makoto Tsuyuki Amit Upadhyay Geert Vanderkelen Milton Waddams diff --git a/django/contrib/admin/templates/admin/base.html b/django/contrib/admin/templates/admin/base.html index b63604b268d8b..d3e8c96b912fa 100644 --- a/django/contrib/admin/templates/admin/base.html +++ b/django/contrib/admin/templates/admin/base.html @@ -38,7 +38,10 @@
{% block pretitle %}{% endblock %} {% block content_title %}{% if title %}

{{ title|escape }}

{% endif %}{% endblock %} - {% block content %}{{ content }}{% endblock %} + {% block content %} + {% block object-tools %}{% endblock %} + {{ content }} + {% endblock %} {% block sidebar %}{% endblock %}
diff --git a/django/contrib/admin/templates/admin/change_form.html b/django/contrib/admin/templates/admin/change_form.html index b1fdc5ebdb864..7e7b639139c38 100644 --- a/django/contrib/admin/templates/admin/change_form.html +++ b/django/contrib/admin/templates/admin/change_form.html @@ -16,11 +16,13 @@ {% endif %}{% endblock %} {% block content %}
+{% block object-tools %} {% if change %}{% if not is_popup %} {% endif %}{% endif %} +{% endblock %}
{% block form_top %}{% endblock %}
{% if is_popup %}{% endif %} diff --git a/django/contrib/admin/templates/admin/change_list.html b/django/contrib/admin/templates/admin/change_list.html index bd2304bd52081..f50a73c934220 100644 --- a/django/contrib/admin/templates/admin/change_list.html +++ b/django/contrib/admin/templates/admin/change_list.html @@ -7,9 +7,11 @@ {% block coltype %}flex{% endblock %} {% block content %}
+{% block object-tools %} {% if has_add_permission %} {% endif %} +{% endblock %}
{% block search %}{% search_form cl %}{% endblock %} {% block date_hierarchy %}{% date_hierarchy cl %}{% endblock %} diff --git a/django/contrib/csrf/middleware.py b/django/contrib/csrf/middleware.py index f6f78867dc828..93a9484ca655e 100644 --- a/django/contrib/csrf/middleware.py +++ b/django/contrib/csrf/middleware.py @@ -11,7 +11,7 @@ import re import itertools -_ERROR_MSG = "

403 Forbidden

Cross Site Request Forgery detected. Request aborted.

" +_ERROR_MSG = '

403 Forbidden

Cross Site Request Forgery detected. Request aborted.

' _POST_FORM_RE = \ re.compile(r'(]*\bmethod=(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE) diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py index 85473a635315d..ca48b301d4d2e 100644 --- a/django/core/handlers/base.py +++ b/django/core/handlers/base.py @@ -60,7 +60,10 @@ def get_response(self, request): if response: return response - resolver = urlresolvers.RegexURLResolver(r'^/', settings.ROOT_URLCONF) + # Get urlconf from request object, if available. Otherwise use default. + urlconf = getattr(request, "urlconf", settings.ROOT_URLCONF) + + resolver = urlresolvers.RegexURLResolver(r'^/', urlconf) try: callback, callback_args, callback_kwargs = resolver.resolve(request.path) diff --git a/django/db/backends/oracle/creation.py b/django/db/backends/oracle/creation.py index c1387a7ff8455..c3326cf84371d 100644 --- a/django/db/backends/oracle/creation.py +++ b/django/db/backends/oracle/creation.py @@ -81,6 +81,8 @@ def destroy_test_db(settings, connection, backend, old_database_name, verbosity= settings.DATABASE_NAME = old_database_name #settings.DATABASE_USER = 'old_user' #settings.DATABASE_PASSWORD = 'old_password' + settings.DATABASE_USER = 'mboersma' + settings.DATABASE_PASSWORD = 'password' cursor = connection.cursor() time.sleep(1) # To avoid "database is being accessed by other users" errors. diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py index db92ef5df0747..3dceb5bf52a89 100644 --- a/django/db/backends/postgresql/base.py +++ b/django/db/backends/postgresql/base.py @@ -20,6 +20,38 @@ # Import copy of _thread_local.py from Python 2.4 from django.utils._threading_local import local +def smart_basestring(s, charset): + if isinstance(s, unicode): + return s.encode(charset) + return s + +class UnicodeCursorWrapper(object): + """ + A thin wrapper around psycopg cursors that allows them to accept Unicode + strings as params. + + This is necessary because psycopg doesn't apply any DB quoting to + parameters that are Unicode strings. If a param is Unicode, this will + convert it to a bytestring using DEFAULT_CHARSET before passing it to + psycopg. + """ + def __init__(self, cursor, charset): + self.cursor = cursor + self.charset = charset + + def execute(self, sql, params=()): + return self.cursor.execute(sql, [smart_basestring(p, self.charset) for p in params]) + + def executemany(self, sql, param_list): + new_param_list = [[smart_basestring(p, self.charset) for p in params] for params in param_list] + return self.cursor.executemany(sql, new_param_list) + + def __getattr__(self, attr): + if self.__dict__.has_key(attr): + return self.__dict__[attr] + else: + return getattr(self.cursor, attr) + class DatabaseWrapper(local): def __init__(self, **kwargs): self.connection = None @@ -45,6 +77,7 @@ def cursor(self): self.connection.set_isolation_level(1) # make transactions transparent to all cursors cursor = self.connection.cursor() cursor.execute("SET TIME ZONE %s", [settings.TIME_ZONE]) + cursor = UnicodeCursorWrapper(cursor, settings.DEFAULT_CHARSET) if settings.DEBUG: return util.CursorDebugWrapper(cursor, self) return cursor @@ -131,7 +164,7 @@ def get_autoinc_sql(table): try: Database.register_type(Database.new_type((1082,), "DATE", util.typecast_date)) except AttributeError: - raise Exception, "You appear to be using psycopg version 2, which isn't supported yet, because it's still in beta. Use psycopg version 1 instead: http://initd.org/projects/psycopg1" + raise Exception, "You appear to be using psycopg version 2. Set your DATABASE_ENGINE to 'postgresql_psycopg2' instead of 'postgresql'." Database.register_type(Database.new_type((1083,1266), "TIME", util.typecast_time)) Database.register_type(Database.new_type((1114,1184), "TIMESTAMP", util.typecast_timestamp)) Database.register_type(Database.new_type((16,), "BOOLEAN", util.typecast_boolean)) diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index d4dd4cdd47f92..2471514e0e45b 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -337,11 +337,15 @@ def _get_choices(self): return self._choices choices = property(_get_choices) - def formfield(self): + def formfield(self, initial=None): "Returns a django.newforms.Field instance for this database Field." from django.newforms import CharField # TODO: This is just a temporary default during development. - return CharField(label=capfirst(self.verbose_name)) + return forms.CharField(required=not self.blank, label=capfirst(self.verbose_name), initial=initial) + + def value_from_object(self, obj): + "Returns the value of this field in the given model instance." + return getattr(obj, self.attname) class AutoField(Field): empty_strings_allowed = False @@ -379,6 +383,9 @@ def contribute_to_class(self, cls, name): super(AutoField, self).contribute_to_class(cls, name) cls._meta.has_auto_field = True + def formfield(self, initial=None): + return None + class BooleanField(Field): def __init__(self, *args, **kwargs): kwargs['blank'] = True @@ -393,6 +400,9 @@ def to_python(self, value): def get_manipulator_field_objs(self): return [oldforms.CheckboxField] + def formfield(self, initial=None): + return forms.BooleanField(required=not self.blank, label=capfirst(self.verbose_name), initial=initial) + class CharField(Field): def get_manipulator_field_objs(self): return [oldforms.TextField] @@ -407,6 +417,9 @@ def to_python(self, value): raise validators.ValidationError, gettext_lazy("This field cannot be null.") return str(value) + def formfield(self, initial=None): + return forms.CharField(max_length=self.maxlength, required=not self.blank, label=capfirst(self.verbose_name), initial=initial) + # TODO: Maybe move this into contrib, because it's specialized. class CommaSeparatedIntegerField(CharField): def get_manipulator_field_objs(self): @@ -480,10 +493,13 @@ def get_db_prep_save(self, value): def get_manipulator_field_objs(self): return [oldforms.DateField] - def flatten_data(self, follow, obj = None): + def flatten_data(self, follow, obj=None): val = self._get_val_from_obj(obj) return {self.attname: (val is not None and val.strftime("%Y-%m-%d") or '')} + def formfield(self, initial=None): + return forms.DateField(required=not self.blank, label=capfirst(self.verbose_name), initial=initial) + class DateTimeField(DateField): def to_python(self, value): if isinstance(value, datetime.datetime): @@ -553,6 +569,9 @@ def flatten_data(self,follow, obj = None): return {date_field: (val is not None and val.strftime("%Y-%m-%d") or ''), time_field: (val is not None and val.strftime("%H:%M:%S") or '')} + def formfield(self, initial=None): + return forms.DateTimeField(required=not self.blank, label=capfirst(self.verbose_name), initial=initial) + class EmailField(CharField): def __init__(self, *args, **kwargs): kwargs['maxlength'] = 75 @@ -567,6 +586,9 @@ def get_manipulator_field_objs(self): def validate(self, field_data, all_data): validators.isValidEmail(field_data, all_data) + def formfield(self, initial=None): + return forms.EmailField(required=not self.blank, label=capfirst(self.verbose_name), initial=initial) + class FileField(Field): def __init__(self, verbose_name=None, name=None, upload_to='', **kwargs): self.upload_to = upload_to @@ -699,6 +721,9 @@ class IntegerField(Field): def get_manipulator_field_objs(self): return [oldforms.IntegerField] + def formfield(self, initial=None): + return forms.IntegerField(required=not self.blank, label=capfirst(self.verbose_name), initial=initial) + class IPAddressField(Field): def __init__(self, *args, **kwargs): kwargs['maxlength'] = 15 @@ -799,15 +824,22 @@ def flatten_data(self,follow, obj = None): val = self._get_val_from_obj(obj) return {self.attname: (val is not None and val.strftime("%H:%M:%S") or '')} + def formfield(self, initial=None): + return forms.TimeField(required=not self.blank, label=capfirst(self.verbose_name), initial=initial) + class URLField(Field): def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs): if verify_exists: kwargs.setdefault('validator_list', []).append(validators.isExistingURL) + self.verify_exists = verify_exists Field.__init__(self, verbose_name, name, **kwargs) def get_manipulator_field_objs(self): return [oldforms.URLField] + def formfield(self, initial=None): + return forms.URLField(required=not self.blank, verify_exists=self.verify_exists, label=capfirst(self.verbose_name), initial=initial) + class USStateField(Field): def get_manipulator_field_objs(self): return [oldforms.USStateField] diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 102e117631399..75d1f6e27adc0 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -2,10 +2,12 @@ from django.db.models import signals, get_model from django.db.models.fields import AutoField, Field, IntegerField, get_ul_class from django.db.models.related import RelatedObject +from django.utils.text import capfirst from django.utils.translation import gettext_lazy, string_concat, ngettext from django.utils.functional import curry from django.core import validators from django import oldforms +from django import newforms as forms from django.dispatch import dispatcher # For Python 2.3 @@ -256,8 +258,7 @@ def __set__(self, instance, value): # Otherwise, just move the named objects into the set. if self.related.field.null: manager.clear() - for obj in value: - manager.add(obj) + manager.add(*value) def create_many_related_manager(superclass): """Creates a manager that subclasses 'superclass' (which is a Manager) @@ -318,25 +319,31 @@ def _add_items(self, source_col_name, target_col_name, *objs): # *objs - objects to add from django.db import connection - # Add the newly created or already existing objects to the join table. - # First find out which items are already added, to avoid adding them twice - new_ids = set([obj._get_pk_val() for obj in objs]) - cursor = connection.cursor() - cursor.execute("SELECT %s FROM %s WHERE %s = %%s AND %s IN (%s)" % \ - (target_col_name, self.join_table, source_col_name, - target_col_name, ",".join(['%s'] * len(new_ids))), - [self._pk_val] + list(new_ids)) - if cursor.rowcount is not None and cursor.rowcount != 0: - existing_ids = set([row[0] for row in cursor.fetchmany(cursor.rowcount)]) - else: - existing_ids = set() + # If there aren't any objects, there is nothing to do. + if objs: + # Check that all the objects are of the right type + for obj in objs: + if not isinstance(obj, self.model): + raise ValueError, "objects to add() must be %s instances" % self.model._meta.object_name + # Add the newly created or already existing objects to the join table. + # First find out which items are already added, to avoid adding them twice + new_ids = set([obj._get_pk_val() for obj in objs]) + cursor = connection.cursor() + cursor.execute("SELECT %s FROM %s WHERE %s = %%s AND %s IN (%s)" % \ + (target_col_name, self.join_table, source_col_name, + target_col_name, ",".join(['%s'] * len(new_ids))), + [self._pk_val] + list(new_ids)) + if cursor.rowcount is not None and cursor.rowcount != 0: + existing_ids = set([row[0] for row in cursor.fetchmany(cursor.rowcount)]) + else: + existing_ids = set() - # Add the ones that aren't there already - for obj_id in (new_ids - existing_ids): - cursor.execute("INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \ - (self.join_table, source_col_name, target_col_name), - [self._pk_val, obj_id]) - transaction.commit_unless_managed() + # Add the ones that aren't there already + for obj_id in (new_ids - existing_ids): + cursor.execute("INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \ + (self.join_table, source_col_name, target_col_name), + [self._pk_val, obj_id]) + transaction.commit_unless_managed() def _remove_items(self, source_col_name, target_col_name, *objs): # source_col_name: the PK colname in join_table for the source object @@ -344,16 +351,20 @@ def _remove_items(self, source_col_name, target_col_name, *objs): # *objs - objects to remove from django.db import connection - for obj in objs: - if not isinstance(obj, self.model): - raise ValueError, "objects to remove() must be %s instances" % self.model._meta.object_name - # Remove the specified objects from the join table - cursor = connection.cursor() - for obj in objs: - cursor.execute("DELETE FROM %s WHERE %s = %%s AND %s = %%s" % \ - (self.join_table, source_col_name, target_col_name), - [self._pk_val, obj._get_pk_val()]) - transaction.commit_unless_managed() + # If there aren't any objects, there is nothing to do. + if objs: + # Check that all the objects are of the right type + for obj in objs: + if not isinstance(obj, self.model): + raise ValueError, "objects to remove() must be %s instances" % self.model._meta.object_name + # Remove the specified objects from the join table + old_ids = set([obj._get_pk_val() for obj in objs]) + cursor = connection.cursor() + cursor.execute("DELETE FROM %s WHERE %s = %%s AND %s IN (%s)" % \ + (self.join_table, source_col_name, + target_col_name, ",".join(['%s'] * len(old_ids))), + [self._pk_val] + list(old_ids)) + transaction.commit_unless_managed() def _clear_items(self, source_col_name): # source_col_name: the PK colname in join_table for the source object @@ -405,8 +416,7 @@ def __set__(self, instance, value): manager = self.__get__(instance) manager.clear() - for obj in value: - manager.add(obj) + manager.add(*value) class ReverseManyRelatedObjectsDescriptor(object): # This class provides the functionality that makes the related-object @@ -447,8 +457,7 @@ def __set__(self, instance, value): manager = self.__get__(instance) manager.clear() - for obj in value: - manager.add(obj) + manager.add(*value) class ForeignKey(RelatedField, Field): empty_strings_allowed = False @@ -539,6 +548,9 @@ def contribute_to_class(self, cls, name): def contribute_to_related_class(self, cls, related): setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related)) + def formfield(self, initial=None): + return forms.ChoiceField(choices=self.get_choices_default(), required=not self.blank, label=capfirst(self.verbose_name), initial=initial) + class OneToOneField(RelatedField, IntegerField): def __init__(self, to, to_field=None, **kwargs): try: @@ -600,6 +612,9 @@ def contribute_to_related_class(self, cls, related): if not cls._meta.one_to_one_field: cls._meta.one_to_one_field = self + def formfield(self, initial=None): + return forms.ChoiceField(choices=self.get_choices_default(), required=not self.blank, label=capfirst(self.verbose_name), initial=initial) + class ManyToManyField(RelatedField, Field): def __init__(self, to, **kwargs): kwargs['verbose_name'] = kwargs.get('verbose_name', None) @@ -709,6 +724,13 @@ def contribute_to_related_class(self, cls, related): def set_attributes_from_rel(self): pass + def value_from_object(self, obj): + "Returns the value of this field in the given model instance." + return getattr(obj, self.attname).all() + + def formfield(self, initial=None): + return forms.MultipleChoiceField(choices=self.get_choices_default(), required=not self.blank, label=capfirst(self.verbose_name), initial=initial) + class ManyToOneRel(object): def __init__(self, to, field_name, num_in_admin=3, min_num_in_admin=None, max_num_in_admin=None, num_extra_on_change=1, edit_inline=False, diff --git a/django/forms/__init__.py b/django/forms/__init__.py index 0b9ac05edb15a..68d3d245a2965 100644 --- a/django/forms/__init__.py +++ b/django/forms/__init__.py @@ -1,1008 +1 @@ -from django.core import validators -from django.core.exceptions import PermissionDenied -from django.utils.html import escape -from django.conf import settings -from django.utils.translation import gettext, ngettext - -FORM_FIELD_ID_PREFIX = 'id_' - -class EmptyValue(Exception): - "This is raised when empty data is provided" - pass - -class Manipulator(object): - # List of permission strings. User must have at least one to manipulate. - # None means everybody has permission. - required_permission = '' - - def __init__(self): - # List of FormField objects - self.fields = [] - - def __getitem__(self, field_name): - "Looks up field by field name; raises KeyError on failure" - for field in self.fields: - if field.field_name == field_name: - return field - raise KeyError, "Field %s not found\n%s" % (field_name, repr(self.fields)) - - def __delitem__(self, field_name): - "Deletes the field with the given field name; raises KeyError on failure" - for i, field in enumerate(self.fields): - if field.field_name == field_name: - del self.fields[i] - return - raise KeyError, "Field %s not found" % field_name - - def check_permissions(self, user): - """Confirms user has required permissions to use this manipulator; raises - PermissionDenied on failure.""" - if self.required_permission is None: - return - if user.has_perm(self.required_permission): - return - raise PermissionDenied - - def prepare(self, new_data): - """ - Makes any necessary preparations to new_data, in place, before data has - been validated. - """ - for field in self.fields: - field.prepare(new_data) - - def get_validation_errors(self, new_data): - "Returns dictionary mapping field_names to error-message lists" - errors = {} - self.prepare(new_data) - for field in self.fields: - errors.update(field.get_validation_errors(new_data)) - val_name = 'validate_%s' % field.field_name - if hasattr(self, val_name): - val = getattr(self, val_name) - try: - field.run_validator(new_data, val) - except (validators.ValidationError, validators.CriticalValidationError), e: - errors.setdefault(field.field_name, []).extend(e.messages) - -# if field.is_required and not new_data.get(field.field_name, False): -# errors.setdefault(field.field_name, []).append(gettext_lazy('This field is required.')) -# continue -# try: -# validator_list = field.validator_list -# if hasattr(self, 'validate_%s' % field.field_name): -# validator_list.append(getattr(self, 'validate_%s' % field.field_name)) -# for validator in validator_list: -# if field.is_required or new_data.get(field.field_name, False) or hasattr(validator, 'always_test'): -# try: -# if hasattr(field, 'requires_data_list'): -# validator(new_data.getlist(field.field_name), new_data) -# else: -# validator(new_data.get(field.field_name, ''), new_data) -# except validators.ValidationError, e: -# errors.setdefault(field.field_name, []).extend(e.messages) -# # If a CriticalValidationError is raised, ignore any other ValidationErrors -# # for this particular field -# except validators.CriticalValidationError, e: -# errors.setdefault(field.field_name, []).extend(e.messages) - return errors - - def save(self, new_data): - "Saves the changes and returns the new object" - # changes is a dictionary-like object keyed by field_name - raise NotImplementedError - - def do_html2python(self, new_data): - """ - Convert the data from HTML data types to Python datatypes, changing the - object in place. This happens after validation but before storage. This - must happen after validation because html2python functions aren't - expected to deal with invalid input. - """ - for field in self.fields: - field.convert_post_data(new_data) - -class FormWrapper(object): - """ - A wrapper linking a Manipulator to the template system. - This allows dictionary-style lookups of formfields. It also handles feeding - prepopulated data and validation error messages to the formfield objects. - """ - def __init__(self, manipulator, data=None, error_dict=None, edit_inline=True): - self.manipulator = manipulator - if data is None: - data = {} - if error_dict is None: - error_dict = {} - self.data = data - self.error_dict = error_dict - self._inline_collections = None - self.edit_inline = edit_inline - - def __repr__(self): - return repr(self.__dict__) - - def __getitem__(self, key): - for field in self.manipulator.fields: - if field.field_name == key: - data = field.extract_data(self.data) - return FormFieldWrapper(field, data, self.error_dict.get(field.field_name, [])) - if self.edit_inline: - self.fill_inline_collections() - for inline_collection in self._inline_collections: - if inline_collection.name == key: - return inline_collection - raise KeyError, "Could not find Formfield or InlineObjectCollection named %r" % key - - def fill_inline_collections(self): - if not self._inline_collections: - ic = [] - related_objects = self.manipulator.get_related_objects() - for rel_obj in related_objects: - data = rel_obj.extract_data(self.data) - inline_collection = InlineObjectCollection(self.manipulator, rel_obj, data, self.error_dict) - ic.append(inline_collection) - self._inline_collections = ic - - def has_errors(self): - return self.error_dict != {} - - def _get_fields(self): - try: - return self._fields - except AttributeError: - self._fields = [self.__getitem__(field.field_name) for field in self.manipulator.fields] - return self._fields - - fields = property(_get_fields) - -class FormFieldWrapper(object): - "A bridge between the template system and an individual form field. Used by FormWrapper." - def __init__(self, formfield, data, error_list): - self.formfield, self.data, self.error_list = formfield, data, error_list - self.field_name = self.formfield.field_name # for convenience in templates - - def __str__(self): - "Renders the field" - return str(self.formfield.render(self.data)) - - def __repr__(self): - return '' % self.formfield.field_name - - def field_list(self): - """ - Like __str__(), but returns a list. Use this when the field's render() - method returns a list. - """ - return self.formfield.render(self.data) - - def errors(self): - return self.error_list - - def html_error_list(self): - if self.errors(): - return '
  • %s
' % '
  • '.join([escape(e) for e in self.errors()]) - else: - return '' - - def get_id(self): - return self.formfield.get_id() - -class FormFieldCollection(FormFieldWrapper): - "A utility class that gives the template access to a dict of FormFieldWrappers" - def __init__(self, formfield_dict): - self.formfield_dict = formfield_dict - - def __str__(self): - return str(self.formfield_dict) - - def __getitem__(self, template_key): - "Look up field by template key; raise KeyError on failure" - return self.formfield_dict[template_key] - - def __repr__(self): - return "" % self.formfield_dict - - def errors(self): - "Returns list of all errors in this collection's formfields" - errors = [] - for field in self.formfield_dict.values(): - if hasattr(field, 'errors'): - errors.extend(field.errors()) - return errors - - def has_errors(self): - return bool(len(self.errors())) - - def html_combined_error_list(self): - return ''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')]) - -class InlineObjectCollection(object): - "An object that acts like a sparse list of form field collections." - def __init__(self, parent_manipulator, rel_obj, data, errors): - self.parent_manipulator = parent_manipulator - self.rel_obj = rel_obj - self.data = data - self.errors = errors - self._collections = None - self.name = rel_obj.name - - def __len__(self): - self.fill() - return self._collections.__len__() - - def __getitem__(self, k): - self.fill() - return self._collections.__getitem__(k) - - def __setitem__(self, k, v): - self.fill() - return self._collections.__setitem__(k,v) - - def __delitem__(self, k): - self.fill() - return self._collections.__delitem__(k) - - def __iter__(self): - self.fill() - return iter(self._collections.values()) - - def items(self): - self.fill() - return self._collections.items() - - def fill(self): - if self._collections: - return - else: - var_name = self.rel_obj.opts.object_name.lower() - collections = {} - orig = None - if hasattr(self.parent_manipulator, 'original_object'): - orig = self.parent_manipulator.original_object - orig_list = self.rel_obj.get_list(orig) - - for i, instance in enumerate(orig_list): - collection = {'original': instance} - for f in self.rel_obj.editable_fields(): - for field_name in f.get_manipulator_field_names(''): - full_field_name = '%s.%d.%s' % (var_name, i, field_name) - field = self.parent_manipulator[full_field_name] - data = field.extract_data(self.data) - errors = self.errors.get(full_field_name, []) - collection[field_name] = FormFieldWrapper(field, data, errors) - collections[i] = FormFieldCollection(collection) - self._collections = collections - - -class FormField(object): - """Abstract class representing a form field. - - Classes that extend FormField should define the following attributes: - field_name - The field's name for use by programs. - validator_list - A list of validation tests (callback functions) that the data for - this field must pass in order to be added or changed. - is_required - A Boolean. Is it a required field? - Subclasses should also implement a render(data) method, which is responsible - for rending the form field in XHTML. - """ - def __str__(self): - return self.render('') - - def __repr__(self): - return 'FormField "%s"' % self.field_name - - def prepare(self, new_data): - "Hook for doing something to new_data (in place) before validation." - pass - - def html2python(data): - "Hook for converting an HTML datatype (e.g. 'on' for checkboxes) to a Python type" - return data - html2python = staticmethod(html2python) - - def render(self, data): - raise NotImplementedError - - def get_member_name(self): - if hasattr(self, 'member_name'): - return self.member_name - else: - return self.field_name - - def extract_data(self, data_dict): - if hasattr(self, 'requires_data_list') and hasattr(data_dict, 'getlist'): - data = data_dict.getlist(self.get_member_name()) - else: - data = data_dict.get(self.get_member_name(), None) - if data is None: - data = '' - return data - - def convert_post_data(self, new_data): - name = self.get_member_name() - if new_data.has_key(self.field_name): - d = new_data.getlist(self.field_name) - try: - converted_data = [self.__class__.html2python(data) for data in d] - except ValueError: - converted_data = d - new_data.setlist(name, converted_data) - else: - try: - #individual fields deal with None values themselves - new_data.setlist(name, [self.__class__.html2python(None)]) - except EmptyValue: - new_data.setlist(name, []) - - - def run_validator(self, new_data, validator): - if self.is_required or new_data.get(self.field_name, False) or hasattr(validator, 'always_test'): - if hasattr(self, 'requires_data_list'): - validator(new_data.getlist(self.field_name), new_data) - else: - validator(new_data.get(self.field_name, ''), new_data) - - def get_validation_errors(self, new_data): - errors = {} - if self.is_required and not new_data.get(self.field_name, False): - errors.setdefault(self.field_name, []).append(gettext('This field is required.')) - return errors - try: - for validator in self.validator_list: - try: - self.run_validator(new_data, validator) - except validators.ValidationError, e: - errors.setdefault(self.field_name, []).extend(e.messages) - # If a CriticalValidationError is raised, ignore any other ValidationErrors - # for this particular field - except validators.CriticalValidationError, e: - errors.setdefault(self.field_name, []).extend(e.messages) - return errors - - def get_id(self): - "Returns the HTML 'id' attribute for this form field." - return FORM_FIELD_ID_PREFIX + self.field_name - -#################### -# GENERIC WIDGETS # -#################### - -class TextField(FormField): - input_type = "text" - def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=None, member_name=None): - if validator_list is None: validator_list = [] - self.field_name = field_name - self.length, self.maxlength = length, maxlength - self.is_required = is_required - self.validator_list = [self.isValidLength, self.hasNoNewlines] + validator_list - if member_name != None: - self.member_name = member_name - - def isValidLength(self, data, form): - if data and self.maxlength and len(data.decode(settings.DEFAULT_CHARSET)) > self.maxlength: - raise validators.ValidationError, ngettext("Ensure your text is less than %s character.", - "Ensure your text is less than %s characters.", self.maxlength) % self.maxlength - - def hasNoNewlines(self, data, form): - if data and '\n' in data: - raise validators.ValidationError, gettext("Line breaks are not allowed here.") - - def render(self, data): - if data is None: - data = '' - maxlength = '' - if self.maxlength: - maxlength = 'maxlength="%s" ' % self.maxlength - if isinstance(data, unicode): - data = data.encode(settings.DEFAULT_CHARSET) - return '' % \ - (self.input_type, self.get_id(), self.__class__.__name__, self.is_required and ' required' or '', - self.field_name, self.length, escape(data), maxlength) - - def html2python(data): - return data - html2python = staticmethod(html2python) - -class PasswordField(TextField): - input_type = "password" - -class LargeTextField(TextField): - def __init__(self, field_name, rows=10, cols=40, is_required=False, validator_list=None, maxlength=None): - if validator_list is None: validator_list = [] - self.field_name = field_name - self.rows, self.cols, self.is_required = rows, cols, is_required - self.validator_list = validator_list[:] - if maxlength: - self.validator_list.append(self.isValidLength) - self.maxlength = maxlength - - def render(self, data): - if data is None: - data = '' - if isinstance(data, unicode): - data = data.encode(settings.DEFAULT_CHARSET) - return '' % \ - (self.get_id(), self.__class__.__name__, self.is_required and ' required' or '', - self.field_name, self.rows, self.cols, escape(data)) - -class HiddenField(FormField): - def __init__(self, field_name, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - self.field_name, self.is_required = field_name, is_required - self.validator_list = validator_list[:] - - def render(self, data): - return '' % \ - (self.get_id(), self.field_name, escape(data)) - -class CheckboxField(FormField): - def __init__(self, field_name, checked_by_default=False, validator_list=None, is_required=False): - if validator_list is None: validator_list = [] - self.field_name = field_name - self.checked_by_default = checked_by_default - self.is_required = is_required - self.validator_list = validator_list[:] - - def render(self, data): - checked_html = '' - if data or (data is '' and self.checked_by_default): - checked_html = ' checked="checked"' - return '' % \ - (self.get_id(), self.__class__.__name__, - self.field_name, checked_html) - - def html2python(data): - "Convert value from browser ('on' or '') to a Python boolean" - if data == 'on': - return True - return False - html2python = staticmethod(html2python) - -class SelectField(FormField): - def __init__(self, field_name, choices=None, size=1, is_required=False, validator_list=None, member_name=None): - if validator_list is None: validator_list = [] - if choices is None: choices = [] - self.field_name = field_name - # choices is a list of (value, human-readable key) tuples because order matters - self.choices, self.size, self.is_required = choices, size, is_required - self.validator_list = [self.isValidChoice] + validator_list - if member_name != None: - self.member_name = member_name - - def render(self, data): - output = ['') - return '\n'.join(output) - - def isValidChoice(self, data, form): - str_data = str(data) - str_choices = [str(item[0]) for item in self.choices] - if str_data not in str_choices: - raise validators.ValidationError, gettext("Select a valid choice; '%(data)s' is not in %(choices)s.") % {'data': str_data, 'choices': str_choices} - -class NullSelectField(SelectField): - "This SelectField converts blank fields to None" - def html2python(data): - if not data: - return None - return data - html2python = staticmethod(html2python) - -class RadioSelectField(FormField): - def __init__(self, field_name, choices=None, ul_class='', is_required=False, validator_list=None, member_name=None): - if validator_list is None: validator_list = [] - if choices is None: choices = [] - self.field_name = field_name - # choices is a list of (value, human-readable key) tuples because order matters - self.choices, self.is_required = choices, is_required - self.validator_list = [self.isValidChoice] + validator_list - self.ul_class = ul_class - if member_name != None: - self.member_name = member_name - - def render(self, data): - """ - Returns a special object, RadioFieldRenderer, that is iterable *and* - has a default str() rendered output. - - This allows for flexible use in templates. You can just use the default - rendering: - - {{ field_name }} - - ...which will output the radio buttons in an unordered list. - Or, you can manually traverse each radio option for special layout: - - {% for option in field_name.field_list %} - {{ option.field }} {{ option.label }}
    - {% endfor %} - """ - class RadioFieldRenderer: - def __init__(self, datalist, ul_class): - self.datalist, self.ul_class = datalist, ul_class - def __str__(self): - "Default str() output for this radio field -- a
      " - output = ['' % (self.ul_class and ' class="%s"' % self.ul_class or '')] - output.extend(['
    • %s %s
    • ' % (d['field'], d['label']) for d in self.datalist]) - output.append('
    ') - return ''.join(output) - def __iter__(self): - for d in self.datalist: - yield d - def __len__(self): - return len(self.datalist) - datalist = [] - str_data = str(data) # normalize to string - for i, (value, display_name) in enumerate(self.choices): - selected_html = '' - if str(value) == str_data: - selected_html = ' checked="checked"' - datalist.append({ - 'value': value, - 'name': display_name, - 'field': '' % \ - (self.get_id() + '_' + str(i), self.field_name, value, selected_html), - 'label': '' % \ - (self.get_id() + '_' + str(i), display_name), - }) - return RadioFieldRenderer(datalist, self.ul_class) - - def isValidChoice(self, data, form): - str_data = str(data) - str_choices = [str(item[0]) for item in self.choices] - if str_data not in str_choices: - raise validators.ValidationError, gettext("Select a valid choice; '%(data)s' is not in %(choices)s.") % {'data':str_data, 'choices':str_choices} - -class NullBooleanField(SelectField): - "This SelectField provides 'Yes', 'No' and 'Unknown', mapping results to True, False or None" - def __init__(self, field_name, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - SelectField.__init__(self, field_name, choices=[('1', 'Unknown'), ('2', 'Yes'), ('3', 'No')], - is_required=is_required, validator_list=validator_list) - - def render(self, data): - if data is None: data = '1' - elif data == True: data = '2' - elif data == False: data = '3' - return SelectField.render(self, data) - - def html2python(data): - return {None: None, '1': None, '2': True, '3': False}[data] - html2python = staticmethod(html2python) - -class SelectMultipleField(SelectField): - requires_data_list = True - def render(self, data): - output = ['') - return '\n'.join(output) - - def isValidChoice(self, field_data, all_data): - # data is something like ['1', '2', '3'] - str_choices = [str(item[0]) for item in self.choices] - for val in map(str, field_data): - if val not in str_choices: - raise validators.ValidationError, gettext("Select a valid choice; '%(data)s' is not in %(choices)s.") % {'data':val, 'choices':str_choices} - - def html2python(data): - if data is None: - raise EmptyValue - return data - html2python = staticmethod(html2python) - -class CheckboxSelectMultipleField(SelectMultipleField): - """ - This has an identical interface to SelectMultipleField, except the rendered - widget is different. Instead of a es. - - Of course, that results in multiple form elements for the same "single" - field, so this class's prepare() method flattens the split data elements - back into the single list that validators, renderers and save() expect. - """ - requires_data_list = True - def __init__(self, field_name, choices=None, ul_class='', validator_list=None): - if validator_list is None: validator_list = [] - if choices is None: choices = [] - self.ul_class = ul_class - SelectMultipleField.__init__(self, field_name, choices, size=1, is_required=False, validator_list=validator_list) - - def prepare(self, new_data): - # new_data has "split" this field into several fields, so flatten it - # back into a single list. - data_list = [] - for value, readable_value in self.choices: - if new_data.get('%s%s' % (self.field_name, value), '') == 'on': - data_list.append(value) - new_data.setlist(self.field_name, data_list) - - def render(self, data): - output = ['' % (self.ul_class and ' class="%s"' % self.ul_class or '')] - str_data_list = map(str, data) # normalize to strings - for value, choice in self.choices: - checked_html = '' - if str(value) in str_data_list: - checked_html = ' checked="checked"' - field_name = '%s%s' % (self.field_name, value) - output.append('
  • ' % \ - (self.get_id() + escape(value), self.__class__.__name__, field_name, checked_html, - self.get_id() + escape(value), choice)) - output.append('') - return '\n'.join(output) - -#################### -# FILE UPLOADS # -#################### - -class FileUploadField(FormField): - def __init__(self, field_name, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - self.field_name, self.is_required = field_name, is_required - self.validator_list = [self.isNonEmptyFile] + validator_list - - def isNonEmptyFile(self, field_data, all_data): - try: - content = field_data['content'] - except TypeError: - raise validators.CriticalValidationError, gettext("No file was submitted. Check the encoding type on the form.") - if not content: - raise validators.CriticalValidationError, gettext("The submitted file is empty.") - - def render(self, data): - return '' % \ - (self.get_id(), self.__class__.__name__, self.field_name) - - def html2python(data): - if data is None: - raise EmptyValue - return data - html2python = staticmethod(html2python) - -class ImageUploadField(FileUploadField): - "A FileUploadField that raises CriticalValidationError if the uploaded file isn't an image." - def __init__(self, *args, **kwargs): - FileUploadField.__init__(self, *args, **kwargs) - self.validator_list.insert(0, self.isValidImage) - - def isValidImage(self, field_data, all_data): - try: - validators.isValidImage(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages - -#################### -# INTEGERS/FLOATS # -#################### - -class IntegerField(TextField): - def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=None, member_name=None): - if validator_list is None: validator_list = [] - validator_list = [self.isInteger] + validator_list - if member_name is not None: - self.member_name = member_name - TextField.__init__(self, field_name, length, maxlength, is_required, validator_list) - - def isInteger(self, field_data, all_data): - try: - validators.isInteger(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages - - def html2python(data): - if data == '' or data is None: - return None - return int(data) - html2python = staticmethod(html2python) - -class SmallIntegerField(IntegerField): - def __init__(self, field_name, length=5, maxlength=5, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - validator_list = [self.isSmallInteger] + validator_list - IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list) - - def isSmallInteger(self, field_data, all_data): - if not -32768 <= int(field_data) <= 32767: - raise validators.CriticalValidationError, gettext("Enter a whole number between -32,768 and 32,767.") - -class PositiveIntegerField(IntegerField): - def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - validator_list = [self.isPositive] + validator_list - IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list) - - def isPositive(self, field_data, all_data): - if int(field_data) < 0: - raise validators.CriticalValidationError, gettext("Enter a positive number.") - -class PositiveSmallIntegerField(IntegerField): - def __init__(self, field_name, length=5, maxlength=None, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - validator_list = [self.isPositiveSmall] + validator_list - IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list) - - def isPositiveSmall(self, field_data, all_data): - if not 0 <= int(field_data) <= 32767: - raise validators.CriticalValidationError, gettext("Enter a whole number between 0 and 32,767.") - -class FloatField(TextField): - def __init__(self, field_name, max_digits, decimal_places, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - self.max_digits, self.decimal_places = max_digits, decimal_places - validator_list = [self.isValidFloat] + validator_list - TextField.__init__(self, field_name, max_digits+2, max_digits+2, is_required, validator_list) - - def isValidFloat(self, field_data, all_data): - v = validators.IsValidFloat(self.max_digits, self.decimal_places) - try: - v(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages - - def html2python(data): - if data == '' or data is None: - return None - return float(data) - html2python = staticmethod(html2python) - -#################### -# DATES AND TIMES # -#################### - -class DatetimeField(TextField): - """A FormField that automatically converts its data to a datetime.datetime object. - The data should be in the format YYYY-MM-DD HH:MM:SS.""" - def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - self.field_name = field_name - self.length, self.maxlength = length, maxlength - self.is_required = is_required - self.validator_list = [validators.isValidANSIDatetime] + validator_list - - def html2python(data): - "Converts the field into a datetime.datetime object" - import datetime - try: - date, time = data.split() - y, m, d = date.split('-') - timebits = time.split(':') - h, mn = timebits[:2] - if len(timebits) > 2: - s = int(timebits[2]) - else: - s = 0 - return datetime.datetime(int(y), int(m), int(d), int(h), int(mn), s) - except ValueError: - return None - html2python = staticmethod(html2python) - -class DateField(TextField): - """A FormField that automatically converts its data to a datetime.date object. - The data should be in the format YYYY-MM-DD.""" - def __init__(self, field_name, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - validator_list = [self.isValidDate] + validator_list - TextField.__init__(self, field_name, length=10, maxlength=10, - is_required=is_required, validator_list=validator_list) - - def isValidDate(self, field_data, all_data): - try: - validators.isValidANSIDate(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages - - def html2python(data): - "Converts the field into a datetime.date object" - import time, datetime - try: - time_tuple = time.strptime(data, '%Y-%m-%d') - return datetime.date(*time_tuple[0:3]) - except (ValueError, TypeError): - return None - html2python = staticmethod(html2python) - -class TimeField(TextField): - """A FormField that automatically converts its data to a datetime.time object. - The data should be in the format HH:MM:SS or HH:MM:SS.mmmmmm.""" - def __init__(self, field_name, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - validator_list = [self.isValidTime] + validator_list - TextField.__init__(self, field_name, length=8, maxlength=8, - is_required=is_required, validator_list=validator_list) - - def isValidTime(self, field_data, all_data): - try: - validators.isValidANSITime(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages - - def html2python(data): - "Converts the field into a datetime.time object" - import time, datetime - try: - part_list = data.split('.') - try: - time_tuple = time.strptime(part_list[0], '%H:%M:%S') - except ValueError: # seconds weren't provided - time_tuple = time.strptime(part_list[0], '%H:%M') - t = datetime.time(*time_tuple[3:6]) - if (len(part_list) == 2): - t = t.replace(microsecond=int(part_list[1])) - return t - except (ValueError, TypeError, AttributeError): - return None - html2python = staticmethod(html2python) - -#################### -# INTERNET-RELATED # -#################### - -class EmailField(TextField): - "A convenience FormField for validating e-mail addresses" - def __init__(self, field_name, length=50, maxlength=75, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - validator_list = [self.isValidEmail] + validator_list - TextField.__init__(self, field_name, length, maxlength=maxlength, - is_required=is_required, validator_list=validator_list) - - def isValidEmail(self, field_data, all_data): - try: - validators.isValidEmail(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages - -class URLField(TextField): - "A convenience FormField for validating URLs" - def __init__(self, field_name, length=50, maxlength=200, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - validator_list = [self.isValidURL] + validator_list - TextField.__init__(self, field_name, length=length, maxlength=maxlength, - is_required=is_required, validator_list=validator_list) - - def isValidURL(self, field_data, all_data): - try: - validators.isValidURL(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages - -class IPAddressField(TextField): - def __init__(self, field_name, length=15, maxlength=15, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - validator_list = [self.isValidIPAddress] + validator_list - TextField.__init__(self, field_name, length=length, maxlength=maxlength, - is_required=is_required, validator_list=validator_list) - - def isValidIPAddress(self, field_data, all_data): - try: - validators.isValidIPAddress4(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages - - def html2python(data): - return data or None - html2python = staticmethod(html2python) - -#################### -# MISCELLANEOUS # -#################### - -class FilePathField(SelectField): - "A SelectField whose choices are the files in a given directory." - def __init__(self, field_name, path, match=None, recursive=False, is_required=False, validator_list=None): - import os - from django.db.models import BLANK_CHOICE_DASH - if match is not None: - import re - match_re = re.compile(match) - choices = not is_required and BLANK_CHOICE_DASH[:] or [] - if recursive: - for root, dirs, files in os.walk(path): - for f in files: - if match is None or match_re.search(f): - choices.append((os.path.join(root, f), f)) - else: - try: - for f in os.listdir(path): - full_file = os.path.join(path, f) - if os.path.isfile(full_file) and (match is None or match_re.search(f)): - choices.append((full_file, f)) - except OSError: - pass - SelectField.__init__(self, field_name, choices, 1, is_required, validator_list) - -class PhoneNumberField(TextField): - "A convenience FormField for validating phone numbers (e.g. '630-555-1234')" - def __init__(self, field_name, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - validator_list = [self.isValidPhone] + validator_list - TextField.__init__(self, field_name, length=12, maxlength=12, - is_required=is_required, validator_list=validator_list) - - def isValidPhone(self, field_data, all_data): - try: - validators.isValidPhone(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages - -class USStateField(TextField): - "A convenience FormField for validating U.S. states (e.g. 'IL')" - def __init__(self, field_name, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - validator_list = [self.isValidUSState] + validator_list - TextField.__init__(self, field_name, length=2, maxlength=2, - is_required=is_required, validator_list=validator_list) - - def isValidUSState(self, field_data, all_data): - try: - validators.isValidUSState(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages - - def html2python(data): - return data.upper() # Should always be stored in upper case - html2python = staticmethod(html2python) - -class CommaSeparatedIntegerField(TextField): - "A convenience FormField for validating comma-separated integer fields" - def __init__(self, field_name, maxlength=None, is_required=False, validator_list=None): - if validator_list is None: validator_list = [] - validator_list = [self.isCommaSeparatedIntegerList] + validator_list - TextField.__init__(self, field_name, length=20, maxlength=maxlength, - is_required=is_required, validator_list=validator_list) - - def isCommaSeparatedIntegerList(self, field_data, all_data): - try: - validators.isCommaSeparatedIntegerList(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages - - def render(self, data): - if data is None: - data = '' - elif isinstance(data, (list, tuple)): - data = ','.join(data) - return super(CommaSeparatedIntegerField, self).render(data) - -class RawIdAdminField(CommaSeparatedIntegerField): - def html2python(data): - if data: - return data.split(',') - else: - return [] - html2python = staticmethod(html2python) - -class XMLLargeTextField(LargeTextField): - """ - A LargeTextField with an XML validator. The schema_path argument is the - full path to a Relax NG compact schema to validate against. - """ - def __init__(self, field_name, schema_path, **kwargs): - self.schema_path = schema_path - kwargs.setdefault('validator_list', []).insert(0, self.isValidXML) - LargeTextField.__init__(self, field_name, **kwargs) - - def isValidXML(self, field_data, all_data): - v = validators.RelaxNGCompact(self.schema_path) - try: - v(field_data, all_data) - except validators.ValidationError, e: - raise validators.CriticalValidationError, e.messages +from django.oldforms import * diff --git a/django/newforms/__init__.py b/django/newforms/__init__.py index a445a21bfbb7b..0d9c68f9e0d80 100644 --- a/django/newforms/__init__.py +++ b/django/newforms/__init__.py @@ -13,5 +13,5 @@ from util import ValidationError from widgets import * from fields import * -from forms import Form +from forms import * from models import * diff --git a/django/newforms/extras/__init__.py b/django/newforms/extras/__init__.py new file mode 100644 index 0000000000000..a7f6a9b3f613d --- /dev/null +++ b/django/newforms/extras/__init__.py @@ -0,0 +1 @@ +from widgets import * diff --git a/django/newforms/extras/widgets.py b/django/newforms/extras/widgets.py new file mode 100644 index 0000000000000..1011934fb8adb --- /dev/null +++ b/django/newforms/extras/widgets.py @@ -0,0 +1,59 @@ +""" +Extra HTML Widget classes +""" + +from django.newforms.widgets import Widget, Select +from django.utils.dates import MONTHS +import datetime + +__all__ = ('SelectDateWidget',) + +class SelectDateWidget(Widget): + """ + A Widget that splits date input into three . def __init__(self, attrs=None): self.attrs = attrs or {} - def render(self, name, value): + def render(self, name, value, attrs=None): + """ + Returns this Widget rendered as HTML, as a Unicode string. + + The 'value' given is not guaranteed to be valid input, so subclass + implementations should program defensively. + """ raise NotImplementedError def build_attrs(self, extra_attrs=None, **kwargs): @@ -130,7 +135,6 @@ def render(self, name, value, attrs=None, choices=()): return u'\n'.join(output) class SelectMultiple(Widget): - requires_data_list = True def __init__(self, attrs=None, choices=()): # choices can be any iterable self.attrs = attrs or {} @@ -185,6 +189,10 @@ def __iter__(self): for i, choice in enumerate(self.choices): yield RadioInput(self.name, self.value, self.attrs.copy(), choice, i) + def __getitem__(self, idx): + choice = self.choices[idx] # Let the IndexError propogate + return RadioInput(self.name, self.value, self.attrs.copy(), choice, idx) + def __unicode__(self): "Outputs a
      for this set of radio fields." return u'
        \n%s\n
      ' % u'\n'.join([u'
    • %s
    • ' % w for w in self]) diff --git a/django/utils/dateformat.py b/django/utils/dateformat.py index 0890a81a8117f..00eb9fe617bbb 100644 --- a/django/utils/dateformat.py +++ b/django/utils/dateformat.py @@ -11,7 +11,7 @@ >>> """ -from django.utils.dates import MONTHS, MONTHS_AP, WEEKDAYS +from django.utils.dates import MONTHS, MONTHS_3, MONTHS_AP, WEEKDAYS from django.utils.tzinfo import LocalTimezone from calendar import isleap, monthrange import re, time @@ -147,7 +147,7 @@ def m(self): def M(self): "Month, textual, 3 letters; e.g. 'Jan'" - return MONTHS[self.data.month][0:3] + return MONTHS_3[self.data.month].title() def n(self): "Month without leading zeros; i.e. '1' to '12'" diff --git a/django/utils/text.py b/django/utils/text.py index 9e7bb3b6c4472..217f42491b965 100644 --- a/django/utils/text.py +++ b/django/utils/text.py @@ -8,17 +8,28 @@ def wrap(text, width): """ A word-wrap function that preserves existing line breaks and most spaces in - the text. Expects that existing line breaks are posix newlines (\n). - See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061 + the text. Expects that existing line breaks are posix newlines. """ - return reduce(lambda line, word, width=width: '%s%s%s' % - (line, - ' \n'[(len(line[line.rfind('\n')+1:]) - + len(word.split('\n',1)[0] - ) >= width)], - word), - text.split(' ') - ) + def _generator(): + it = iter(text.split(' ')) + word = it.next() + yield word + pos = len(word) - word.rfind('\n') - 1 + for word in it: + if "\n" in word: + lines = word.splitlines() + else: + lines = (word,) + pos += len(lines[0]) + 1 + if pos > width: + yield '\n' + pos = len(lines[-1]) + else: + yield ' ' + if len(lines) > 1: + pos = len(lines[-1]) + yield word + return "".join(_generator()) def truncate_words(s, num): "Truncates a string after a certain number of words." diff --git a/docs/csrf.txt b/docs/csrf.txt index 218b43a61a387..c12dd1d116403 100644 --- a/docs/csrf.txt +++ b/docs/csrf.txt @@ -1,9 +1,9 @@ ===================================== -Cross Site Request Forgery Protection +Cross Site Request Forgery protection ===================================== -The CsrfMiddleware class provides easy-to-use protection against -`Cross Site Request Forgeries`_. This type of attack occurs when a malicious +The CsrfMiddleware class provides easy-to-use protection against +`Cross Site Request Forgeries`_. This type of attack occurs when a malicious web site creates a link or form button that is intended to perform some action on your web site, using the credentials of a logged-in user who is tricked into clicking on the link in their browser. @@ -12,12 +12,12 @@ The first defense against CSRF attacks is to ensure that GET requests are side-effect free. POST requests can then be protected by adding this middleware into your list of installed middleware. - .. _Cross Site Request Forgeries: http://www.squarefree.com/securitytips/web-developers.html#CSRF How to use it ============= -Add the middleware ``'django.contrib.csrf.middleware.CsrfMiddleware'`` to + +Add the middleware ``'django.contrib.csrf.middleware.CsrfMiddleware'`` to your list of middleware classes, ``MIDDLEWARE_CLASSES``. It needs to process the response after the SessionMiddleware, so must come before it in the list. It also must process the response before things like compression @@ -25,16 +25,17 @@ happen to the response, so it must come after GZipMiddleware in the list. How it works ============ + CsrfMiddleware does two things: -1. It modifies outgoing requests by adding a hidden form field to all - 'POST' forms, with the name 'csrfmiddlewaretoken' and a value which is - a hash of the session ID plus a secret. If there is no session ID set, - this modification of the response isn't done, so there is very little +1. It modifies outgoing requests by adding a hidden form field to all + 'POST' forms, with the name 'csrfmiddlewaretoken' and a value which is + a hash of the session ID plus a secret. If there is no session ID set, + this modification of the response isn't done, so there is very little performance penalty for those requests that don't have a session. -2. On all incoming POST requests that have the session cookie set, it - checks that the 'csrfmiddlewaretoken' is present and correct. If it +2. On all incoming POST requests that have the session cookie set, it + checks that the 'csrfmiddlewaretoken' is present and correct. If it isn't, the user will get a 403 error. This ensures that only forms that have originated from your web site @@ -43,26 +44,26 @@ can be used to POST data back. It deliberately only targets HTTP POST requests (and the corresponding POST forms). GET requests ought never to have side effects (if you are using HTTP GET and POST correctly), and so a CSRF attack with a GET -request will always be harmless. +request will always be harmless. POST requests that are not accompanied by a session cookie are not protected, but they do not need to be protected, since the 'attacking' web site could make these kind of requests anyway. -The Content-Type is checked before modifying the response, and only +The Content-Type is checked before modifying the response, and only pages that are served as 'text/html' or 'application/xml+xhtml' are modified. Limitations =========== + CsrfMiddleware requires Django's session framework to work. If you have a custom authentication system that manually sets cookies and the like, it won't help you. -If your app creates HTML pages and forms in some unusual way, (e.g. -it sends fragments of HTML in javascript document.write statements) -you might bypass the filter that adds the hidden field to the form, +If your app creates HTML pages and forms in some unusual way, (e.g. +it sends fragments of HTML in javascript document.write statements) +you might bypass the filter that adds the hidden field to the form, in which case form submission will always fail. It may still be possible to use the middleware, provided you can find some way to get the -CSRF token and ensure that is included when your form is submitted. - +CSRF token and ensure that is included when your form is submitted. \ No newline at end of file diff --git a/docs/db-api.txt b/docs/db-api.txt index 2f0c8b05892f0..13a32bd0b8812 100644 --- a/docs/db-api.txt +++ b/docs/db-api.txt @@ -143,9 +143,9 @@ or ``UPDATE`` SQL statements. Specifically, when you call ``save()``, Django follows this algorithm: * If the object's primary key attribute is set to a value that evaluates to - ``False`` (such as ``None`` or the empty string), Django executes a - ``SELECT`` query to determine whether a record with the given primary key - already exists. + ``True`` (i.e., a value other than ``None`` or the empty string), Django + executes a ``SELECT`` query to determine whether a record with the given + primary key already exists. * If the record with the given primary key does already exist, Django executes an ``UPDATE`` query. * If the object's primary key attribute is *not* set, or if it's set but a diff --git a/docs/legacy_databases.txt b/docs/legacy_databases.txt index 66cb1a2ef4d4f..094c534e72cce 100644 --- a/docs/legacy_databases.txt +++ b/docs/legacy_databases.txt @@ -22,7 +22,6 @@ what the name of the database is. Do that by editing these settings in your * `DATABASE_ENGINE`_ * `DATABASE_USER`_ * `DATABASE_PASSWORD`_ - * `DATABASE_NAME`_ * `DATABASE_HOST`_ * `DATABASE_PORT`_ @@ -31,7 +30,6 @@ what the name of the database is. Do that by editing these settings in your .. _DATABASE_ENGINE: http://www.djangoproject.com/documentation/settings/#database-engine .. _DATABASE_USER: http://www.djangoproject.com/documentation/settings/#database-user .. _DATABASE_PASSWORD: http://www.djangoproject.com/documentation/settings/#database-password -.. _DATABASE_NAME: http://www.djangoproject.com/documentation/settings/#database-name .. _DATABASE_HOST: http://www.djangoproject.com/documentation/settings/#database-host .. _DATABASE_PORT: http://www.djangoproject.com/documentation/settings/#database-port diff --git a/docs/newforms.txt b/docs/newforms.txt index c4986eb45e280..ec721effc4451 100644 --- a/docs/newforms.txt +++ b/docs/newforms.txt @@ -22,7 +22,7 @@ release, our plan is to do the following: from django import oldforms as forms # new * At an undecided future date, we will move the current ``django.newforms`` - to ``django.forms``. This will be a backwards-incompatible change, and + to ``django.forms``. This will be a backwards-incompatible change, and anybody who is still using the old version of ``django.forms`` at that time will need to change their import statements, as described in the previous bullet. @@ -282,6 +282,13 @@ example, in the ``ContactForm`` example, the fields are defined in the order ``subject``, ``message``, ``sender``, ``cc_myself``. To reorder the HTML output, just change the order in which those fields are listed in the class. +Using forms to validate data +---------------------------- + +In addition to HTML form display, a ``Form`` class is responsible for +validating data. + + More coming soon ================ @@ -290,9 +297,6 @@ http://code.djangoproject.com/browser/django/trunk/tests/regressiontests/forms/t -- the unit tests for ``django.newforms``. This can give you a good idea of what's possible. -Using forms to validate data ----------------------------- - Using forms with templates ========================== diff --git a/docs/redirects.txt b/docs/redirects.txt index e0bcb2f1fa7c5..13f08668c8c9f 100644 --- a/docs/redirects.txt +++ b/docs/redirects.txt @@ -63,9 +63,9 @@ Via the Python API ------------------ Redirects are represented by a standard `Django model`_, which lives in -`django/contrib/redirects/models/redirects.py`_. You can access redirect +`django/contrib/redirects/models.py`_. You can access redirect objects via the `Django database API`_. .. _Django model: http://www.djangoproject.com/documentation/model_api/ -.. _django/contrib/redirects/models/redirects.py: http://code.djangoproject.com/browser/django/trunk/django/contrib/redirects/models/redirects.py +.. _django/contrib/redirects/models.py: http://code.djangoproject.com/browser/django/trunk/django/contrib/redirects/models.py .. _Django database API: http://www.djangoproject.com/documentation/db_api/ diff --git a/tests/modeltests/basic/models.py b/tests/modeltests/basic/models.py index ebf870ee1231b..2ebe857e7e968 100644 --- a/tests/modeltests/basic/models.py +++ b/tests/modeltests/basic/models.py @@ -12,6 +12,9 @@ class Article(models.Model): class Meta: ordering = ('pub_date',) + class Meta: + ordering = ('pub_date','headline') + def __str__(self): return self.headline @@ -247,7 +250,7 @@ def __str__(self): # Slices (without step) are lazy: >>> Article.objects.all()[0:5].filter() -[, , , , ] +[, , , , ] # Slicing again works: >>> Article.objects.all()[0:5][0:2] @@ -255,17 +258,17 @@ def __str__(self): >>> Article.objects.all()[0:5][:2] [, ] >>> Article.objects.all()[0:5][4:] -[] +[] >>> Article.objects.all()[0:5][5:] [] # Some more tests! >>> Article.objects.all()[2:][0:2] -[, ] +[, ] >>> Article.objects.all()[2:][:2] -[, ] +[, ] >>> Article.objects.all()[2:][2:3] -[] +[] # Note that you can't use 'offset' without 'limit' (on some dbs), so this doesn't work: >>> Article.objects.all()[2:] @@ -314,7 +317,7 @@ def __str__(self): # Bulk delete test: How many objects before and after the delete? >>> Article.objects.all() -[, , , , , , , ] +[, , , , , , , ] >>> Article.objects.filter(id__lte=4).delete() >>> Article.objects.all() [, , , ] diff --git a/tests/modeltests/many_to_many/models.py b/tests/modeltests/many_to_many/models.py index 357f3ca629be0..38f8931ee7e52 100644 --- a/tests/modeltests/many_to_many/models.py +++ b/tests/modeltests/many_to_many/models.py @@ -231,4 +231,16 @@ class Meta: >>> p1.article_set.all() [] +# An alternate to calling clear() is to assign the empty set +>>> p1.article_set = [] +>>> p1.article_set.all() +[] + +>>> a2.publications = [p1, new_publication] +>>> a2.publications.all() +[, ] +>>> a2.publications = [] +>>> a2.publications.all() +[] + """} diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py index b51b4e1a8bce0..60a3defde503f 100644 --- a/tests/modeltests/model_forms/models.py +++ b/tests/modeltests/model_forms/models.py @@ -2,13 +2,26 @@ 34. Generating HTML forms from models Django provides shortcuts for creating Form objects from a model class. + +The function django.newforms.form_for_model() takes a model class and returns +a Form that is tied to the model. This Form works just like any other Form, +with one additional method: create(). The create() method creates an instance +of the model and returns that newly created instance. It saves the instance to +the database if create(save=True), which is default. If you pass +create(save=False), then you'll get the object without saving it. """ from django.db import models class Category(models.Model): name = models.CharField(maxlength=20) - url = models.CharField('The URL', maxlength=20) + url = models.CharField('The URL', maxlength=40) + + def __str__(self): + return self.name + +class Writer(models.Model): + name = models.CharField(maxlength=50) def __str__(self): return self.name @@ -16,29 +29,156 @@ def __str__(self): class Article(models.Model): headline = models.CharField(maxlength=50) pub_date = models.DateTimeField() - categories = models.ManyToManyField(Category) + writer = models.ForeignKey(Writer) + categories = models.ManyToManyField(Category, blank=True) def __str__(self): return self.headline __test__ = {'API_TESTS': """ ->>> from django.newforms import form_for_model +>>> from django.newforms import form_for_model, form_for_instance, BaseForm +>>> import datetime + +>>> Category.objects.all() +[] + >>> CategoryForm = form_for_model(Category) >>> f = CategoryForm() >>> print f - - - + + >>> print f.as_ul() -
    • -
    • -
    • +
    • +
    • >>> print f['name'] - + >>> f = CategoryForm(auto_id=False) >>> print f.as_ul() -
    • ID:
    • -
    • Name:
    • -
    • The URL:
    • +
    • Name:
    • +
    • The URL:
    • + +>>> f = CategoryForm({'name': 'Entertainment', 'url': 'entertainment'}) +>>> f.errors +{} +>>> f.clean_data +{'url': u'entertainment', 'name': u'Entertainment'} +>>> obj = f.create() +>>> obj + +>>> Category.objects.all() +[] + +>>> f = CategoryForm({'name': "It's a test", 'url': 'test'}) +>>> f.errors +{} +>>> f.clean_data +{'url': u'test', 'name': u"It's a test"} +>>> obj = f.create() +>>> obj + +>>> Category.objects.all() +[, ] + +>>> f = CategoryForm({'name': 'Third test', 'url': 'third'}) +>>> f.errors +{} +>>> f.clean_data +{'url': u'third', 'name': u'Third test'} +>>> obj = f.create(save=False) +>>> obj + +>>> Category.objects.all() +[, ] +>>> obj.save() +>>> Category.objects.all() +[, , ] + +>>> f = CategoryForm({'name': '', 'url': 'foo'}) +>>> f.errors +{'name': [u'This field is required.']} +>>> f.clean_data +>>> f.create() +Traceback (most recent call last): +... +ValueError: The Category could not be created because the data didn't validate. + +>>> f = CategoryForm({'name': '', 'url': 'foo'}) +>>> f.create() +Traceback (most recent call last): +... +ValueError: The Category could not be created because the data didn't validate. + +Create a couple of Writers. +>>> w = Writer(name='Mike Royko') +>>> w.save() +>>> w = Writer(name='Bob Woodward') +>>> w.save() + +ManyToManyFields are represented by a MultipleChoiceField, and ForeignKeys are +represented by a ChoiceField. +>>> ArticleForm = form_for_model(Article) +>>> f = ArticleForm(auto_id=False) +>>> print f +Headline: +Pub date: +Writer: +Categories: + +You can pass a custom Form class to form_for_model. Make sure it's a +subclass of BaseForm, not Form. +>>> class CustomForm(BaseForm): +... def say_hello(self): +... print 'hello' +>>> CategoryForm = form_for_model(Category, form=CustomForm) +>>> f = CategoryForm() +>>> f.say_hello() +hello + +Use form_for_instance to create a Form from a model instance. There are two +differences between this Form and one created via form_for_model. First, the +object's current values are inserted as 'initial' data in each Field. Second, +the Form gets an apply_changes() method instead of a create() method. +>>> w = Writer.objects.get(name='Mike Royko') +>>> RoykoForm = form_for_instance(w) +>>> f = RoykoForm(auto_id=False) +>>> print f +Name: + +>>> art = Article(headline='Test article', pub_date=datetime.date(1988, 1, 4), writer=w) +>>> art.save() +>>> art.id +1 +>>> TestArticleForm = form_for_instance(art) +>>> f = TestArticleForm(auto_id=False) +>>> print f.as_ul() +
    • Headline:
    • +
    • Pub date:
    • +
    • Writer:
    • +
    • Categories:
    • +>>> f = TestArticleForm({'headline': u'New headline', 'pub_date': u'1988-01-04', 'writer': u'1'}) +>>> f.is_valid() +True +>>> new_art = f.apply_changes() +>>> new_art.id +1 +>>> new_art = Article.objects.get(id=1) +>>> new_art.headline +'New headline' """} diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py index c75cedab14c0d..79a320c131b63 100644 --- a/tests/regressiontests/forms/tests.py +++ b/tests/regressiontests/forms/tests.py @@ -514,6 +514,25 @@ beatle J G George False beatle J R Ringo False +A RadioFieldRenderer object also allows index access to individual RadioInput +objects. +>>> w = RadioSelect() +>>> r = w.render('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))) +>>> print r[1] + +>>> print r[0] + +>>> r[0].is_checked() +True +>>> r[1].is_checked() +False +>>> r[1].name, r[1].value, r[1].choice_value, r[1].choice_label +('beatle', u'J', 'P', 'Paul') +>>> r[10] +Traceback (most recent call last): +... +IndexError: list index out of range + # CheckboxSelectMultiple Widget ############################################### >>> w = CheckboxSelectMultiple() @@ -639,6 +658,8 @@ label -- A verbose name for this field, for use in displaying this field in a form. By default, Django will use a "pretty" version of the form field name, if the Field is part of a Form. + initial -- A value to use in this Field's initial display. This value is + *not* used as a fallback if data isn't given. Other than that, the Field subclasses have class-specific options for __init__(). For example, CharField has a max_length option. @@ -687,9 +708,21 @@ CharField accepts an optional min_length parameter: >>> f = CharField(min_length=10, required=False) >>> f.clean('') +u'' +>>> f.clean('12345') Traceback (most recent call last): ... ValidationError: [u'Ensure this value has at least 10 characters.'] +>>> f.clean('1234567890') +u'1234567890' +>>> f.clean('1234567890a') +u'1234567890a' + +>>> f = CharField(min_length=10, required=True) +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] >>> f.clean('12345') Traceback (most recent call last): ... @@ -757,6 +790,71 @@ ... ValidationError: [u'Enter a whole number.'] +IntegerField accepts an optional max_value parameter: +>>> f = IntegerField(max_value=10) +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean(1) +1 +>>> f.clean(10) +10 +>>> f.clean(11) +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value is less than or equal to 10.'] +>>> f.clean('10') +10 +>>> f.clean('11') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value is less than or equal to 10.'] + +IntegerField accepts an optional min_value parameter: +>>> f = IntegerField(min_value=10) +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean(1) +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value is greater than or equal to 10.'] +>>> f.clean(10) +10 +>>> f.clean(11) +11 +>>> f.clean('10') +10 +>>> f.clean('11') +11 + +min_value and max_value can be used together: +>>> f = IntegerField(min_value=10, max_value=20) +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean(1) +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value is greater than or equal to 10.'] +>>> f.clean(10) +10 +>>> f.clean(11) +11 +>>> f.clean('10') +10 +>>> f.clean('11') +11 +>>> f.clean(20) +20 +>>> f.clean(21) +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value is less than or equal to 20.'] + # DateField ################################################################### >>> import datetime @@ -1002,7 +1100,7 @@ ValidationError: [u'Enter a valid value.'] RegexField takes an optional error_message argument: ->>> f = RegexField('^\d\d\d\d$', 'Enter a four-digit number.') +>>> f = RegexField('^\d\d\d\d$', error_message='Enter a four-digit number.') >>> f.clean('1234') u'1234' >>> f.clean('123') @@ -1014,6 +1112,29 @@ ... ValidationError: [u'Enter a four-digit number.'] +RegexField also access min_length and max_length parameters, for convenience. +>>> f = RegexField('^\d+$', min_length=5, max_length=10) +>>> f.clean('123') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value has at least 5 characters.'] +>>> f.clean('abc') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value has at least 5 characters.'] +>>> f.clean('12345') +u'12345' +>>> f.clean('1234567890') +u'1234567890' +>>> f.clean('12345678901') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value has at most 10 characters.'] +>>> f.clean('12345a') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid value.'] + # EmailField ################################################################## >>> f = EmailField() @@ -1060,6 +1181,19 @@ ... ValidationError: [u'Enter a valid e-mail address.'] +EmailField also access min_length and max_length parameters, for convenience. +>>> f = EmailField(min_length=10, max_length=15) +>>> f.clean('a@foo.com') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value has at least 10 characters.'] +>>> f.clean('alf@foo.com') +u'alf@foo.com' +>>> f.clean('alf123456788@foo.com') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value has at most 15 characters.'] + # URLField ################################################################## >>> f = URLField() @@ -1152,6 +1286,19 @@ ... ValidationError: [u'This URL appears to be a broken link.'] +EmailField also access min_length and max_length parameters, for convenience. +>>> f = URLField(min_length=15, max_length=20) +>>> f.clean('http://f.com') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value has at least 15 characters.'] +>>> f.clean('http://example.com') +u'http://example.com' +>>> f.clean('http://abcdefghijklmnopqrstuvwxyz.com') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value has at most 20 characters.'] + # BooleanField ################################################################ >>> f = BooleanField() @@ -1398,19 +1545,13 @@ >>> p.is_valid() False >>> print p -
      • This field is required.
      - -
      • This field is required.
      - -
      • This field is required.
      - +
      • This field is required.
      +
      • This field is required.
      +
      • This field is required.
      >>> print p.as_table() -
      • This field is required.
      - -
      • This field is required.
      - -
      • This field is required.
      - +
      • This field is required.
      +
      • This field is required.
      +
      • This field is required.
      >>> print p.as_ul()
      • This field is required.
      • This field is required.
    • @@ -1799,12 +1940,9 @@ {} >>> f = UserRegistration({}, auto_id=False) >>> print f.as_table() -
      • This field is required.
      -Username: -
      • This field is required.
      -Password1: -
      • This field is required.
      -Password2: +Username:
      • This field is required.
      +Password1:
      • This field is required.
      +Password2:
      • This field is required.
      >>> f.errors {'username': [u'This field is required.'], 'password1': [u'This field is required.'], 'password2': [u'This field is required.']} >>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'bar'}, auto_id=False) @@ -1972,6 +2110,8 @@
    • Username:
    • Password:
    • +# Specifying labels ########################################################### + You can specify the label for a field by using the 'label' argument to a Field class. If you don't specify 'label', Django will use the field name with underscores converted to spaces, and the initial letter capitalized. @@ -1985,6 +2125,81 @@
    • Password1:
    • Password (again):
    • +A label can be a Unicode object or a bytestring with special characters. +>>> class UserRegistration(Form): +... username = CharField(max_length=10, label='ŠĐĆŽćžšđ') +... password = CharField(widget=PasswordInput, label=u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111') +>>> p = UserRegistration(auto_id=False) +>>> p.as_ul() +u'
    • \u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111:
    • \n
    • \u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111:
    • ' + +If a label is set to the empty string for a field, that field won't get a label. +>>> class UserRegistration(Form): +... username = CharField(max_length=10, label='') +... password = CharField(widget=PasswordInput) +>>> p = UserRegistration(auto_id=False) +>>> print p.as_ul() +
    • +
    • Password:
    • +>>> p = UserRegistration(auto_id='id_%s') +>>> print p.as_ul() +
    • +
    • + +If label is None, Django will auto-create the label from the field name. This +is default behavior. +>>> class UserRegistration(Form): +... username = CharField(max_length=10, label=None) +... password = CharField(widget=PasswordInput) +>>> p = UserRegistration(auto_id=False) +>>> print p.as_ul() +
    • Username:
    • +
    • Password:
    • +>>> p = UserRegistration(auto_id='id_%s') +>>> print p.as_ul() +
    • +
    • + +# Initial data ################################################################ + +You can specify initial data for a field by using the 'initial' argument to a +Field class. This initial data is displayed when a Form is rendered with *no* +data. It is not displayed when a Form is rendered with any data (including an +empty dictionary). Also, the initial value is *not* used if data for a +particular required field isn't provided. +>>> class UserRegistration(Form): +... username = CharField(max_length=10, initial='django') +... password = CharField(widget=PasswordInput) + +Here, we're not submitting any data, so the initial value will be displayed. +>>> p = UserRegistration(auto_id=False) +>>> print p.as_ul() +
    • Username:
    • +
    • Password:
    • + +Here, we're submitting data, so the initial value will *not* be displayed. +>>> p = UserRegistration({}, auto_id=False) +>>> print p.as_ul() +
      • This field is required.
      Username:
    • +
      • This field is required.
      Password:
    • +>>> p = UserRegistration({'username': u''}, auto_id=False) +>>> print p.as_ul() +
      • This field is required.
      Username:
    • +
      • This field is required.
      Password:
    • +>>> p = UserRegistration({'username': u'foo'}, auto_id=False) +>>> print p.as_ul() +
    • Username:
    • +
      • This field is required.
      Password:
    • + +An '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'}) +>>> p.errors +{'username': [u'This field is required.']} +>>> p.is_valid() +False + # Forms with prefixes ######################################################### Sometimes it's necessary to have multiple forms display on the same HTML page, @@ -2133,8 +2348,7 @@ - - +
      • Please make sure your passwords match.
      • Ensure this value has at most 10 characters.
      Username:
      Username:
      • Ensure this value has at most 10 characters.
      Password1:
      Password2:
      @@ -2257,6 +2471,141 @@

      + +################# +# Extra widgets # +################# + +The newforms library comes with some extra, higher-level Widget classes that +demonstrate some of the library's abilities. + +# SelectDateWidget ############################################################ + +>>> from django.newforms.extras import SelectDateWidget +>>> w = SelectDateWidget() +>>> print w.render('mydate', '') + + + +>>> w.render('mydate', None) == w.render('mydate', '') +True +>>> print w.render('mydate', '2010-04-15') + + + + """ if __name__ == "__main__":