Changeset 4254
- Timestamp:
- 12/28/06 11:17:52 (2 years ago)
- Files:
-
- django/branches/boulder-oracle-sprint/AUTHORS (modified) (2 diffs)
- django/branches/boulder-oracle-sprint/django/contrib/admin/templates/admin/base.html (modified) (1 diff)
- django/branches/boulder-oracle-sprint/django/contrib/admin/templates/admin/change_form.html (modified) (2 diffs)
- django/branches/boulder-oracle-sprint/django/contrib/admin/templates/admin/change_list.html (modified) (1 diff)
- django/branches/boulder-oracle-sprint/django/contrib/csrf/middleware.py (modified) (1 diff)
- django/branches/boulder-oracle-sprint/django/core/handlers/base.py (modified) (1 diff)
- django/branches/boulder-oracle-sprint/django/db/backends/oracle/creation.py (modified) (1 diff)
- django/branches/boulder-oracle-sprint/django/db/backends/postgresql/base.py (modified) (3 diffs)
- django/branches/boulder-oracle-sprint/django/db/models/fields/__init__.py (modified) (9 diffs)
- django/branches/boulder-oracle-sprint/django/db/models/fields/related.py (modified) (9 diffs)
- django/branches/boulder-oracle-sprint/django/forms/__init__.py (modified) (1 diff)
- django/branches/boulder-oracle-sprint/django/newforms/extras (copied) (copied from django/trunk/django/newforms/extras)
- django/branches/boulder-oracle-sprint/django/newforms/extras/__init__.py (copied) (copied from django/trunk/django/newforms/extras/__init__.py)
- django/branches/boulder-oracle-sprint/django/newforms/extras/widgets.py (copied) (copied from django/trunk/django/newforms/extras/widgets.py)
- django/branches/boulder-oracle-sprint/django/newforms/fields.py (modified) (15 diffs)
- django/branches/boulder-oracle-sprint/django/newforms/forms.py (modified) (6 diffs)
- django/branches/boulder-oracle-sprint/django/newforms/__init__.py (modified) (1 diff)
- django/branches/boulder-oracle-sprint/django/newforms/models.py (modified) (1 diff)
- django/branches/boulder-oracle-sprint/django/newforms/widgets.py (modified) (4 diffs)
- django/branches/boulder-oracle-sprint/django/utils/dateformat.py (modified) (2 diffs)
- django/branches/boulder-oracle-sprint/django/utils/text.py (modified) (1 diff)
- django/branches/boulder-oracle-sprint/docs/csrf.txt (modified) (6 diffs)
- django/branches/boulder-oracle-sprint/docs/db-api.txt (modified) (1 diff)
- django/branches/boulder-oracle-sprint/docs/legacy_databases.txt (modified) (2 diffs)
- django/branches/boulder-oracle-sprint/docs/newforms.txt (modified) (3 diffs)
- django/branches/boulder-oracle-sprint/docs/redirects.txt (modified) (1 diff)
- django/branches/boulder-oracle-sprint/tests/modeltests/basic/models.py (modified) (5 diffs)
- django/branches/boulder-oracle-sprint/tests/modeltests/many_to_many/models.py (modified) (1 diff)
- django/branches/boulder-oracle-sprint/tests/modeltests/model_forms/models.py (modified) (4 diffs)
- django/branches/boulder-oracle-sprint/tests/regressiontests/forms/tests.py (modified) (14 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
django/branches/boulder-oracle-sprint/AUTHORS
r4212 r4254 119 119 Petar Marić 120 120 mark@junklight.com 121 Yasushi Masuda <whosaysni@gmail.com> 121 122 mattycakes@gmail.com 122 123 Jason McBrayer <http://www.carcosa.net/jason/> 123 124 mccutchen@gmail.com 124 125 michael.mcewan@gmail.com 126 mitakummaa@gmail.com 125 127 mmarshall 126 128 Eric Moritz <http://eric.themoritzfamily.com/> … … 161 163 Joe Topjian <http://joe.terrarum.net/geek/code/python/django/> 162 164 Karen Tracey <graybark@bellsouth.net> 165 Makoto Tsuyuki <mtsuyuki@gmail.com> 163 166 Amit Upadhyay 164 167 Geert Vanderkelen django/branches/boulder-oracle-sprint/django/contrib/admin/templates/admin/base.html
r3600 r4254 39 39 {% block pretitle %}{% endblock %} 40 40 {% block content_title %}{% if title %}<h1>{{ title|escape }}</h1>{% endif %}{% endblock %} 41 {% block content %}{{ content }}{% endblock %} 41 {% block content %} 42 {% block object-tools %}{% endblock %} 43 {{ content }} 44 {% endblock %} 42 45 {% block sidebar %}{% endblock %} 43 46 <br class="clear" /> django/branches/boulder-oracle-sprint/django/contrib/admin/templates/admin/change_form.html
r3568 r4254 17 17 {% endif %}{% endblock %} 18 18 {% block content %}<div id="content-main"> 19 {% block object-tools %} 19 20 {% if change %}{% if not is_popup %} 20 21 <ul class="object-tools"><li><a href="history/" class="historylink">{% trans "History" %}</a></li> … … 22 23 </ul> 23 24 {% endif %}{% endif %} 25 {% endblock %} 24 26 <form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% block form_top %}{% endblock %} 25 27 <div> django/branches/boulder-oracle-sprint/django/contrib/admin/templates/admin/change_list.html
r3349 r4254 8 8 {% block content %} 9 9 <div id="content-main"> 10 {% block object-tools %} 10 11 {% if has_add_permission %} 11 12 <ul class="object-tools"><li><a href="add/{% if is_popup %}?_popup=1{% endif %}" class="addlink">{% blocktrans with cl.opts.verbose_name|escape as name %}Add {{ name }}{% endblocktrans %}</a></li></ul> 12 13 {% endif %} 14 {% endblock %} 13 15 <div class="module{% if cl.has_filters %} filtered{% endif %}" id="changelist"> 14 16 {% block search %}{% search_form cl %}{% endblock %} django/branches/boulder-oracle-sprint/django/contrib/csrf/middleware.py
r2900 r4254 12 12 import itertools 13 13 14 _ERROR_MSG = "<h1>403 Forbidden</h1><p>Cross Site Request Forgery detected. Request aborted.</p>"14 _ERROR_MSG = '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"><body><h1>403 Forbidden</h1><p>Cross Site Request Forgery detected. Request aborted.</p></body></html>' 15 15 16 16 _POST_FORM_RE = \ django/branches/boulder-oracle-sprint/django/core/handlers/base.py
r4212 r4254 61 61 return response 62 62 63 resolver = urlresolvers.RegexURLResolver(r'^/', settings.ROOT_URLCONF) 63 # Get urlconf from request object, if available. Otherwise use default. 64 urlconf = getattr(request, "urlconf", settings.ROOT_URLCONF) 65 66 resolver = urlresolvers.RegexURLResolver(r'^/', urlconf) 64 67 try: 65 68 callback, callback_args, callback_kwargs = resolver.resolve(request.path) django/branches/boulder-oracle-sprint/django/db/backends/oracle/creation.py
r4085 r4254 82 82 #settings.DATABASE_USER = 'old_user' 83 83 #settings.DATABASE_PASSWORD = 'old_password' 84 settings.DATABASE_USER = 'mboersma' 85 settings.DATABASE_PASSWORD = 'password' 84 86 85 87 cursor = connection.cursor() django/branches/boulder-oracle-sprint/django/db/backends/postgresql/base.py
r4084 r4254 20 20 # Import copy of _thread_local.py from Python 2.4 21 21 from django.utils._threading_local import local 22 23 def smart_basestring(s, charset): 24 if isinstance(s, unicode): 25 return s.encode(charset) 26 return s 27 28 class UnicodeCursorWrapper(object): 29 """ 30 A thin wrapper around psycopg cursors that allows them to accept Unicode 31 strings as params. 32 33 This is necessary because psycopg doesn't apply any DB quoting to 34 parameters that are Unicode strings. If a param is Unicode, this will 35 convert it to a bytestring using DEFAULT_CHARSET before passing it to 36 psycopg. 37 """ 38 def __init__(self, cursor, charset): 39 self.cursor = cursor 40 self.charset = charset 41 42 def execute(self, sql, params=()): 43 return self.cursor.execute(sql, [smart_basestring(p, self.charset) for p in params]) 44 45 def executemany(self, sql, param_list): 46 new_param_list = [[smart_basestring(p, self.charset) for p in params] for params in param_list] 47 return self.cursor.executemany(sql, new_param_list) 48 49 def __getattr__(self, attr): 50 if self.__dict__.has_key(attr): 51 return self.__dict__[attr] 52 else: 53 return getattr(self.cursor, attr) 22 54 23 55 class DatabaseWrapper(local): … … 46 78 cursor = self.connection.cursor() 47 79 cursor.execute("SET TIME ZONE %s", [settings.TIME_ZONE]) 80 cursor = UnicodeCursorWrapper(cursor, settings.DEFAULT_CHARSET) 48 81 if settings.DEBUG: 49 82 return util.CursorDebugWrapper(cursor, self) … … 132 165 Database.register_type(Database.new_type((1082,), "DATE", util.typecast_date)) 133 166 except AttributeError: 134 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"167 raise Exception, "You appear to be using psycopg version 2. Set your DATABASE_ENGINE to 'postgresql_psycopg2' instead of 'postgresql'." 135 168 Database.register_type(Database.new_type((1083,1266), "TIME", util.typecast_time)) 136 169 Database.register_type(Database.new_type((1114,1184), "TIMESTAMP", util.typecast_timestamp)) django/branches/boulder-oracle-sprint/django/db/models/fields/__init__.py
r4212 r4254 338 338 choices = property(_get_choices) 339 339 340 def formfield(self ):340 def formfield(self, initial=None): 341 341 "Returns a django.newforms.Field instance for this database Field." 342 342 from django.newforms import CharField 343 343 # TODO: This is just a temporary default during development. 344 return CharField(label=capfirst(self.verbose_name)) 344 return forms.CharField(required=not self.blank, label=capfirst(self.verbose_name), initial=initial) 345 346 def value_from_object(self, obj): 347 "Returns the value of this field in the given model instance." 348 return getattr(obj, self.attname) 345 349 346 350 class AutoField(Field): … … 380 384 cls._meta.has_auto_field = True 381 385 386 def formfield(self, initial=None): 387 return None 388 382 389 class BooleanField(Field): 383 390 def __init__(self, *args, **kwargs): … … 393 400 def get_manipulator_field_objs(self): 394 401 return [oldforms.CheckboxField] 402 403 def formfield(self, initial=None): 404 return forms.BooleanField(required=not self.blank, label=capfirst(self.verbose_name), initial=initial) 395 405 396 406 class CharField(Field): … … 407 417 raise validators.ValidationError, gettext_lazy("This field cannot be null.") 408 418 return str(value) 419 420 def formfield(self, initial=None): 421 return forms.CharField(max_length=self.maxlength, required=not self.blank, label=capfirst(self.verbose_name), initial=initial) 409 422 410 423 # TODO: Maybe move this into contrib, because it's specialized. … … 481 494 return [oldforms.DateField] 482 495 483 def flatten_data(self, follow, obj =None):496 def flatten_data(self, follow, obj=None): 484 497 val = self._get_val_from_obj(obj) 485 498 return {self.attname: (val is not None and val.strftime("%Y-%m-%d") or '')} 499 500 def formfield(self, initial=None): 501 return forms.DateField(required=not self.blank, label=capfirst(self.verbose_name), initial=initial) 486 502 487 503 class DateTimeField(DateField): … … 554 570 time_field: (val is not None and val.strftime("%H:%M:%S") or '')} 555 571 572 def formfield(self, initial=None): 573 return forms.DateTimeField(required=not self.blank, label=capfirst(self.verbose_name), initial=initial) 574 556 575 class EmailField(CharField): 557 576 def __init__(self, *args, **kwargs): … … 567 586 def validate(self, field_data, all_data): 568 587 validators.isValidEmail(field_data, all_data) 588 589 def formfield(self, initial=None): 590 return forms.EmailField(required=not self.blank, label=capfirst(self.verbose_name), initial=initial) 569 591 570 592 class FileField(Field): … … 700 722 return [oldforms.IntegerField] 701 723 724 def formfield(self, initial=None): 725 return forms.IntegerField(required=not self.blank, label=capfirst(self.verbose_name), initial=initial) 726 702 727 class IPAddressField(Field): 703 728 def __init__(self, *args, **kwargs): … … 800 825 return {self.attname: (val is not None and val.strftime("%H:%M:%S") or '')} 801 826 827 def formfield(self, initial=None): 828 return forms.TimeField(required=not self.blank, label=capfirst(self.verbose_name), initial=initial) 829 802 830 class URLField(Field): 803 831 def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs): 804 832 if verify_exists: 805 833 kwargs.setdefault('validator_list', []).append(validators.isExistingURL) 834 self.verify_exists = verify_exists 806 835 Field.__init__(self, verbose_name, name, **kwargs) 807 836 808 837 def get_manipulator_field_objs(self): 809 838 return [oldforms.URLField] 839 840 def formfield(self, initial=None): 841 return forms.URLField(required=not self.blank, verify_exists=self.verify_exists, label=capfirst(self.verbose_name), initial=initial) 810 842 811 843 class USStateField(Field): django/branches/boulder-oracle-sprint/django/db/models/fields/related.py
r4212 r4254 3 3 from django.db.models.fields import AutoField, Field, IntegerField, get_ul_class 4 4 from django.db.models.related import RelatedObject 5 from django.utils.text import capfirst 5 6 from django.utils.translation import gettext_lazy, string_concat, ngettext 6 7 from django.utils.functional import curry 7 8 from django.core import validators 8 9 from django import oldforms 10 from django import newforms as forms 9 11 from django.dispatch import dispatcher 10 12 … … 257 259 if self.related.field.null: 258 260 manager.clear() 259 for obj in value: 260 manager.add(obj) 261 manager.add(*value) 261 262 262 263 def create_many_related_manager(superclass): … … 319 320 from django.db import connection 320 321 321 # Add the newly created or already existing objects to the join table. 322 # First find out which items are already added, to avoid adding them twice 323 new_ids = set([obj._get_pk_val() for obj in objs]) 324 cursor = connection.cursor() 325 cursor.execute("SELECT %s FROM %s WHERE %s = %%s AND %s IN (%s)" % \ 326 (target_col_name, self.join_table, source_col_name, 327 target_col_name, ",".join(['%s'] * len(new_ids))), 328 [self._pk_val] + list(new_ids)) 329 if cursor.rowcount is not None and cursor.rowcount != 0: 330 existing_ids = set([row[0] for row in cursor.fetchmany(cursor.rowcount)]) 331 else: 332 existing_ids = set() 333 334 # Add the ones that aren't there already 335 for obj_id in (new_ids - existing_ids): 336 cursor.execute("INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \ 337 (self.join_table, source_col_name, target_col_name), 338 [self._pk_val, obj_id]) 339 transaction.commit_unless_managed() 322 # If there aren't any objects, there is nothing to do. 323 if objs: 324 # Check that all the objects are of the right type 325 for obj in objs: 326 if not isinstance(obj, self.model): 327 raise ValueError, "objects to add() must be %s instances" % self.model._meta.object_name 328 # Add the newly created or already existing objects to the join table. 329 # First find out which items are already added, to avoid adding them twice 330 new_ids = set([obj._get_pk_val() for obj in objs]) 331 cursor = connection.cursor() 332 cursor.execute("SELECT %s FROM %s WHERE %s = %%s AND %s IN (%s)" % \ 333 (target_col_name, self.join_table, source_col_name, 334 target_col_name, ",".join(['%s'] * len(new_ids))), 335 [self._pk_val] + list(new_ids)) 336 if cursor.rowcount is not None and cursor.rowcount != 0: 337 existing_ids = set([row[0] for row in cursor.fetchmany(cursor.rowcount)]) 338 else: 339 existing_ids = set() 340 341 # Add the ones that aren't there already 342 for obj_id in (new_ids - existing_ids): 343 cursor.execute("INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \ 344 (self.join_table, source_col_name, target_col_name), 345 [self._pk_val, obj_id]) 346 transaction.commit_unless_managed() 340 347 341 348 def _remove_items(self, source_col_name, target_col_name, *objs): … … 345 352 from django.db import connection 346 353 347 for obj in objs: 348 if not isinstance(obj, self.model): 349 raise ValueError, "objects to remove() must be %s instances" % self.model._meta.object_name 350 # Remove the specified objects from the join table 351 cursor = connection.cursor() 352 for obj in objs: 353 cursor.execute("DELETE FROM %s WHERE %s = %%s AND %s = %%s" % \ 354 (self.join_table, source_col_name, target_col_name), 355 [self._pk_val, obj._get_pk_val()]) 356 transaction.commit_unless_managed() 354 # If there aren't any objects, there is nothing to do. 355 if objs: 356 # Check that all the objects are of the right type 357 for obj in objs: 358 if not isinstance(obj, self.model): 359 raise ValueError, "objects to remove() must be %s instances" % self.model._meta.object_name 360 # Remove the specified objects from the join table 361 old_ids = set([obj._get_pk_val() for obj in objs]) 362 cursor = connection.cursor() 363 cursor.execute("DELETE FROM %s WHERE %s = %%s AND %s IN (%s)" % \ 364 (self.join_table, source_col_name, 365 target_col_name, ",".join(['%s'] * len(old_ids))), 366 [self._pk_val] + list(old_ids)) 367 transaction.commit_unless_managed() 357 368 358 369 def _clear_items(self, source_col_name): … … 406 417 manager = self.__get__(instance) 407 418 manager.clear() 408 for obj in value: 409 manager.add(obj) 419 manager.add(*value) 410 420 411 421 class ReverseManyRelatedObjectsDescriptor(object): … … 448 458 manager = self.__get__(instance) 449 459 manager.clear() 450 for obj in value: 451 manager.add(obj) 460 manager.add(*value) 452 461 453 462 class ForeignKey(RelatedField, Field): … … 540 549 setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related)) 541 550 551 def formfield(self, initial=None): 552 return forms.ChoiceField(choices=self.get_choices_default(), required=not self.blank, label=capfirst(self.verbose_name), initial=initial) 553 542 554 class OneToOneField(RelatedField, IntegerField): 543 555 def __init__(self, to, to_field=None, **kwargs): … … 600 612 if not cls._meta.one_to_one_field: 601 613 cls._meta.one_to_one_field = self 614 615 def formfield(self, initial=None): 616 return forms.ChoiceField(choices=self.get_choices_default(), required=not self.blank, label=capfirst(self.verbose_name), initial=initial) 602 617 603 618 class ManyToManyField(RelatedField, Field): … … 709 724 def set_attributes_from_rel(self): 710 725 pass 726 727 def value_from_object(self, obj): 728 "Returns the value of this field in the given model instance." 729 return getattr(obj, self.attname).all() 730 731 def formfield(self, initial=None): 732 return forms.MultipleChoiceField(choices=self.get_choices_default(), required=not self.blank, label=capfirst(self.verbose_name), initial=initial) 711 733 712 734 class ManyToOneRel(object): django/branches/boulder-oracle-sprint/django/forms/__init__.py
r4066 r4254 1 from django.core import validators 2 from django.core.exceptions import PermissionDenied 3 from django.utils.html import escape 4 from django.conf import settings 5 from django.utils.translation import gettext, ngettext 6 7 FORM_FIELD_ID_PREFIX = 'id_' 8 9 class EmptyValue(Exception): 10 "This is raised when empty data is provided" 11 pass 12 13 class Manipulator(object): 14 # List of permission strings. User must have at least one to manipulate. 15 # None means everybody has permission. 16 required_permission = '' 17 18 def __init__(self): 19 # List of FormField objects 20 self.fields = [] 21 22 def __getitem__(self, field_name): 23 "Looks up field by field name; raises KeyError on failure" 24 for field in self.fields: 25 if field.field_name == field_name: 26 return field 27 raise KeyError, "Field %s not found\n%s" % (field_name, repr(self.fields)) 28 29 def __delitem__(self, field_name): 30 "Deletes the field with the given field name; raises KeyError on failure" 31 for i, field in enumerate(self.fields): 32 if field.field_name == field_name: 33 del self.fields[i] 34 return 35 raise KeyError, "Field %s not found" % field_name 36 37 def check_permissions(self, user): 38 """Confirms user has required permissions to use this manipulator; raises 39 PermissionDenied on failure.""" 40 if self.required_permission is None: 41 return 42 if user.has_perm(self.required_permission): 43 return 44 raise PermissionDenied 45 46 def prepare(self, new_data): 47 """ 48 Makes any necessary preparations to new_data, in place, before data has 49 been validated. 50 """ 51 for field in self.fields: 52 field.prepare(new_data) 53 54 def get_validation_errors(self, new_data): 55 "Returns dictionary mapping field_names to error-message lists" 56 errors = {} 57 self.prepare(new_data) 58 for field in self.fields: 59 errors.update(field.get_validation_errors(new_data)) 60 val_name = 'validate_%s' % field.field_name 61 if hasattr(self, val_name): 62 val = getattr(self, val_name) 63 try: 64 field.run_validator(new_data, val) 65 except (validators.ValidationError, validators.CriticalValidationError), e: 66 errors.setdefault(field.field_name, []).extend(e.messages) 67 68 # if field.is_required and not new_data.get(field.field_name, False): 69 # errors.setdefault(field.field_name, []).append(gettext_lazy('This field is required.')) 70 # continue 71 # try: 72 # validator_list = field.validator_list 73 # if hasattr(self, 'validate_%s' % field.field_name): 74 # validator_list.append(getattr(self, 'validate_%s' % field.field_name)) 75 # for validator in validator_list: 76 # if field.is_required or new_data.get(field.field_name, False) or hasattr(validator, 'always_test'): 77 # try: 78 # if hasattr(field, 'requires_data_list'): 79 # validator(new_data.getlist(field.field_name), new_data) 80 # else: 81 # validator(new_data.get(field.field_name, ''), new_data) 82 # except validators.ValidationError, e: 83 # errors.setdefault(field.field_name, []).extend(e.messages) 84 # # If a CriticalValidationError is raised, ignore any other ValidationErrors 85 # # for this particular field 86 # except validators.CriticalValidationError, e: 87 # errors.setdefault(field.field_name, []).extend(e.messages) 88 return errors 89 90 def save(self, new_data): 91 "Saves the changes and returns the new object" 92 # changes is a dictionary-like object keyed by field_name 93 raise NotImplementedError 94 95 def do_html2python(self, new_data): 96 """ 97 Convert the data from HTML data types to Python datatypes, changing the 98 object in place. This happens after validation but before storage. This 99 must happen after validation because html2python functions aren't 100 expected to deal with invalid input. 101 """ 102 for field in self.fields: 103 field.convert_post_data(new_data) 104 105 class FormWrapper(object): 106 """ 107 A wrapper linking a Manipulator to the template system. 108 This allows dictionary-style lookups of formfields. It also handles feeding 109 prepopulated data and validation error messages to the formfield objects. 110 """ 111 def __init__(self, manipulator, data=None, error_dict=None, edit_inline=True): 112 self.manipulator = manipulator 113 if data is None: 114 data = {} 115 if error_dict is None: 116 error_dict = {} 117 self.data = data 118 self.error_dict = error_dict 119 self._inline_collections = None 120 self.edit_inline = edit_inline 121 122 def __repr__(self): 123 return repr(self.__dict__) 124 125 def __getitem__(self, key): 126 for field in self.manipulator.fields: 127 if field.field_name == key: 128 data = field.extract_data(self.data) 129 return FormFieldWrapper(field, data, self.error_dict.get(field.field_name, [])) 130 if self.edit_inline: 131 self.fill_inline_collections() 132 for inline_collection in self._inline_collections: 133 if inline_collection.name == key: 134 return inline_collection 135 raise KeyError, "Could not find Formfield or InlineObjectCollection named %r" % key 136 137 def fill_inline_collections(self): 138 if not self._inline_collections: 139 ic = [] 140 related_objects = self.manipulator.get_related_objects() 141 for rel_obj in related_objects: 142 data = rel_obj.extract_data(self.data) 143 inline_collection = InlineObjectCollection(self.manipulator, rel_obj, data, self.error_dict) 144 ic.append(inline_collection) 145 self._inline_collections = ic 146 147 def has_errors(self): 148 return self.error_dict != {} 149 150 def _get_fields(self): 151 try: 152 return self._fields 153 except AttributeError: 154 self._fields = [self.__getitem__(field.field_name) for field in self.manipulator.fields] 155 return self._fields 156 157 fields = property(_get_fields) 158 159 class FormFieldWrapper(object): 160 "A bridge between the template system and an individual form field. Used by FormWrapper." 161 def __init__(self, formfield, data, error_list): 162 self.formfield, self.data, self.error_list = formfield, data, error_list 163 self.field_name = self.formfield.field_name # for convenience in templates 164 165 def __str__(self): 166 "Renders the field" 167 return str(self.formfield.render(self.data)) 168 169 def __repr__(self): 170 return '<FormFieldWrapper for "%s">' % self.formfield.field_name 171 172 def field_list(self): 173 """ 174 Like __str__(), but returns a list. Use this when the field's render() 175 method returns a list. 176 """ 177 return self.formfield.render(self.data) 178 179 def errors(self): 180 return self.error_list 181 182 def html_error_list(self): 183 if self.errors(): 184 return '<ul class="errorlist"><li>%s</li></ul>' % '</li><li>'.join([escape(e) for e in self.errors()]) 185 else: 186 return '' 187 188 def get_id(self): 189 return self.formfield.get_id() 190 191 class FormFieldCollection(FormFieldWrapper): 192 "A utility class that gives the template access to a dict of FormFieldWrappers" 193 def __init__(self, formfield_dict): 194 self.formfield_dict = formfield_dict 195 196 def __str__(self): 197 return str(self.formfield_dict) 198 199 def __getitem__(self, template_key): 200 "Look up field by template key; raise KeyError on failure" 201 return self.formfield_dict[template_key] 202 203 def __repr__(self): 204 return "<FormFieldCollection: %s>" % self.formfield_dict 205 206 def errors(self): 207 "Returns list of all errors in this collection's formfields" 208 errors = [] 209 for field in self.formfield_dict.values(): 210 if hasattr(field, 'errors'): 211 errors.extend(field.errors()) 212 return errors 213 214 def has_errors(self): 215 return bool(len(self.errors())) 216 217 def html_combined_error_list(self): 218 return ''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')]) 219 220 class InlineObjectCollection(object): 221 "An object that acts like a sparse list of form field collections." 222 def __init__(self, parent_manipulator, rel_obj, data, errors): 223 self.parent_manipulator = parent_manipulator 224 self.rel_obj = rel_obj 225 self.data = data 226 self.errors = errors 227 self._collections = None 228 self.name = rel_obj.name 229 230 def __len__(self): 231 self.fill() 232 return self._collections.__len__() 233 234 def __getitem__(self, k): 235 self.fill() 236 return self._collections.__getitem__(k) 237 238 def __setitem__(self, k, v): 239 self.fill() 240 return self._collections.__setitem__(k,v) 241 242 def __delitem__(self, k): 243 self.fill() 244 return self._collections.__delitem__(k) 245 246 def __iter__(self): 247 self.fill() 248 return iter(self._collections.values()) 249 250 def items(self): 251 self.fill() 252 return self._collections.items() 253 254 def fill(self): 255 if self._collections: 256 return 257 else: 258 var_name = self.rel_obj.opts.object_name.lower() 259 collections = {} 260 orig = None 261 if hasattr(self.parent_manipulator, 'original_object'): 262 orig = self.parent_manipulator.original_object 263 orig_list = self.rel_obj.get_list(orig) 264 265 for i, instance in enumerate(orig_list): 266 collection = {'original': instance} 267 for f in self.rel_obj.editable_fields(): 268 for field_name in f.get_manipulator_field_names(''): 269 full_field_name = '%s.%d.%s' % (var_name, i, field_name) 270 field = self.parent_manipulator[full_field_name] 271 data = field.extract_data(self.data) 272 errors = self.errors.get(full_field_name, []) 273 collection[field_name] = FormFieldWrapper(field, data, errors) 274 collections[i] = FormFieldCollection(collection) 275 self._collections = collections 276 277 278 class FormField(object): 279 """Abstract class representing a form field. 280 281 Classes that extend FormField should define the following attributes: 282 field_name 283 The field's name for use by programs. 284 validator_list 285 A list of validation tests (callback functions) that the data for 286 this field must pass in order to be added or changed. 287 is_required 288 A Boolean. Is it a required field? 289 Subclasses should also implement a render(data) method, which is responsible 290 for rending the form field in XHTML. 291 """ 292 def __str__(self): 293 return self.render('') 294 295 def __repr__(self): 296 return 'FormField "%s"' % self.field_name 297 298 def prepare(self, new_data): 299 "Hook for doing something to new_data (in place) before validation." 300 pass 301 302 def html2python(data): 303 "Hook for converting an HTML datatype (e.g. 'on' for checkboxes) to a Python type" 304 return data 305 html2python = staticmethod(html2python) 306 307 def render(self, data): 308 raise NotImplementedError 309 310 def get_member_name(self): 311 if hasattr(self, 'member_name'): 312 return self.member_name 313 else: 314 return self.field_name 315 316 def extract_data(self, data_dict): 317 if hasattr(self, 'requires_data_list') and hasattr(data_dict, 'getlist'): 318 data = data_dict.getlist(self.get_member_name()) 319 else: 320 data = data_dict.get(self.get_member_name(), None) 321 if data is None: 322 data = '' 323 return data 324 325 def convert_post_data(self, new_data): 326 name = self.get_member_name() 327 if new_data.has_key(self.field_name): 328 d = new_data.getlist(self.field_name) 329 try: 330 converted_data = [self.__class__.html2python(data) for data in d] 331 except ValueError: 332 converted_data = d 333 new_data.setlist(name, converted_data) 334 else: 335 try: 336 #individual fields deal with None values themselves 337 new_data.setlist(name, [self.__class__.html2python(None)]) 338 except EmptyValue: 339 new_data.setlist(name, []) 340 341 342 def run_validator(self, new_data, validator): 343 if self.is_required or new_data.get(self.field_name, False) or hasattr(validator, 'always_test'): 344 if hasattr(self, 'requires_data_list'): 345 validator(new_data.getlist(self.field_name), new_data) 346 else: 347 validator(new_data.get(self.field_name, ''), new_data) 348 349 def get_validation_errors(self, new_data): 350 errors = {} 351 if self.is_required and not new_data.get(self.field_name, False): 352 errors.setdefault(self.field_name, []).append(gettext('This field is required.')) 353 return errors 354 try: 355 for validator in self.validator_list: 356 try: 357 self.run_validator(new_data, validator) 358 except validators.ValidationError, e: 359 errors.setdefault(self.field_name, []).extend(e.messages) 360 # If a CriticalValidationError is raised, ignore any other ValidationErrors 361 # for this particular field 362 except validators.CriticalValidationError, e: 363 errors.setdefault(self.field_name, []).extend(e.messages) 364 return errors 365 366 def get_id(self): 367 "Returns the HTML 'id' attribute for this form field." 368 return FORM_FIELD_ID_PREFIX + self.field_name 369 370 #################### 371 # GENERIC WIDGETS # 372 #################### 373 374 class TextField(FormField): 375 input_type = "text" 376 def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=None, member_name=None): 377 if validator_list is None: validator_list = [] 378 self.field_name = field_name 379 self.length, self.maxlength = length, maxlength 380 self.is_required = is_required 381 self.validator_list = [self.isValidLength, self.hasNoNewlines] + validator_list 382 if member_name != None: 383 self.member_name = member_name 384 385 def isValidLength(self, data, form): 386 if data and self.maxlength and len(data.decode(settings.DEFAULT_CHARSET)) > self.maxlength: 387 raise validators.ValidationError, ngettext("Ensure your text is less than %s character.", 388 "Ensure your text is less than %s characters.", self.maxlength) % self.maxlength 389 390 def hasNoNewlines(self, data, form): 391 if data and '\n' in data: 392 raise validators.ValidationError, gettext("Line breaks are not allowed here.") 393 394 def render(self, data): 395 if data is None: 396 data = '' 397 maxlength = '' 398 if self.maxlength: 399 maxlength = 'maxlength="%s" ' % self.maxlength 400 if isinstance(data, unicode): 401 data = data.encode(settings.DEFAULT_CHARSET) 402 return '<input type="%s" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \ 403 (self.input_type, self.get_id(), self.__class__.__name__, self.is_required and ' required' or '', 404 self.field_name, self.length, escape(data), maxlength) 405 406 def html2python(data): 407 return data 408 html2python = staticmethod(html2python) 409 410 class PasswordField(TextField): 411 input_type = "password" 412 413 class LargeTextField(TextField): 414 def __init__(self, field_name, rows=10, cols=40, is_required=False, validator_list=None, maxlength=None): 415 if validator_list is None: validator_list = [] 416 self.field_name = field_name 417 self.rows, self.cols, self.is_required = rows, cols, is_required 418 self.validator_list = validator_list[:] 419 if maxlength: 420 self.validator_list.append(self.isValidLength) 421 self.maxlength = maxlength 422 423 def render(self, data): 424 if data is None: 425 data = '' 426 if isinstance(data, unicode): 427 data = data.encode(settings.DEFAULT_CHARSET) 428 return '<textarea id=
