mirror of
https://github.com/django/django.git
synced 2025-07-04 09:49:12 +00:00
boulder-oracle-sprint: Merged to trunk [4253]
git-svn-id: http://code.djangoproject.com/svn/django/branches/boulder-oracle-sprint@4254 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
f4aa493322
commit
58ba520f32
3
AUTHORS
3
AUTHORS
@ -118,10 +118,12 @@ answer newbie questions, and generally made Django that much better:
|
|||||||
Manuzhai
|
Manuzhai
|
||||||
Petar Marić
|
Petar Marić
|
||||||
mark@junklight.com
|
mark@junklight.com
|
||||||
|
Yasushi Masuda <whosaysni@gmail.com>
|
||||||
mattycakes@gmail.com
|
mattycakes@gmail.com
|
||||||
Jason McBrayer <http://www.carcosa.net/jason/>
|
Jason McBrayer <http://www.carcosa.net/jason/>
|
||||||
mccutchen@gmail.com
|
mccutchen@gmail.com
|
||||||
michael.mcewan@gmail.com
|
michael.mcewan@gmail.com
|
||||||
|
mitakummaa@gmail.com
|
||||||
mmarshall
|
mmarshall
|
||||||
Eric Moritz <http://eric.themoritzfamily.com/>
|
Eric Moritz <http://eric.themoritzfamily.com/>
|
||||||
Robin Munn <http://www.geekforgod.com/>
|
Robin Munn <http://www.geekforgod.com/>
|
||||||
@ -160,6 +162,7 @@ answer newbie questions, and generally made Django that much better:
|
|||||||
Tom Insam
|
Tom Insam
|
||||||
Joe Topjian <http://joe.terrarum.net/geek/code/python/django/>
|
Joe Topjian <http://joe.terrarum.net/geek/code/python/django/>
|
||||||
Karen Tracey <graybark@bellsouth.net>
|
Karen Tracey <graybark@bellsouth.net>
|
||||||
|
Makoto Tsuyuki <mtsuyuki@gmail.com>
|
||||||
Amit Upadhyay
|
Amit Upadhyay
|
||||||
Geert Vanderkelen
|
Geert Vanderkelen
|
||||||
Milton Waddams
|
Milton Waddams
|
||||||
|
@ -38,7 +38,10 @@
|
|||||||
<div id="content" class="{% block coltype %}colM{% endblock %}">
|
<div id="content" class="{% block coltype %}colM{% endblock %}">
|
||||||
{% block pretitle %}{% endblock %}
|
{% block pretitle %}{% endblock %}
|
||||||
{% block content_title %}{% if title %}<h1>{{ title|escape }}</h1>{% endif %}{% endblock %}
|
{% block content_title %}{% if title %}<h1>{{ title|escape }}</h1>{% endif %}{% endblock %}
|
||||||
{% block content %}{{ content }}{% endblock %}
|
{% block content %}
|
||||||
|
{% block object-tools %}{% endblock %}
|
||||||
|
{{ content }}
|
||||||
|
{% endblock %}
|
||||||
{% block sidebar %}{% endblock %}
|
{% block sidebar %}{% endblock %}
|
||||||
<br class="clear" />
|
<br class="clear" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -16,11 +16,13 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}{% endblock %}
|
{% endif %}{% endblock %}
|
||||||
{% block content %}<div id="content-main">
|
{% block content %}<div id="content-main">
|
||||||
|
{% block object-tools %}
|
||||||
{% if change %}{% if not is_popup %}
|
{% if change %}{% if not is_popup %}
|
||||||
<ul class="object-tools"><li><a href="history/" class="historylink">{% trans "History" %}</a></li>
|
<ul class="object-tools"><li><a href="history/" class="historylink">{% trans "History" %}</a></li>
|
||||||
{% if has_absolute_url %}<li><a href="../../../r/{{ content_type_id }}/{{ object_id }}/" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif%}
|
{% if has_absolute_url %}<li><a href="../../../r/{{ content_type_id }}/{{ object_id }}/" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif%}
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}{% endif %}
|
{% endif %}{% endif %}
|
||||||
|
{% endblock %}
|
||||||
<form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% block form_top %}{% endblock %}
|
<form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% block form_top %}{% endblock %}
|
||||||
<div>
|
<div>
|
||||||
{% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %}
|
{% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %}
|
||||||
|
@ -7,9 +7,11 @@
|
|||||||
{% block coltype %}flex{% endblock %}
|
{% block coltype %}flex{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div id="content-main">
|
<div id="content-main">
|
||||||
|
{% block object-tools %}
|
||||||
{% if has_add_permission %}
|
{% if has_add_permission %}
|
||||||
<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>
|
<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>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
<div class="module{% if cl.has_filters %} filtered{% endif %}" id="changelist">
|
<div class="module{% if cl.has_filters %} filtered{% endif %}" id="changelist">
|
||||||
{% block search %}{% search_form cl %}{% endblock %}
|
{% block search %}{% search_form cl %}{% endblock %}
|
||||||
{% block date_hierarchy %}{% date_hierarchy cl %}{% endblock %}
|
{% block date_hierarchy %}{% date_hierarchy cl %}{% endblock %}
|
||||||
|
@ -11,7 +11,7 @@ import md5
|
|||||||
import re
|
import re
|
||||||
import itertools
|
import itertools
|
||||||
|
|
||||||
_ERROR_MSG = "<h1>403 Forbidden</h1><p>Cross Site Request Forgery detected. Request aborted.</p>"
|
_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>'
|
||||||
|
|
||||||
_POST_FORM_RE = \
|
_POST_FORM_RE = \
|
||||||
re.compile(r'(<form\W[^>]*\bmethod=(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE)
|
re.compile(r'(<form\W[^>]*\bmethod=(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE)
|
||||||
|
@ -60,7 +60,10 @@ class BaseHandler(object):
|
|||||||
if response:
|
if response:
|
||||||
return 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:
|
try:
|
||||||
callback, callback_args, callback_kwargs = resolver.resolve(request.path)
|
callback, callback_args, callback_kwargs = resolver.resolve(request.path)
|
||||||
|
|
||||||
|
@ -81,6 +81,8 @@ def destroy_test_db(settings, connection, backend, old_database_name, verbosity=
|
|||||||
settings.DATABASE_NAME = old_database_name
|
settings.DATABASE_NAME = old_database_name
|
||||||
#settings.DATABASE_USER = 'old_user'
|
#settings.DATABASE_USER = 'old_user'
|
||||||
#settings.DATABASE_PASSWORD = 'old_password'
|
#settings.DATABASE_PASSWORD = 'old_password'
|
||||||
|
settings.DATABASE_USER = 'mboersma'
|
||||||
|
settings.DATABASE_PASSWORD = 'password'
|
||||||
|
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
time.sleep(1) # To avoid "database is being accessed by other users" errors.
|
time.sleep(1) # To avoid "database is being accessed by other users" errors.
|
||||||
|
@ -20,6 +20,38 @@ except ImportError:
|
|||||||
# Import copy of _thread_local.py from Python 2.4
|
# Import copy of _thread_local.py from Python 2.4
|
||||||
from django.utils._threading_local import local
|
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):
|
class DatabaseWrapper(local):
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
self.connection = None
|
self.connection = None
|
||||||
@ -45,6 +77,7 @@ class DatabaseWrapper(local):
|
|||||||
self.connection.set_isolation_level(1) # make transactions transparent to all cursors
|
self.connection.set_isolation_level(1) # make transactions transparent to all cursors
|
||||||
cursor = self.connection.cursor()
|
cursor = self.connection.cursor()
|
||||||
cursor.execute("SET TIME ZONE %s", [settings.TIME_ZONE])
|
cursor.execute("SET TIME ZONE %s", [settings.TIME_ZONE])
|
||||||
|
cursor = UnicodeCursorWrapper(cursor, settings.DEFAULT_CHARSET)
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
return util.CursorDebugWrapper(cursor, self)
|
return util.CursorDebugWrapper(cursor, self)
|
||||||
return cursor
|
return cursor
|
||||||
@ -131,7 +164,7 @@ def get_autoinc_sql(table):
|
|||||||
try:
|
try:
|
||||||
Database.register_type(Database.new_type((1082,), "DATE", util.typecast_date))
|
Database.register_type(Database.new_type((1082,), "DATE", util.typecast_date))
|
||||||
except AttributeError:
|
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((1083,1266), "TIME", util.typecast_time))
|
||||||
Database.register_type(Database.new_type((1114,1184), "TIMESTAMP", util.typecast_timestamp))
|
Database.register_type(Database.new_type((1114,1184), "TIMESTAMP", util.typecast_timestamp))
|
||||||
Database.register_type(Database.new_type((16,), "BOOLEAN", util.typecast_boolean))
|
Database.register_type(Database.new_type((16,), "BOOLEAN", util.typecast_boolean))
|
||||||
|
@ -337,11 +337,15 @@ class Field(object):
|
|||||||
return self._choices
|
return self._choices
|
||||||
choices = property(_get_choices)
|
choices = property(_get_choices)
|
||||||
|
|
||||||
def formfield(self):
|
def formfield(self, initial=None):
|
||||||
"Returns a django.newforms.Field instance for this database Field."
|
"Returns a django.newforms.Field instance for this database Field."
|
||||||
from django.newforms import CharField
|
from django.newforms import CharField
|
||||||
# TODO: This is just a temporary default during development.
|
# 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):
|
class AutoField(Field):
|
||||||
empty_strings_allowed = False
|
empty_strings_allowed = False
|
||||||
@ -379,6 +383,9 @@ class AutoField(Field):
|
|||||||
super(AutoField, self).contribute_to_class(cls, name)
|
super(AutoField, self).contribute_to_class(cls, name)
|
||||||
cls._meta.has_auto_field = True
|
cls._meta.has_auto_field = True
|
||||||
|
|
||||||
|
def formfield(self, initial=None):
|
||||||
|
return None
|
||||||
|
|
||||||
class BooleanField(Field):
|
class BooleanField(Field):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
kwargs['blank'] = True
|
kwargs['blank'] = True
|
||||||
@ -393,6 +400,9 @@ class BooleanField(Field):
|
|||||||
def get_manipulator_field_objs(self):
|
def get_manipulator_field_objs(self):
|
||||||
return [oldforms.CheckboxField]
|
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):
|
class CharField(Field):
|
||||||
def get_manipulator_field_objs(self):
|
def get_manipulator_field_objs(self):
|
||||||
return [oldforms.TextField]
|
return [oldforms.TextField]
|
||||||
@ -407,6 +417,9 @@ class CharField(Field):
|
|||||||
raise validators.ValidationError, gettext_lazy("This field cannot be null.")
|
raise validators.ValidationError, gettext_lazy("This field cannot be null.")
|
||||||
return str(value)
|
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.
|
# TODO: Maybe move this into contrib, because it's specialized.
|
||||||
class CommaSeparatedIntegerField(CharField):
|
class CommaSeparatedIntegerField(CharField):
|
||||||
def get_manipulator_field_objs(self):
|
def get_manipulator_field_objs(self):
|
||||||
@ -480,10 +493,13 @@ class DateField(Field):
|
|||||||
def get_manipulator_field_objs(self):
|
def get_manipulator_field_objs(self):
|
||||||
return [oldforms.DateField]
|
return [oldforms.DateField]
|
||||||
|
|
||||||
def flatten_data(self, follow, obj = None):
|
def flatten_data(self, follow, obj=None):
|
||||||
val = self._get_val_from_obj(obj)
|
val = self._get_val_from_obj(obj)
|
||||||
return {self.attname: (val is not None and val.strftime("%Y-%m-%d") or '')}
|
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):
|
class DateTimeField(DateField):
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
if isinstance(value, datetime.datetime):
|
if isinstance(value, datetime.datetime):
|
||||||
@ -553,6 +569,9 @@ class DateTimeField(DateField):
|
|||||||
return {date_field: (val is not None and val.strftime("%Y-%m-%d") or ''),
|
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 '')}
|
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):
|
class EmailField(CharField):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
kwargs['maxlength'] = 75
|
kwargs['maxlength'] = 75
|
||||||
@ -567,6 +586,9 @@ class EmailField(CharField):
|
|||||||
def validate(self, field_data, all_data):
|
def validate(self, field_data, all_data):
|
||||||
validators.isValidEmail(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):
|
class FileField(Field):
|
||||||
def __init__(self, verbose_name=None, name=None, upload_to='', **kwargs):
|
def __init__(self, verbose_name=None, name=None, upload_to='', **kwargs):
|
||||||
self.upload_to = upload_to
|
self.upload_to = upload_to
|
||||||
@ -699,6 +721,9 @@ class IntegerField(Field):
|
|||||||
def get_manipulator_field_objs(self):
|
def get_manipulator_field_objs(self):
|
||||||
return [oldforms.IntegerField]
|
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):
|
class IPAddressField(Field):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
kwargs['maxlength'] = 15
|
kwargs['maxlength'] = 15
|
||||||
@ -799,15 +824,22 @@ class TimeField(Field):
|
|||||||
val = self._get_val_from_obj(obj)
|
val = self._get_val_from_obj(obj)
|
||||||
return {self.attname: (val is not None and val.strftime("%H:%M:%S") or '')}
|
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):
|
class URLField(Field):
|
||||||
def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs):
|
def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs):
|
||||||
if verify_exists:
|
if verify_exists:
|
||||||
kwargs.setdefault('validator_list', []).append(validators.isExistingURL)
|
kwargs.setdefault('validator_list', []).append(validators.isExistingURL)
|
||||||
|
self.verify_exists = verify_exists
|
||||||
Field.__init__(self, verbose_name, name, **kwargs)
|
Field.__init__(self, verbose_name, name, **kwargs)
|
||||||
|
|
||||||
def get_manipulator_field_objs(self):
|
def get_manipulator_field_objs(self):
|
||||||
return [oldforms.URLField]
|
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):
|
class USStateField(Field):
|
||||||
def get_manipulator_field_objs(self):
|
def get_manipulator_field_objs(self):
|
||||||
return [oldforms.USStateField]
|
return [oldforms.USStateField]
|
||||||
|
@ -2,10 +2,12 @@ from django.db import backend, transaction
|
|||||||
from django.db.models import signals, get_model
|
from django.db.models import signals, get_model
|
||||||
from django.db.models.fields import AutoField, Field, IntegerField, get_ul_class
|
from django.db.models.fields import AutoField, Field, IntegerField, get_ul_class
|
||||||
from django.db.models.related import RelatedObject
|
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.translation import gettext_lazy, string_concat, ngettext
|
||||||
from django.utils.functional import curry
|
from django.utils.functional import curry
|
||||||
from django.core import validators
|
from django.core import validators
|
||||||
from django import oldforms
|
from django import oldforms
|
||||||
|
from django import newforms as forms
|
||||||
from django.dispatch import dispatcher
|
from django.dispatch import dispatcher
|
||||||
|
|
||||||
# For Python 2.3
|
# For Python 2.3
|
||||||
@ -256,8 +258,7 @@ class ForeignRelatedObjectsDescriptor(object):
|
|||||||
# Otherwise, just move the named objects into the set.
|
# Otherwise, just move the named objects into the set.
|
||||||
if self.related.field.null:
|
if self.related.field.null:
|
||||||
manager.clear()
|
manager.clear()
|
||||||
for obj in value:
|
manager.add(*value)
|
||||||
manager.add(obj)
|
|
||||||
|
|
||||||
def create_many_related_manager(superclass):
|
def create_many_related_manager(superclass):
|
||||||
"""Creates a manager that subclasses 'superclass' (which is a Manager)
|
"""Creates a manager that subclasses 'superclass' (which is a Manager)
|
||||||
@ -318,25 +319,31 @@ def create_many_related_manager(superclass):
|
|||||||
# *objs - objects to add
|
# *objs - objects to add
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
|
|
||||||
# Add the newly created or already existing objects to the join table.
|
# If there aren't any objects, there is nothing to do.
|
||||||
# First find out which items are already added, to avoid adding them twice
|
if objs:
|
||||||
new_ids = set([obj._get_pk_val() for obj in objs])
|
# Check that all the objects are of the right type
|
||||||
cursor = connection.cursor()
|
for obj in objs:
|
||||||
cursor.execute("SELECT %s FROM %s WHERE %s = %%s AND %s IN (%s)" % \
|
if not isinstance(obj, self.model):
|
||||||
(target_col_name, self.join_table, source_col_name,
|
raise ValueError, "objects to add() must be %s instances" % self.model._meta.object_name
|
||||||
target_col_name, ",".join(['%s'] * len(new_ids))),
|
# Add the newly created or already existing objects to the join table.
|
||||||
[self._pk_val] + list(new_ids))
|
# First find out which items are already added, to avoid adding them twice
|
||||||
if cursor.rowcount is not None and cursor.rowcount != 0:
|
new_ids = set([obj._get_pk_val() for obj in objs])
|
||||||
existing_ids = set([row[0] for row in cursor.fetchmany(cursor.rowcount)])
|
cursor = connection.cursor()
|
||||||
else:
|
cursor.execute("SELECT %s FROM %s WHERE %s = %%s AND %s IN (%s)" % \
|
||||||
existing_ids = set()
|
(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
|
# Add the ones that aren't there already
|
||||||
for obj_id in (new_ids - existing_ids):
|
for obj_id in (new_ids - existing_ids):
|
||||||
cursor.execute("INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \
|
cursor.execute("INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \
|
||||||
(self.join_table, source_col_name, target_col_name),
|
(self.join_table, source_col_name, target_col_name),
|
||||||
[self._pk_val, obj_id])
|
[self._pk_val, obj_id])
|
||||||
transaction.commit_unless_managed()
|
transaction.commit_unless_managed()
|
||||||
|
|
||||||
def _remove_items(self, source_col_name, target_col_name, *objs):
|
def _remove_items(self, source_col_name, target_col_name, *objs):
|
||||||
# source_col_name: the PK colname in join_table for the source object
|
# source_col_name: the PK colname in join_table for the source object
|
||||||
@ -344,16 +351,20 @@ def create_many_related_manager(superclass):
|
|||||||
# *objs - objects to remove
|
# *objs - objects to remove
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
|
|
||||||
for obj in objs:
|
# If there aren't any objects, there is nothing to do.
|
||||||
if not isinstance(obj, self.model):
|
if objs:
|
||||||
raise ValueError, "objects to remove() must be %s instances" % self.model._meta.object_name
|
# Check that all the objects are of the right type
|
||||||
# Remove the specified objects from the join table
|
for obj in objs:
|
||||||
cursor = connection.cursor()
|
if not isinstance(obj, self.model):
|
||||||
for obj in objs:
|
raise ValueError, "objects to remove() must be %s instances" % self.model._meta.object_name
|
||||||
cursor.execute("DELETE FROM %s WHERE %s = %%s AND %s = %%s" % \
|
# Remove the specified objects from the join table
|
||||||
(self.join_table, source_col_name, target_col_name),
|
old_ids = set([obj._get_pk_val() for obj in objs])
|
||||||
[self._pk_val, obj._get_pk_val()])
|
cursor = connection.cursor()
|
||||||
transaction.commit_unless_managed()
|
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):
|
def _clear_items(self, source_col_name):
|
||||||
# source_col_name: the PK colname in join_table for the source object
|
# source_col_name: the PK colname in join_table for the source object
|
||||||
@ -405,8 +416,7 @@ class ManyRelatedObjectsDescriptor(object):
|
|||||||
|
|
||||||
manager = self.__get__(instance)
|
manager = self.__get__(instance)
|
||||||
manager.clear()
|
manager.clear()
|
||||||
for obj in value:
|
manager.add(*value)
|
||||||
manager.add(obj)
|
|
||||||
|
|
||||||
class ReverseManyRelatedObjectsDescriptor(object):
|
class ReverseManyRelatedObjectsDescriptor(object):
|
||||||
# This class provides the functionality that makes the related-object
|
# This class provides the functionality that makes the related-object
|
||||||
@ -447,8 +457,7 @@ class ReverseManyRelatedObjectsDescriptor(object):
|
|||||||
|
|
||||||
manager = self.__get__(instance)
|
manager = self.__get__(instance)
|
||||||
manager.clear()
|
manager.clear()
|
||||||
for obj in value:
|
manager.add(*value)
|
||||||
manager.add(obj)
|
|
||||||
|
|
||||||
class ForeignKey(RelatedField, Field):
|
class ForeignKey(RelatedField, Field):
|
||||||
empty_strings_allowed = False
|
empty_strings_allowed = False
|
||||||
@ -539,6 +548,9 @@ class ForeignKey(RelatedField, Field):
|
|||||||
def contribute_to_related_class(self, cls, related):
|
def contribute_to_related_class(self, cls, related):
|
||||||
setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(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):
|
class OneToOneField(RelatedField, IntegerField):
|
||||||
def __init__(self, to, to_field=None, **kwargs):
|
def __init__(self, to, to_field=None, **kwargs):
|
||||||
try:
|
try:
|
||||||
@ -600,6 +612,9 @@ class OneToOneField(RelatedField, IntegerField):
|
|||||||
if not cls._meta.one_to_one_field:
|
if not cls._meta.one_to_one_field:
|
||||||
cls._meta.one_to_one_field = self
|
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):
|
class ManyToManyField(RelatedField, Field):
|
||||||
def __init__(self, to, **kwargs):
|
def __init__(self, to, **kwargs):
|
||||||
kwargs['verbose_name'] = kwargs.get('verbose_name', None)
|
kwargs['verbose_name'] = kwargs.get('verbose_name', None)
|
||||||
@ -709,6 +724,13 @@ class ManyToManyField(RelatedField, Field):
|
|||||||
def set_attributes_from_rel(self):
|
def set_attributes_from_rel(self):
|
||||||
pass
|
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):
|
class ManyToOneRel(object):
|
||||||
def __init__(self, to, field_name, num_in_admin=3, min_num_in_admin=None,
|
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,
|
max_num_in_admin=None, num_extra_on_change=1, edit_inline=False,
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -13,5 +13,5 @@ TODO:
|
|||||||
from util import ValidationError
|
from util import ValidationError
|
||||||
from widgets import *
|
from widgets import *
|
||||||
from fields import *
|
from fields import *
|
||||||
from forms import Form
|
from forms import *
|
||||||
from models import *
|
from models import *
|
||||||
|
1
django/newforms/extras/__init__.py
Normal file
1
django/newforms/extras/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from widgets import *
|
59
django/newforms/extras/widgets.py
Normal file
59
django/newforms/extras/widgets.py
Normal file
@ -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 <select> boxes.
|
||||||
|
|
||||||
|
This also serves as an example of a Widget that has more than one HTML
|
||||||
|
element and hence implements value_from_datadict.
|
||||||
|
"""
|
||||||
|
month_field = '%s_month'
|
||||||
|
day_field = '%s_day'
|
||||||
|
year_field = '%s_year'
|
||||||
|
|
||||||
|
def __init__(self, attrs=None, years=None):
|
||||||
|
# years is an optional list/tuple of years to use in the "year" select box.
|
||||||
|
self.attrs = attrs or {}
|
||||||
|
if years:
|
||||||
|
self.years = years
|
||||||
|
else:
|
||||||
|
this_year = datetime.date.today().year
|
||||||
|
self.years = range(this_year, this_year+10)
|
||||||
|
|
||||||
|
def render(self, name, value, attrs=None):
|
||||||
|
try:
|
||||||
|
value = datetime.date(*map(int, value.split('-')))
|
||||||
|
year_val, month_val, day_val = value.year, value.month, value.day
|
||||||
|
except (AttributeError, TypeError, ValueError):
|
||||||
|
year_val = month_val = day_val = None
|
||||||
|
|
||||||
|
output = []
|
||||||
|
|
||||||
|
month_choices = MONTHS.items()
|
||||||
|
month_choices.sort()
|
||||||
|
select_html = Select(choices=month_choices).render(self.month_field % name, month_val)
|
||||||
|
output.append(select_html)
|
||||||
|
|
||||||
|
day_choices = [(i, i) for i in range(1, 32)]
|
||||||
|
select_html = Select(choices=day_choices).render(self.day_field % name, day_val)
|
||||||
|
output.append(select_html)
|
||||||
|
|
||||||
|
year_choices = [(i, i) for i in self.years]
|
||||||
|
select_html = Select(choices=year_choices).render(self.year_field % name, year_val)
|
||||||
|
output.append(select_html)
|
||||||
|
|
||||||
|
return u'\n'.join(output)
|
||||||
|
|
||||||
|
def value_from_datadict(self, data, name):
|
||||||
|
y, m, d = data.get(self.year_field % name), data.get(self.month_field % name), data.get(self.day_field % name)
|
||||||
|
if y and m and d:
|
||||||
|
return '%s-%s-%s' % (y, m, d)
|
||||||
|
return None
|
@ -33,8 +33,21 @@ class Field(object):
|
|||||||
# Tracks each time a Field instance is created. Used to retain order.
|
# Tracks each time a Field instance is created. Used to retain order.
|
||||||
creation_counter = 0
|
creation_counter = 0
|
||||||
|
|
||||||
def __init__(self, required=True, widget=None, label=None):
|
def __init__(self, required=True, widget=None, label=None, initial=None):
|
||||||
self.required, self.label = required, label
|
# required -- Boolean that specifies whether the field is required.
|
||||||
|
# True by default.
|
||||||
|
# widget -- A Widget class, or instance of a Widget class, that should be
|
||||||
|
# used for this Field when displaying it. Each Field has a default
|
||||||
|
# Widget that it'll use if you don't specify this. In most cases,
|
||||||
|
# the default widget is TextInput.
|
||||||
|
# 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.
|
||||||
|
if label is not None:
|
||||||
|
label = smart_unicode(label)
|
||||||
|
self.required, self.label, self.initial = required, label, initial
|
||||||
widget = widget or self.widget
|
widget = widget or self.widget
|
||||||
if isinstance(widget, type):
|
if isinstance(widget, type):
|
||||||
widget = widget()
|
widget = widget()
|
||||||
@ -70,14 +83,17 @@ class Field(object):
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
class CharField(Field):
|
class CharField(Field):
|
||||||
def __init__(self, max_length=None, min_length=None, required=True, widget=None, label=None):
|
def __init__(self, max_length=None, min_length=None, required=True, widget=None, label=None, initial=None):
|
||||||
self.max_length, self.min_length = max_length, min_length
|
self.max_length, self.min_length = max_length, min_length
|
||||||
Field.__init__(self, required, widget, label)
|
Field.__init__(self, required, widget, label, initial)
|
||||||
|
|
||||||
def clean(self, value):
|
def clean(self, value):
|
||||||
"Validates max_length and min_length. Returns a Unicode object."
|
"Validates max_length and min_length. Returns a Unicode object."
|
||||||
Field.clean(self, value)
|
Field.clean(self, value)
|
||||||
if value in EMPTY_VALUES: value = u''
|
if value in EMPTY_VALUES:
|
||||||
|
value = u''
|
||||||
|
if not self.required:
|
||||||
|
return value
|
||||||
value = smart_unicode(value)
|
value = smart_unicode(value)
|
||||||
if self.max_length is not None and len(value) > self.max_length:
|
if self.max_length is not None and len(value) > self.max_length:
|
||||||
raise ValidationError(gettext(u'Ensure this value has at most %d characters.') % self.max_length)
|
raise ValidationError(gettext(u'Ensure this value has at most %d characters.') % self.max_length)
|
||||||
@ -90,6 +106,10 @@ class CharField(Field):
|
|||||||
return {'maxlength': str(self.max_length)}
|
return {'maxlength': str(self.max_length)}
|
||||||
|
|
||||||
class IntegerField(Field):
|
class IntegerField(Field):
|
||||||
|
def __init__(self, max_value=None, min_value=None, required=True, widget=None, label=None, initial=None):
|
||||||
|
self.max_value, self.min_value = max_value, min_value
|
||||||
|
Field.__init__(self, required, widget, label, initial)
|
||||||
|
|
||||||
def clean(self, value):
|
def clean(self, value):
|
||||||
"""
|
"""
|
||||||
Validates that int() can be called on the input. Returns the result
|
Validates that int() can be called on the input. Returns the result
|
||||||
@ -99,9 +119,14 @@ class IntegerField(Field):
|
|||||||
if not self.required and value in EMPTY_VALUES:
|
if not self.required and value in EMPTY_VALUES:
|
||||||
return u''
|
return u''
|
||||||
try:
|
try:
|
||||||
return int(value)
|
value = int(value)
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
raise ValidationError(gettext(u'Enter a whole number.'))
|
raise ValidationError(gettext(u'Enter a whole number.'))
|
||||||
|
if self.max_value is not None and value > self.max_value:
|
||||||
|
raise ValidationError(gettext(u'Ensure this value is less than or equal to %s.') % self.max_value)
|
||||||
|
if self.min_value is not None and value < self.min_value:
|
||||||
|
raise ValidationError(gettext(u'Ensure this value is greater than or equal to %s.') % self.min_value)
|
||||||
|
return value
|
||||||
|
|
||||||
DEFAULT_DATE_INPUT_FORMATS = (
|
DEFAULT_DATE_INPUT_FORMATS = (
|
||||||
'%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06'
|
'%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06'
|
||||||
@ -112,8 +137,8 @@ DEFAULT_DATE_INPUT_FORMATS = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
class DateField(Field):
|
class DateField(Field):
|
||||||
def __init__(self, input_formats=None, required=True, widget=None, label=None):
|
def __init__(self, input_formats=None, required=True, widget=None, label=None, initial=None):
|
||||||
Field.__init__(self, required, widget, label)
|
Field.__init__(self, required, widget, label, initial)
|
||||||
self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS
|
self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS
|
||||||
|
|
||||||
def clean(self, value):
|
def clean(self, value):
|
||||||
@ -141,8 +166,8 @@ DEFAULT_TIME_INPUT_FORMATS = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
class TimeField(Field):
|
class TimeField(Field):
|
||||||
def __init__(self, input_formats=None, required=True, widget=None, label=None):
|
def __init__(self, input_formats=None, required=True, widget=None, label=None, initial=None):
|
||||||
Field.__init__(self, required, widget, label)
|
Field.__init__(self, required, widget, label, initial)
|
||||||
self.input_formats = input_formats or DEFAULT_TIME_INPUT_FORMATS
|
self.input_formats = input_formats or DEFAULT_TIME_INPUT_FORMATS
|
||||||
|
|
||||||
def clean(self, value):
|
def clean(self, value):
|
||||||
@ -175,8 +200,8 @@ DEFAULT_DATETIME_INPUT_FORMATS = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
class DateTimeField(Field):
|
class DateTimeField(Field):
|
||||||
def __init__(self, input_formats=None, required=True, widget=None, label=None):
|
def __init__(self, input_formats=None, required=True, widget=None, label=None, initial=None):
|
||||||
Field.__init__(self, required, widget, label)
|
Field.__init__(self, required, widget, label, initial)
|
||||||
self.input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS
|
self.input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS
|
||||||
|
|
||||||
def clean(self, value):
|
def clean(self, value):
|
||||||
@ -199,16 +224,18 @@ class DateTimeField(Field):
|
|||||||
raise ValidationError(gettext(u'Enter a valid date/time.'))
|
raise ValidationError(gettext(u'Enter a valid date/time.'))
|
||||||
|
|
||||||
class RegexField(Field):
|
class RegexField(Field):
|
||||||
def __init__(self, regex, error_message=None, required=True, widget=None, label=None):
|
def __init__(self, regex, max_length=None, min_length=None, error_message=None,
|
||||||
|
required=True, widget=None, label=None, initial=None):
|
||||||
"""
|
"""
|
||||||
regex can be either a string or a compiled regular expression object.
|
regex can be either a string or a compiled regular expression object.
|
||||||
error_message is an optional error message to use, if
|
error_message is an optional error message to use, if
|
||||||
'Enter a valid value' is too generic for you.
|
'Enter a valid value' is too generic for you.
|
||||||
"""
|
"""
|
||||||
Field.__init__(self, required, widget, label)
|
Field.__init__(self, required, widget, label, initial)
|
||||||
if isinstance(regex, basestring):
|
if isinstance(regex, basestring):
|
||||||
regex = re.compile(regex)
|
regex = re.compile(regex)
|
||||||
self.regex = regex
|
self.regex = regex
|
||||||
|
self.max_length, self.min_length = max_length, min_length
|
||||||
self.error_message = error_message or gettext(u'Enter a valid value.')
|
self.error_message = error_message or gettext(u'Enter a valid value.')
|
||||||
|
|
||||||
def clean(self, value):
|
def clean(self, value):
|
||||||
@ -221,6 +248,10 @@ class RegexField(Field):
|
|||||||
value = smart_unicode(value)
|
value = smart_unicode(value)
|
||||||
if not self.required and value == u'':
|
if not self.required and value == u'':
|
||||||
return value
|
return value
|
||||||
|
if self.max_length is not None and len(value) > self.max_length:
|
||||||
|
raise ValidationError(gettext(u'Ensure this value has at most %d characters.') % self.max_length)
|
||||||
|
if self.min_length is not None and len(value) < self.min_length:
|
||||||
|
raise ValidationError(gettext(u'Ensure this value has at least %d characters.') % self.min_length)
|
||||||
if not self.regex.search(value):
|
if not self.regex.search(value):
|
||||||
raise ValidationError(self.error_message)
|
raise ValidationError(self.error_message)
|
||||||
return value
|
return value
|
||||||
@ -231,8 +262,8 @@ email_re = re.compile(
|
|||||||
r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE) # domain
|
r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE) # domain
|
||||||
|
|
||||||
class EmailField(RegexField):
|
class EmailField(RegexField):
|
||||||
def __init__(self, required=True, widget=None, label=None):
|
def __init__(self, max_length=None, min_length=None, required=True, widget=None, label=None, initial=None):
|
||||||
RegexField.__init__(self, email_re, gettext(u'Enter a valid e-mail address.'), required, widget, label)
|
RegexField.__init__(self, email_re, max_length, min_length, gettext(u'Enter a valid e-mail address.'), required, widget, label, initial)
|
||||||
|
|
||||||
url_re = re.compile(
|
url_re = re.compile(
|
||||||
r'^https?://' # http:// or https://
|
r'^https?://' # http:// or https://
|
||||||
@ -248,9 +279,9 @@ except ImportError:
|
|||||||
URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)'
|
URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)'
|
||||||
|
|
||||||
class URLField(RegexField):
|
class URLField(RegexField):
|
||||||
def __init__(self, required=True, verify_exists=False, widget=None, label=None,
|
def __init__(self, max_length=None, min_length=None, required=True, verify_exists=False, widget=None, label=None,
|
||||||
validator_user_agent=URL_VALIDATOR_USER_AGENT):
|
initial=None, validator_user_agent=URL_VALIDATOR_USER_AGENT):
|
||||||
RegexField.__init__(self, url_re, gettext(u'Enter a valid URL.'), required, widget, label)
|
RegexField.__init__(self, url_re, max_length, min_length, gettext(u'Enter a valid URL.'), required, widget, label, initial)
|
||||||
self.verify_exists = verify_exists
|
self.verify_exists = verify_exists
|
||||||
self.user_agent = validator_user_agent
|
self.user_agent = validator_user_agent
|
||||||
|
|
||||||
@ -284,10 +315,10 @@ class BooleanField(Field):
|
|||||||
return bool(value)
|
return bool(value)
|
||||||
|
|
||||||
class ChoiceField(Field):
|
class ChoiceField(Field):
|
||||||
def __init__(self, choices=(), required=True, widget=Select, label=None):
|
def __init__(self, choices=(), required=True, widget=Select, label=None, initial=None):
|
||||||
if isinstance(widget, type):
|
if isinstance(widget, type):
|
||||||
widget = widget(choices=choices)
|
widget = widget(choices=choices)
|
||||||
Field.__init__(self, required, widget, label)
|
Field.__init__(self, required, widget, label, initial)
|
||||||
self.choices = choices
|
self.choices = choices
|
||||||
|
|
||||||
def clean(self, value):
|
def clean(self, value):
|
||||||
@ -305,8 +336,8 @@ class ChoiceField(Field):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
class MultipleChoiceField(ChoiceField):
|
class MultipleChoiceField(ChoiceField):
|
||||||
def __init__(self, choices=(), required=True, widget=SelectMultiple, label=None):
|
def __init__(self, choices=(), required=True, widget=SelectMultiple, label=None, initial=None):
|
||||||
ChoiceField.__init__(self, choices, required, widget, label)
|
ChoiceField.__init__(self, choices, required, widget, label, initial)
|
||||||
|
|
||||||
def clean(self, value):
|
def clean(self, value):
|
||||||
"""
|
"""
|
||||||
@ -330,8 +361,8 @@ class MultipleChoiceField(ChoiceField):
|
|||||||
return new_value
|
return new_value
|
||||||
|
|
||||||
class ComboField(Field):
|
class ComboField(Field):
|
||||||
def __init__(self, fields=(), required=True, widget=None, label=None):
|
def __init__(self, fields=(), required=True, widget=None, label=None, initial=None):
|
||||||
Field.__init__(self, required, widget, label)
|
Field.__init__(self, required, widget, label, initial)
|
||||||
# Set 'required' to False on the individual fields, because the
|
# Set 'required' to False on the individual fields, because the
|
||||||
# required validation will be handled by ComboField, not by those
|
# required validation will be handled by ComboField, not by those
|
||||||
# individual fields.
|
# individual fields.
|
||||||
|
@ -8,6 +8,8 @@ from fields import Field
|
|||||||
from widgets import TextInput, Textarea, HiddenInput
|
from widgets import TextInput, Textarea, HiddenInput
|
||||||
from util import StrAndUnicode, ErrorDict, ErrorList, ValidationError
|
from util import StrAndUnicode, ErrorDict, ErrorList, ValidationError
|
||||||
|
|
||||||
|
__all__ = ('BaseForm', 'Form')
|
||||||
|
|
||||||
NON_FIELD_ERRORS = '__all__'
|
NON_FIELD_ERRORS = '__all__'
|
||||||
|
|
||||||
def pretty_name(name):
|
def pretty_name(name):
|
||||||
@ -97,7 +99,8 @@ class BaseForm(StrAndUnicode):
|
|||||||
else:
|
else:
|
||||||
if errors_on_separate_row and bf_errors:
|
if errors_on_separate_row and bf_errors:
|
||||||
output.append(error_row % bf_errors)
|
output.append(error_row % bf_errors)
|
||||||
output.append(normal_row % {'errors': bf_errors, 'label': bf.label_tag(escape(bf.label+':')), 'field': bf})
|
label = bf.label and bf.label_tag(escape(bf.label + ':')) or ''
|
||||||
|
output.append(normal_row % {'errors': bf_errors, 'label': label, 'field': bf})
|
||||||
if top_errors:
|
if top_errors:
|
||||||
output.insert(0, error_row % top_errors)
|
output.insert(0, error_row % top_errors)
|
||||||
if hidden_fields: # Insert any hidden fields in the last row.
|
if hidden_fields: # Insert any hidden fields in the last row.
|
||||||
@ -112,7 +115,7 @@ class BaseForm(StrAndUnicode):
|
|||||||
|
|
||||||
def as_table(self):
|
def as_table(self):
|
||||||
"Returns this form rendered as HTML <tr>s -- excluding the <table></table>."
|
"Returns this form rendered as HTML <tr>s -- excluding the <table></table>."
|
||||||
return self._html_output(u'<tr><th>%(label)s</th><td>%(field)s</td></tr>', u'<tr><td colspan="2">%s</td></tr>', '</td></tr>', True)
|
return self._html_output(u'<tr><th>%(label)s</th><td>%(errors)s%(field)s</td></tr>', u'<tr><td colspan="2">%s</td></tr>', '</td></tr>', False)
|
||||||
|
|
||||||
def as_ul(self):
|
def as_ul(self):
|
||||||
"Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
|
"Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
|
||||||
@ -185,7 +188,10 @@ class BoundField(StrAndUnicode):
|
|||||||
self.field = field
|
self.field = field
|
||||||
self.name = name
|
self.name = name
|
||||||
self.html_name = form.add_prefix(name)
|
self.html_name = form.add_prefix(name)
|
||||||
self.label = self.field.label or pretty_name(name)
|
if self.field.label is None:
|
||||||
|
self.label = pretty_name(name)
|
||||||
|
else:
|
||||||
|
self.label = self.field.label
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
"Renders this field as an HTML widget."
|
"Renders this field as an HTML widget."
|
||||||
@ -212,7 +218,11 @@ class BoundField(StrAndUnicode):
|
|||||||
auto_id = self.auto_id
|
auto_id = self.auto_id
|
||||||
if auto_id and not attrs.has_key('id') and not widget.attrs.has_key('id'):
|
if auto_id and not attrs.has_key('id') and not widget.attrs.has_key('id'):
|
||||||
attrs['id'] = auto_id
|
attrs['id'] = auto_id
|
||||||
return widget.render(self.html_name, self.data, attrs=attrs)
|
if self.form.ignore_errors:
|
||||||
|
data = self.field.initial
|
||||||
|
else:
|
||||||
|
data = self.data
|
||||||
|
return widget.render(self.html_name, data, attrs=attrs)
|
||||||
|
|
||||||
def as_text(self, attrs=None):
|
def as_text(self, attrs=None):
|
||||||
"""
|
"""
|
||||||
@ -231,10 +241,10 @@ class BoundField(StrAndUnicode):
|
|||||||
return self.as_widget(HiddenInput(), attrs)
|
return self.as_widget(HiddenInput(), attrs)
|
||||||
|
|
||||||
def _data(self):
|
def _data(self):
|
||||||
"Returns the data for this BoundField, or None if it wasn't given."
|
"""
|
||||||
if self.field.widget.requires_data_list and isinstance(self.form.data, MultiValueDict):
|
Returns the data for this BoundField, or None if it wasn't given.
|
||||||
return self.form.data.getlist(self.html_name)
|
"""
|
||||||
return self.form.data.get(self.html_name, None)
|
return self.field.widget.value_from_datadict(self.form.data, self.html_name)
|
||||||
data = property(_data)
|
data = property(_data)
|
||||||
|
|
||||||
def label_tag(self, contents=None):
|
def label_tag(self, contents=None):
|
||||||
|
@ -5,13 +5,69 @@ and database field objects.
|
|||||||
|
|
||||||
from forms import BaseForm, DeclarativeFieldsMetaclass, SortedDictFromList
|
from forms import BaseForm, DeclarativeFieldsMetaclass, SortedDictFromList
|
||||||
|
|
||||||
__all__ = ('form_for_model', 'form_for_fields')
|
__all__ = ('form_for_model', 'form_for_instance', 'form_for_fields')
|
||||||
|
|
||||||
def form_for_model(model):
|
def create(self, save=True):
|
||||||
"Returns a Form class for the given Django model class."
|
"""
|
||||||
|
Creates and returns model instance according to self.clean_data.
|
||||||
|
|
||||||
|
This method is created for any form_for_model Form.
|
||||||
|
"""
|
||||||
|
if self.errors:
|
||||||
|
raise ValueError("The %s could not be created because the data didn't validate." % self._model._meta.object_name)
|
||||||
|
obj = self._model(**self.clean_data)
|
||||||
|
if save:
|
||||||
|
obj.save()
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def make_apply_changes(opts, instance):
|
||||||
|
"Returns the apply_changes() method for a form_for_instance Form."
|
||||||
|
from django.db import models
|
||||||
|
def apply_changes(self, save=True):
|
||||||
|
if self.errors:
|
||||||
|
raise ValueError("The %s could not be changed because the data didn't validate." % opts.object_name)
|
||||||
|
clean_data = self.clean_data
|
||||||
|
for f in opts.fields + opts.many_to_many:
|
||||||
|
if isinstance(f, models.AutoField):
|
||||||
|
continue
|
||||||
|
setattr(instance, f.attname, clean_data[f.name])
|
||||||
|
if save:
|
||||||
|
instance.save()
|
||||||
|
return instance
|
||||||
|
return apply_changes
|
||||||
|
|
||||||
|
def form_for_model(model, form=BaseForm):
|
||||||
|
"""
|
||||||
|
Returns a Form class for the given Django model class.
|
||||||
|
|
||||||
|
Provide 'form' if you want to use a custom BaseForm subclass.
|
||||||
|
"""
|
||||||
opts = model._meta
|
opts = model._meta
|
||||||
fields = SortedDictFromList([(f.name, f.formfield()) for f in opts.fields + opts.many_to_many])
|
field_list = []
|
||||||
return type(opts.object_name + 'Form', (BaseForm,), {'fields': fields, '_model_opts': opts})
|
for f in opts.fields + opts.many_to_many:
|
||||||
|
formfield = f.formfield()
|
||||||
|
if formfield:
|
||||||
|
field_list.append((f.name, formfield))
|
||||||
|
fields = SortedDictFromList(field_list)
|
||||||
|
return type(opts.object_name + 'Form', (form,), {'fields': fields, '_model': model, 'create': create})
|
||||||
|
|
||||||
|
def form_for_instance(instance, form=BaseForm):
|
||||||
|
"""
|
||||||
|
Returns a Form class for the given Django model instance.
|
||||||
|
|
||||||
|
Provide 'form' if you want to use a custom BaseForm subclass.
|
||||||
|
"""
|
||||||
|
model = instance.__class__
|
||||||
|
opts = model._meta
|
||||||
|
field_list = []
|
||||||
|
for f in opts.fields + opts.many_to_many:
|
||||||
|
current_value = f.value_from_object(instance)
|
||||||
|
formfield = f.formfield(initial=current_value)
|
||||||
|
if formfield:
|
||||||
|
field_list.append((f.name, formfield))
|
||||||
|
fields = SortedDictFromList(field_list)
|
||||||
|
return type(opts.object_name + 'InstanceForm', (form,),
|
||||||
|
{'fields': fields, '_model': model, 'apply_changes': make_apply_changes(opts, instance)})
|
||||||
|
|
||||||
def form_for_fields(field_list):
|
def form_for_fields(field_list):
|
||||||
"Returns a Form class for the given list of Django database field instances."
|
"Returns a Form class for the given list of Django database field instances."
|
||||||
|
@ -23,13 +23,18 @@ except NameError:
|
|||||||
flatatt = lambda attrs: u''.join([u' %s="%s"' % (k, escape(v)) for k, v in attrs.items()])
|
flatatt = lambda attrs: u''.join([u' %s="%s"' % (k, escape(v)) for k, v in attrs.items()])
|
||||||
|
|
||||||
class Widget(object):
|
class Widget(object):
|
||||||
requires_data_list = False # Determines whether render()'s 'value' argument should be a list.
|
|
||||||
is_hidden = False # Determines whether this corresponds to an <input type="hidden">.
|
is_hidden = False # Determines whether this corresponds to an <input type="hidden">.
|
||||||
|
|
||||||
def __init__(self, attrs=None):
|
def __init__(self, attrs=None):
|
||||||
self.attrs = attrs or {}
|
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
|
raise NotImplementedError
|
||||||
|
|
||||||
def build_attrs(self, extra_attrs=None, **kwargs):
|
def build_attrs(self, extra_attrs=None, **kwargs):
|
||||||
@ -130,7 +135,6 @@ class Select(Widget):
|
|||||||
return u'\n'.join(output)
|
return u'\n'.join(output)
|
||||||
|
|
||||||
class SelectMultiple(Widget):
|
class SelectMultiple(Widget):
|
||||||
requires_data_list = True
|
|
||||||
def __init__(self, attrs=None, choices=()):
|
def __init__(self, attrs=None, choices=()):
|
||||||
# choices can be any iterable
|
# choices can be any iterable
|
||||||
self.attrs = attrs or {}
|
self.attrs = attrs or {}
|
||||||
@ -185,6 +189,10 @@ class RadioFieldRenderer(StrAndUnicode):
|
|||||||
for i, choice in enumerate(self.choices):
|
for i, choice in enumerate(self.choices):
|
||||||
yield RadioInput(self.name, self.value, self.attrs.copy(), choice, i)
|
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):
|
def __unicode__(self):
|
||||||
"Outputs a <ul> for this set of radio fields."
|
"Outputs a <ul> for this set of radio fields."
|
||||||
return u'<ul>\n%s\n</ul>' % u'\n'.join([u'<li>%s</li>' % w for w in self])
|
return u'<ul>\n%s\n</ul>' % u'\n'.join([u'<li>%s</li>' % w for w in self])
|
||||||
|
@ -11,7 +11,7 @@ Usage:
|
|||||||
>>>
|
>>>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
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 django.utils.tzinfo import LocalTimezone
|
||||||
from calendar import isleap, monthrange
|
from calendar import isleap, monthrange
|
||||||
import re, time
|
import re, time
|
||||||
@ -147,7 +147,7 @@ class DateFormat(TimeFormat):
|
|||||||
|
|
||||||
def M(self):
|
def M(self):
|
||||||
"Month, textual, 3 letters; e.g. 'Jan'"
|
"Month, textual, 3 letters; e.g. 'Jan'"
|
||||||
return MONTHS[self.data.month][0:3]
|
return MONTHS_3[self.data.month].title()
|
||||||
|
|
||||||
def n(self):
|
def n(self):
|
||||||
"Month without leading zeros; i.e. '1' to '12'"
|
"Month without leading zeros; i.e. '1' to '12'"
|
||||||
|
@ -8,17 +8,28 @@ capfirst = lambda x: x and x[0].upper() + x[1:]
|
|||||||
def wrap(text, width):
|
def wrap(text, width):
|
||||||
"""
|
"""
|
||||||
A word-wrap function that preserves existing line breaks and most spaces in
|
A word-wrap function that preserves existing line breaks and most spaces in
|
||||||
the text. Expects that existing line breaks are posix newlines (\n).
|
the text. Expects that existing line breaks are posix newlines.
|
||||||
See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061
|
|
||||||
"""
|
"""
|
||||||
return reduce(lambda line, word, width=width: '%s%s%s' %
|
def _generator():
|
||||||
(line,
|
it = iter(text.split(' '))
|
||||||
' \n'[(len(line[line.rfind('\n')+1:])
|
word = it.next()
|
||||||
+ len(word.split('\n',1)[0]
|
yield word
|
||||||
) >= width)],
|
pos = len(word) - word.rfind('\n') - 1
|
||||||
word),
|
for word in it:
|
||||||
text.split(' ')
|
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):
|
def truncate_words(s, num):
|
||||||
"Truncates a string after a certain number of words."
|
"Truncates a string after a certain number of words."
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
=====================================
|
=====================================
|
||||||
Cross Site Request Forgery Protection
|
Cross Site Request Forgery protection
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
The CsrfMiddleware class provides easy-to-use protection against
|
The CsrfMiddleware class provides easy-to-use protection against
|
||||||
`Cross Site Request Forgeries`_. This type of attack occurs when a malicious
|
`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
|
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
|
on your web site, using the credentials of a logged-in user who is tricked
|
||||||
into clicking on the link in their browser.
|
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
|
are side-effect free. POST requests can then be protected by adding this
|
||||||
middleware into your list of installed middleware.
|
middleware into your list of installed middleware.
|
||||||
|
|
||||||
|
|
||||||
.. _Cross Site Request Forgeries: http://www.squarefree.com/securitytips/web-developers.html#CSRF
|
.. _Cross Site Request Forgeries: http://www.squarefree.com/securitytips/web-developers.html#CSRF
|
||||||
|
|
||||||
How to use it
|
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
|
your list of middleware classes, ``MIDDLEWARE_CLASSES``. It needs to process
|
||||||
the response after the SessionMiddleware, so must come before it in the
|
the response after the SessionMiddleware, so must come before it in the
|
||||||
list. It also must process the response before things like compression
|
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
|
How it works
|
||||||
============
|
============
|
||||||
|
|
||||||
CsrfMiddleware does two things:
|
CsrfMiddleware does two things:
|
||||||
|
|
||||||
1. It modifies outgoing requests by adding a hidden form field to all
|
1. It modifies outgoing requests by adding a hidden form field to all
|
||||||
'POST' forms, with the name 'csrfmiddlewaretoken' and a value which is
|
'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,
|
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
|
this modification of the response isn't done, so there is very little
|
||||||
performance penalty for those requests that don't have a session.
|
performance penalty for those requests that don't have a session.
|
||||||
|
|
||||||
2. On all incoming POST requests that have the session cookie set, it
|
2. On all incoming POST requests that have the session cookie set, it
|
||||||
checks that the 'csrfmiddlewaretoken' is present and correct. If it
|
checks that the 'csrfmiddlewaretoken' is present and correct. If it
|
||||||
isn't, the user will get a 403 error.
|
isn't, the user will get a 403 error.
|
||||||
|
|
||||||
This ensures that only forms that have originated from your web site
|
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
|
It deliberately only targets HTTP POST requests (and the corresponding
|
||||||
POST forms). GET requests ought never to have side effects (if you are
|
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
|
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,
|
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
|
but they do not need to be protected, since the 'attacking' web site
|
||||||
could make these kind of requests anyway.
|
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'
|
pages that are served as 'text/html' or 'application/xml+xhtml'
|
||||||
are modified.
|
are modified.
|
||||||
|
|
||||||
Limitations
|
Limitations
|
||||||
===========
|
===========
|
||||||
|
|
||||||
CsrfMiddleware requires Django's session framework to work. If you have
|
CsrfMiddleware requires Django's session framework to work. If you have
|
||||||
a custom authentication system that manually sets cookies and the like,
|
a custom authentication system that manually sets cookies and the like,
|
||||||
it won't help you.
|
it won't help you.
|
||||||
|
|
||||||
If your app creates HTML pages and forms in some unusual way, (e.g.
|
If your app creates HTML pages and forms in some unusual way, (e.g.
|
||||||
it sends fragments of HTML in javascript document.write statements)
|
it sends fragments of HTML in javascript document.write statements)
|
||||||
you might bypass the filter that adds the hidden field to the form,
|
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
|
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
|
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.
|
||||||
|
|
@ -143,9 +143,9 @@ or ``UPDATE`` SQL statements. Specifically, when you call ``save()``, Django
|
|||||||
follows this algorithm:
|
follows this algorithm:
|
||||||
|
|
||||||
* If the object's primary key attribute is set to a value that evaluates to
|
* 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
|
``True`` (i.e., a value other than ``None`` or the empty string), Django
|
||||||
``SELECT`` query to determine whether a record with the given primary key
|
executes a ``SELECT`` query to determine whether a record with the given
|
||||||
already exists.
|
primary key already exists.
|
||||||
* If the record with the given primary key does already exist, Django
|
* If the record with the given primary key does already exist, Django
|
||||||
executes an ``UPDATE`` query.
|
executes an ``UPDATE`` query.
|
||||||
* If the object's primary key attribute is *not* set, or if it's set but a
|
* If the object's primary key attribute is *not* set, or if it's set but a
|
||||||
|
@ -22,7 +22,6 @@ what the name of the database is. Do that by editing these settings in your
|
|||||||
* `DATABASE_ENGINE`_
|
* `DATABASE_ENGINE`_
|
||||||
* `DATABASE_USER`_
|
* `DATABASE_USER`_
|
||||||
* `DATABASE_PASSWORD`_
|
* `DATABASE_PASSWORD`_
|
||||||
* `DATABASE_NAME`_
|
|
||||||
* `DATABASE_HOST`_
|
* `DATABASE_HOST`_
|
||||||
* `DATABASE_PORT`_
|
* `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_ENGINE: http://www.djangoproject.com/documentation/settings/#database-engine
|
||||||
.. _DATABASE_USER: http://www.djangoproject.com/documentation/settings/#database-user
|
.. _DATABASE_USER: http://www.djangoproject.com/documentation/settings/#database-user
|
||||||
.. _DATABASE_PASSWORD: http://www.djangoproject.com/documentation/settings/#database-password
|
.. _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_HOST: http://www.djangoproject.com/documentation/settings/#database-host
|
||||||
.. _DATABASE_PORT: http://www.djangoproject.com/documentation/settings/#database-port
|
.. _DATABASE_PORT: http://www.djangoproject.com/documentation/settings/#database-port
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ release, our plan is to do the following:
|
|||||||
from django import oldforms as forms # new
|
from django import oldforms as forms # new
|
||||||
|
|
||||||
* At an undecided future date, we will move the current ``django.newforms``
|
* 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
|
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
|
time will need to change their import statements, as described in the
|
||||||
previous bullet.
|
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
|
``subject``, ``message``, ``sender``, ``cc_myself``. To reorder the HTML
|
||||||
output, just change the order in which those fields are listed in the class.
|
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
|
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
|
-- the unit tests for ``django.newforms``. This can give you a good idea of
|
||||||
what's possible.
|
what's possible.
|
||||||
|
|
||||||
Using forms to validate data
|
|
||||||
----------------------------
|
|
||||||
|
|
||||||
Using forms with templates
|
Using forms with templates
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
|
@ -63,9 +63,9 @@ Via the Python API
|
|||||||
------------------
|
------------------
|
||||||
|
|
||||||
Redirects are represented by a standard `Django model`_, which lives in
|
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`_.
|
objects via the `Django database API`_.
|
||||||
|
|
||||||
.. _Django model: http://www.djangoproject.com/documentation/model_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/
|
.. _Django database API: http://www.djangoproject.com/documentation/db_api/
|
||||||
|
@ -12,6 +12,9 @@ class Article(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('pub_date',)
|
ordering = ('pub_date',)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ('pub_date','headline')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.headline
|
return self.headline
|
||||||
|
|
||||||
@ -247,7 +250,7 @@ datetime.datetime(2005, 7, 28, 0, 0)
|
|||||||
|
|
||||||
# Slices (without step) are lazy:
|
# Slices (without step) are lazy:
|
||||||
>>> Article.objects.all()[0:5].filter()
|
>>> Article.objects.all()[0:5].filter()
|
||||||
[<Article: Area woman programs in Python>, <Article: Second article>, <Article: Third article>, <Article: Fourth article>, <Article: Article 6>]
|
[<Article: Area woman programs in Python>, <Article: Second article>, <Article: Third article>, <Article: Article 6>, <Article: Default headline>]
|
||||||
|
|
||||||
# Slicing again works:
|
# Slicing again works:
|
||||||
>>> Article.objects.all()[0:5][0:2]
|
>>> Article.objects.all()[0:5][0:2]
|
||||||
@ -255,17 +258,17 @@ datetime.datetime(2005, 7, 28, 0, 0)
|
|||||||
>>> Article.objects.all()[0:5][:2]
|
>>> Article.objects.all()[0:5][:2]
|
||||||
[<Article: Area woman programs in Python>, <Article: Second article>]
|
[<Article: Area woman programs in Python>, <Article: Second article>]
|
||||||
>>> Article.objects.all()[0:5][4:]
|
>>> Article.objects.all()[0:5][4:]
|
||||||
[<Article: Article 6>]
|
[<Article: Default headline>]
|
||||||
>>> Article.objects.all()[0:5][5:]
|
>>> Article.objects.all()[0:5][5:]
|
||||||
[]
|
[]
|
||||||
|
|
||||||
# Some more tests!
|
# Some more tests!
|
||||||
>>> Article.objects.all()[2:][0:2]
|
>>> Article.objects.all()[2:][0:2]
|
||||||
[<Article: Third article>, <Article: Fourth article>]
|
[<Article: Third article>, <Article: Article 6>]
|
||||||
>>> Article.objects.all()[2:][:2]
|
>>> Article.objects.all()[2:][:2]
|
||||||
[<Article: Third article>, <Article: Fourth article>]
|
[<Article: Third article>, <Article: Article 6>]
|
||||||
>>> Article.objects.all()[2:][2:3]
|
>>> Article.objects.all()[2:][2:3]
|
||||||
[<Article: Article 6>]
|
[<Article: Default headline>]
|
||||||
|
|
||||||
# Note that you can't use 'offset' without 'limit' (on some dbs), so this doesn't work:
|
# Note that you can't use 'offset' without 'limit' (on some dbs), so this doesn't work:
|
||||||
>>> Article.objects.all()[2:]
|
>>> Article.objects.all()[2:]
|
||||||
@ -314,7 +317,7 @@ AttributeError: Manager isn't accessible via Article instances
|
|||||||
|
|
||||||
# Bulk delete test: How many objects before and after the delete?
|
# Bulk delete test: How many objects before and after the delete?
|
||||||
>>> Article.objects.all()
|
>>> Article.objects.all()
|
||||||
[<Article: Area woman programs in Python>, <Article: Second article>, <Article: Third article>, <Article: Fourth article>, <Article: Article 6>, <Article: Default headline>, <Article: Article 7>, <Article: Updated article 8>]
|
[<Article: Area woman programs in Python>, <Article: Second article>, <Article: Third article>, <Article: Article 6>, <Article: Default headline>, <Article: Fourth article>, <Article: Article 7>, <Article: Updated article 8>]
|
||||||
>>> Article.objects.filter(id__lte=4).delete()
|
>>> Article.objects.filter(id__lte=4).delete()
|
||||||
>>> Article.objects.all()
|
>>> Article.objects.all()
|
||||||
[<Article: Article 6>, <Article: Default headline>, <Article: Article 7>, <Article: Updated article 8>]
|
[<Article: Article 6>, <Article: Default headline>, <Article: Article 7>, <Article: Updated article 8>]
|
||||||
|
@ -231,4 +231,16 @@ __test__ = {'API_TESTS':"""
|
|||||||
>>> p1.article_set.all()
|
>>> p1.article_set.all()
|
||||||
[<Article: NASA uses Python>]
|
[<Article: NASA uses Python>]
|
||||||
|
|
||||||
|
# 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()
|
||||||
|
[<Publication: Highlights for Children>, <Publication: The Python Journal>]
|
||||||
|
>>> a2.publications = []
|
||||||
|
>>> a2.publications.all()
|
||||||
|
[]
|
||||||
|
|
||||||
"""}
|
"""}
|
||||||
|
@ -2,13 +2,26 @@
|
|||||||
34. Generating HTML forms from models
|
34. Generating HTML forms from models
|
||||||
|
|
||||||
Django provides shortcuts for creating Form objects from a model class.
|
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
|
from django.db import models
|
||||||
|
|
||||||
class Category(models.Model):
|
class Category(models.Model):
|
||||||
name = models.CharField(maxlength=20)
|
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):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
@ -16,29 +29,156 @@ class Category(models.Model):
|
|||||||
class Article(models.Model):
|
class Article(models.Model):
|
||||||
headline = models.CharField(maxlength=50)
|
headline = models.CharField(maxlength=50)
|
||||||
pub_date = models.DateTimeField()
|
pub_date = models.DateTimeField()
|
||||||
categories = models.ManyToManyField(Category)
|
writer = models.ForeignKey(Writer)
|
||||||
|
categories = models.ManyToManyField(Category, blank=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.headline
|
return self.headline
|
||||||
|
|
||||||
__test__ = {'API_TESTS': """
|
__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)
|
>>> CategoryForm = form_for_model(Category)
|
||||||
>>> f = CategoryForm()
|
>>> f = CategoryForm()
|
||||||
>>> print f
|
>>> print f
|
||||||
<tr><th><label for="id_id">ID:</label></th><td><input type="text" name="id" id="id_id" /></td></tr>
|
<tr><th><label for="id_name">Name:</label></th><td><input id="id_name" type="text" name="name" maxlength="20" /></td></tr>
|
||||||
<tr><th><label for="id_name">Name:</label></th><td><input type="text" name="name" id="id_name" /></td></tr>
|
<tr><th><label for="id_url">The URL:</label></th><td><input id="id_url" type="text" name="url" maxlength="40" /></td></tr>
|
||||||
<tr><th><label for="id_url">The URL:</label></th><td><input type="text" name="url" id="id_url" /></td></tr>
|
|
||||||
>>> print f.as_ul()
|
>>> print f.as_ul()
|
||||||
<li><label for="id_id">ID:</label> <input type="text" name="id" id="id_id" /></li>
|
<li><label for="id_name">Name:</label> <input id="id_name" type="text" name="name" maxlength="20" /></li>
|
||||||
<li><label for="id_name">Name:</label> <input type="text" name="name" id="id_name" /></li>
|
<li><label for="id_url">The URL:</label> <input id="id_url" type="text" name="url" maxlength="40" /></li>
|
||||||
<li><label for="id_url">The URL:</label> <input type="text" name="url" id="id_url" /></li>
|
|
||||||
>>> print f['name']
|
>>> print f['name']
|
||||||
<input type="text" name="name" id="id_name" />
|
<input id="id_name" type="text" name="name" maxlength="20" />
|
||||||
|
|
||||||
>>> f = CategoryForm(auto_id=False)
|
>>> f = CategoryForm(auto_id=False)
|
||||||
>>> print f.as_ul()
|
>>> print f.as_ul()
|
||||||
<li>ID: <input type="text" name="id" /></li>
|
<li>Name: <input type="text" name="name" maxlength="20" /></li>
|
||||||
<li>Name: <input type="text" name="name" /></li>
|
<li>The URL: <input type="text" name="url" maxlength="40" /></li>
|
||||||
<li>The URL: <input type="text" name="url" /></li>
|
|
||||||
|
>>> f = CategoryForm({'name': 'Entertainment', 'url': 'entertainment'})
|
||||||
|
>>> f.errors
|
||||||
|
{}
|
||||||
|
>>> f.clean_data
|
||||||
|
{'url': u'entertainment', 'name': u'Entertainment'}
|
||||||
|
>>> obj = f.create()
|
||||||
|
>>> obj
|
||||||
|
<Category: Entertainment>
|
||||||
|
>>> Category.objects.all()
|
||||||
|
[<Category: Entertainment>]
|
||||||
|
|
||||||
|
>>> 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: It's a test>
|
||||||
|
>>> Category.objects.all()
|
||||||
|
[<Category: Entertainment>, <Category: It's a test>]
|
||||||
|
|
||||||
|
>>> 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: Third test>
|
||||||
|
>>> Category.objects.all()
|
||||||
|
[<Category: Entertainment>, <Category: It's a test>]
|
||||||
|
>>> obj.save()
|
||||||
|
>>> Category.objects.all()
|
||||||
|
[<Category: Entertainment>, <Category: It's a test>, <Category: Third test>]
|
||||||
|
|
||||||
|
>>> 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
|
||||||
|
<tr><th>Headline:</th><td><input type="text" name="headline" maxlength="50" /></td></tr>
|
||||||
|
<tr><th>Pub date:</th><td><input type="text" name="pub_date" /></td></tr>
|
||||||
|
<tr><th>Writer:</th><td><select name="writer">
|
||||||
|
<option value="" selected="selected">---------</option>
|
||||||
|
<option value="1">Mike Royko</option>
|
||||||
|
<option value="2">Bob Woodward</option>
|
||||||
|
</select></td></tr>
|
||||||
|
<tr><th>Categories:</th><td><select multiple="multiple" name="categories">
|
||||||
|
<option value="1">Entertainment</option>
|
||||||
|
<option value="2">It's a test</option>
|
||||||
|
<option value="3">Third test</option>
|
||||||
|
</select></td></tr>
|
||||||
|
|
||||||
|
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
|
||||||
|
<tr><th>Name:</th><td><input type="text" name="name" value="Mike Royko" maxlength="50" /></td></tr>
|
||||||
|
|
||||||
|
>>> 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()
|
||||||
|
<li>Headline: <input type="text" name="headline" value="Test article" maxlength="50" /></li>
|
||||||
|
<li>Pub date: <input type="text" name="pub_date" value="1988-01-04" /></li>
|
||||||
|
<li>Writer: <select name="writer">
|
||||||
|
<option value="">---------</option>
|
||||||
|
<option value="1" selected="selected">Mike Royko</option>
|
||||||
|
<option value="2">Bob Woodward</option>
|
||||||
|
</select></li>
|
||||||
|
<li>Categories: <select multiple="multiple" name="categories">
|
||||||
|
<option value="1">Entertainment</option>
|
||||||
|
<option value="2">It's a test</option>
|
||||||
|
<option value="3">Third test</option>
|
||||||
|
</select></li>
|
||||||
|
>>> 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'
|
||||||
"""}
|
"""}
|
||||||
|
@ -514,6 +514,25 @@ beatle J P Paul False
|
|||||||
beatle J G George False
|
beatle J G George False
|
||||||
beatle J R Ringo 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]
|
||||||
|
<label><input type="radio" name="beatle" value="P" /> Paul</label>
|
||||||
|
>>> print r[0]
|
||||||
|
<label><input checked="checked" type="radio" name="beatle" value="J" /> John</label>
|
||||||
|
>>> 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 ###############################################
|
# CheckboxSelectMultiple Widget ###############################################
|
||||||
|
|
||||||
>>> w = CheckboxSelectMultiple()
|
>>> w = CheckboxSelectMultiple()
|
||||||
@ -639,6 +658,8 @@ Each Field's __init__() takes at least these parameters:
|
|||||||
label -- A verbose name for this field, for use in displaying this field in
|
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
|
a form. By default, Django will use a "pretty" version of the form
|
||||||
field name, if the Field is part of a 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
|
Other than that, the Field subclasses have class-specific options for
|
||||||
__init__(). For example, CharField has a max_length option.
|
__init__(). For example, CharField has a max_length option.
|
||||||
@ -687,9 +708,21 @@ ValidationError: [u'Ensure this value has at most 10 characters.']
|
|||||||
CharField accepts an optional min_length parameter:
|
CharField accepts an optional min_length parameter:
|
||||||
>>> f = CharField(min_length=10, required=False)
|
>>> f = CharField(min_length=10, required=False)
|
||||||
>>> f.clean('')
|
>>> f.clean('')
|
||||||
|
u''
|
||||||
|
>>> f.clean('12345')
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
ValidationError: [u'Ensure this value has at least 10 characters.']
|
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')
|
>>> f.clean('12345')
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
@ -757,6 +790,71 @@ Traceback (most recent call last):
|
|||||||
...
|
...
|
||||||
ValidationError: [u'Enter a whole number.']
|
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 ###################################################################
|
# DateField ###################################################################
|
||||||
|
|
||||||
>>> import datetime
|
>>> import datetime
|
||||||
@ -1002,7 +1100,7 @@ Traceback (most recent call last):
|
|||||||
ValidationError: [u'Enter a valid value.']
|
ValidationError: [u'Enter a valid value.']
|
||||||
|
|
||||||
RegexField takes an optional error_message argument:
|
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')
|
>>> f.clean('1234')
|
||||||
u'1234'
|
u'1234'
|
||||||
>>> f.clean('123')
|
>>> f.clean('123')
|
||||||
@ -1014,6 +1112,29 @@ Traceback (most recent call last):
|
|||||||
...
|
...
|
||||||
ValidationError: [u'Enter a four-digit number.']
|
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 ##################################################################
|
# EmailField ##################################################################
|
||||||
|
|
||||||
>>> f = EmailField()
|
>>> f = EmailField()
|
||||||
@ -1060,6 +1181,19 @@ Traceback (most recent call last):
|
|||||||
...
|
...
|
||||||
ValidationError: [u'Enter a valid e-mail address.']
|
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 ##################################################################
|
# URLField ##################################################################
|
||||||
|
|
||||||
>>> f = URLField()
|
>>> f = URLField()
|
||||||
@ -1152,6 +1286,19 @@ Traceback (most recent call last):
|
|||||||
...
|
...
|
||||||
ValidationError: [u'This URL appears to be a broken link.']
|
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 ################################################################
|
# BooleanField ################################################################
|
||||||
|
|
||||||
>>> f = BooleanField()
|
>>> f = BooleanField()
|
||||||
@ -1398,19 +1545,13 @@ Empty dictionaries are valid, too.
|
|||||||
>>> p.is_valid()
|
>>> p.is_valid()
|
||||||
False
|
False
|
||||||
>>> print p
|
>>> print p
|
||||||
<tr><td colspan="2"><ul class="errorlist"><li>This field is required.</li></ul></td></tr>
|
<tr><th><label for="id_first_name">First name:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="first_name" id="id_first_name" /></td></tr>
|
||||||
<tr><th><label for="id_first_name">First name:</label></th><td><input type="text" name="first_name" id="id_first_name" /></td></tr>
|
<tr><th><label for="id_last_name">Last name:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="last_name" id="id_last_name" /></td></tr>
|
||||||
<tr><td colspan="2"><ul class="errorlist"><li>This field is required.</li></ul></td></tr>
|
<tr><th><label for="id_birthday">Birthday:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="birthday" id="id_birthday" /></td></tr>
|
||||||
<tr><th><label for="id_last_name">Last name:</label></th><td><input type="text" name="last_name" id="id_last_name" /></td></tr>
|
|
||||||
<tr><td colspan="2"><ul class="errorlist"><li>This field is required.</li></ul></td></tr>
|
|
||||||
<tr><th><label for="id_birthday">Birthday:</label></th><td><input type="text" name="birthday" id="id_birthday" /></td></tr>
|
|
||||||
>>> print p.as_table()
|
>>> print p.as_table()
|
||||||
<tr><td colspan="2"><ul class="errorlist"><li>This field is required.</li></ul></td></tr>
|
<tr><th><label for="id_first_name">First name:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="first_name" id="id_first_name" /></td></tr>
|
||||||
<tr><th><label for="id_first_name">First name:</label></th><td><input type="text" name="first_name" id="id_first_name" /></td></tr>
|
<tr><th><label for="id_last_name">Last name:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="last_name" id="id_last_name" /></td></tr>
|
||||||
<tr><td colspan="2"><ul class="errorlist"><li>This field is required.</li></ul></td></tr>
|
<tr><th><label for="id_birthday">Birthday:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="birthday" id="id_birthday" /></td></tr>
|
||||||
<tr><th><label for="id_last_name">Last name:</label></th><td><input type="text" name="last_name" id="id_last_name" /></td></tr>
|
|
||||||
<tr><td colspan="2"><ul class="errorlist"><li>This field is required.</li></ul></td></tr>
|
|
||||||
<tr><th><label for="id_birthday">Birthday:</label></th><td><input type="text" name="birthday" id="id_birthday" /></td></tr>
|
|
||||||
>>> print p.as_ul()
|
>>> print p.as_ul()
|
||||||
<li><ul class="errorlist"><li>This field is required.</li></ul><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></li>
|
<li><ul class="errorlist"><li>This field is required.</li></ul><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></li>
|
||||||
<li><ul class="errorlist"><li>This field is required.</li></ul><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></li>
|
<li><ul class="errorlist"><li>This field is required.</li></ul><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></li>
|
||||||
@ -1799,12 +1940,9 @@ Form.clean() is required to return a dictionary of all clean data.
|
|||||||
{}
|
{}
|
||||||
>>> f = UserRegistration({}, auto_id=False)
|
>>> f = UserRegistration({}, auto_id=False)
|
||||||
>>> print f.as_table()
|
>>> print f.as_table()
|
||||||
<tr><td colspan="2"><ul class="errorlist"><li>This field is required.</li></ul></td></tr>
|
<tr><th>Username:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="username" maxlength="10" /></td></tr>
|
||||||
<tr><th>Username:</th><td><input type="text" name="username" maxlength="10" /></td></tr>
|
<tr><th>Password1:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="password" name="password1" /></td></tr>
|
||||||
<tr><td colspan="2"><ul class="errorlist"><li>This field is required.</li></ul></td></tr>
|
<tr><th>Password2:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="password" name="password2" /></td></tr>
|
||||||
<tr><th>Password1:</th><td><input type="password" name="password1" /></td></tr>
|
|
||||||
<tr><td colspan="2"><ul class="errorlist"><li>This field is required.</li></ul></td></tr>
|
|
||||||
<tr><th>Password2:</th><td><input type="password" name="password2" /></td></tr>
|
|
||||||
>>> f.errors
|
>>> f.errors
|
||||||
{'username': [u'This field is required.'], 'password1': [u'This field is required.'], 'password2': [u'This field is required.']}
|
{'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)
|
>>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'bar'}, auto_id=False)
|
||||||
@ -1972,6 +2110,8 @@ in "attrs".
|
|||||||
<li>Username: <input type="text" name="username" maxlength="10" /></li>
|
<li>Username: <input type="text" name="username" maxlength="10" /></li>
|
||||||
<li>Password: <input type="password" name="password" maxlength="10" /></li>
|
<li>Password: <input type="password" name="password" maxlength="10" /></li>
|
||||||
|
|
||||||
|
# Specifying labels ###########################################################
|
||||||
|
|
||||||
You can specify the label for a field by using the 'label' argument to a Field
|
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
|
class. If you don't specify 'label', Django will use the field name with
|
||||||
underscores converted to spaces, and the initial letter capitalized.
|
underscores converted to spaces, and the initial letter capitalized.
|
||||||
@ -1985,6 +2125,81 @@ underscores converted to spaces, and the initial letter capitalized.
|
|||||||
<li>Password1: <input type="password" name="password1" /></li>
|
<li>Password1: <input type="password" name="password1" /></li>
|
||||||
<li>Password (again): <input type="password" name="password2" /></li>
|
<li>Password (again): <input type="password" name="password2" /></li>
|
||||||
|
|
||||||
|
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'<li>\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111: <input type="text" name="username" maxlength="10" /></li>\n<li>\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111: <input type="password" name="password" /></li>'
|
||||||
|
|
||||||
|
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()
|
||||||
|
<li> <input type="text" name="username" maxlength="10" /></li>
|
||||||
|
<li>Password: <input type="password" name="password" /></li>
|
||||||
|
>>> p = UserRegistration(auto_id='id_%s')
|
||||||
|
>>> print p.as_ul()
|
||||||
|
<li> <input id="id_username" type="text" name="username" maxlength="10" /></li>
|
||||||
|
<li><label for="id_password">Password:</label> <input type="password" name="password" id="id_password" /></li>
|
||||||
|
|
||||||
|
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()
|
||||||
|
<li>Username: <input type="text" name="username" maxlength="10" /></li>
|
||||||
|
<li>Password: <input type="password" name="password" /></li>
|
||||||
|
>>> p = UserRegistration(auto_id='id_%s')
|
||||||
|
>>> print p.as_ul()
|
||||||
|
<li><label for="id_username">Username:</label> <input id="id_username" type="text" name="username" maxlength="10" /></li>
|
||||||
|
<li><label for="id_password">Password:</label> <input type="password" name="password" id="id_password" /></li>
|
||||||
|
|
||||||
|
# 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()
|
||||||
|
<li>Username: <input type="text" name="username" value="django" maxlength="10" /></li>
|
||||||
|
<li>Password: <input type="password" name="password" /></li>
|
||||||
|
|
||||||
|
Here, we're submitting data, so the initial value will *not* be displayed.
|
||||||
|
>>> p = UserRegistration({}, auto_id=False)
|
||||||
|
>>> print p.as_ul()
|
||||||
|
<li><ul class="errorlist"><li>This field is required.</li></ul>Username: <input type="text" name="username" maxlength="10" /></li>
|
||||||
|
<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li>
|
||||||
|
>>> p = UserRegistration({'username': u''}, auto_id=False)
|
||||||
|
>>> print p.as_ul()
|
||||||
|
<li><ul class="errorlist"><li>This field is required.</li></ul>Username: <input type="text" name="username" maxlength="10" /></li>
|
||||||
|
<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li>
|
||||||
|
>>> p = UserRegistration({'username': u'foo'}, auto_id=False)
|
||||||
|
>>> print p.as_ul()
|
||||||
|
<li>Username: <input type="text" name="username" value="foo" maxlength="10" /></li>
|
||||||
|
<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li>
|
||||||
|
|
||||||
|
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 #########################################################
|
# Forms with prefixes #########################################################
|
||||||
|
|
||||||
Sometimes it's necessary to have multiple forms display on the same HTML page,
|
Sometimes it's necessary to have multiple forms display on the same HTML page,
|
||||||
@ -2133,8 +2348,7 @@ Case 2: POST with erroneous data (a redisplayed form, with errors).
|
|||||||
<form action="" method="post">
|
<form action="" method="post">
|
||||||
<table>
|
<table>
|
||||||
<tr><td colspan="2"><ul class="errorlist"><li>Please make sure your passwords match.</li></ul></td></tr>
|
<tr><td colspan="2"><ul class="errorlist"><li>Please make sure your passwords match.</li></ul></td></tr>
|
||||||
<tr><td colspan="2"><ul class="errorlist"><li>Ensure this value has at most 10 characters.</li></ul></td></tr>
|
<tr><th>Username:</th><td><ul class="errorlist"><li>Ensure this value has at most 10 characters.</li></ul><input type="text" name="username" value="this-is-a-long-username" maxlength="10" /></td></tr>
|
||||||
<tr><th>Username:</th><td><input type="text" name="username" value="this-is-a-long-username" maxlength="10" /></td></tr>
|
|
||||||
<tr><th>Password1:</th><td><input type="password" name="password1" value="foo" /></td></tr>
|
<tr><th>Password1:</th><td><input type="password" name="password1" value="foo" /></td></tr>
|
||||||
<tr><th>Password2:</th><td><input type="password" name="password2" value="bar" /></td></tr>
|
<tr><th>Password2:</th><td><input type="password" name="password2" value="bar" /></td></tr>
|
||||||
</table>
|
</table>
|
||||||
@ -2257,6 +2471,141 @@ the list of errors is empty). You can also use it in {% if %} statements.
|
|||||||
<p><label>Password (again): <input type="password" name="password2" value="bar" /></label></p>
|
<p><label>Password (again): <input type="password" name="password2" value="bar" /></label></p>
|
||||||
<input type="submit" />
|
<input type="submit" />
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
#################
|
||||||
|
# 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', '')
|
||||||
|
<select name="mydate_month">
|
||||||
|
<option value="1">January</option>
|
||||||
|
<option value="2">February</option>
|
||||||
|
<option value="3">March</option>
|
||||||
|
<option value="4">April</option>
|
||||||
|
<option value="5">May</option>
|
||||||
|
<option value="6">June</option>
|
||||||
|
<option value="7">July</option>
|
||||||
|
<option value="8">August</option>
|
||||||
|
<option value="9">September</option>
|
||||||
|
<option value="10">October</option>
|
||||||
|
<option value="11">November</option>
|
||||||
|
<option value="12">December</option>
|
||||||
|
</select>
|
||||||
|
<select name="mydate_day">
|
||||||
|
<option value="1">1</option>
|
||||||
|
<option value="2">2</option>
|
||||||
|
<option value="3">3</option>
|
||||||
|
<option value="4">4</option>
|
||||||
|
<option value="5">5</option>
|
||||||
|
<option value="6">6</option>
|
||||||
|
<option value="7">7</option>
|
||||||
|
<option value="8">8</option>
|
||||||
|
<option value="9">9</option>
|
||||||
|
<option value="10">10</option>
|
||||||
|
<option value="11">11</option>
|
||||||
|
<option value="12">12</option>
|
||||||
|
<option value="13">13</option>
|
||||||
|
<option value="14">14</option>
|
||||||
|
<option value="15">15</option>
|
||||||
|
<option value="16">16</option>
|
||||||
|
<option value="17">17</option>
|
||||||
|
<option value="18">18</option>
|
||||||
|
<option value="19">19</option>
|
||||||
|
<option value="20">20</option>
|
||||||
|
<option value="21">21</option>
|
||||||
|
<option value="22">22</option>
|
||||||
|
<option value="23">23</option>
|
||||||
|
<option value="24">24</option>
|
||||||
|
<option value="25">25</option>
|
||||||
|
<option value="26">26</option>
|
||||||
|
<option value="27">27</option>
|
||||||
|
<option value="28">28</option>
|
||||||
|
<option value="29">29</option>
|
||||||
|
<option value="30">30</option>
|
||||||
|
<option value="31">31</option>
|
||||||
|
</select>
|
||||||
|
<select name="mydate_year">
|
||||||
|
<option value="2006">2006</option>
|
||||||
|
<option value="2007">2007</option>
|
||||||
|
<option value="2008">2008</option>
|
||||||
|
<option value="2009">2009</option>
|
||||||
|
<option value="2010">2010</option>
|
||||||
|
<option value="2011">2011</option>
|
||||||
|
<option value="2012">2012</option>
|
||||||
|
<option value="2013">2013</option>
|
||||||
|
<option value="2014">2014</option>
|
||||||
|
<option value="2015">2015</option>
|
||||||
|
</select>
|
||||||
|
>>> w.render('mydate', None) == w.render('mydate', '')
|
||||||
|
True
|
||||||
|
>>> print w.render('mydate', '2010-04-15')
|
||||||
|
<select name="mydate_month">
|
||||||
|
<option value="1">January</option>
|
||||||
|
<option value="2">February</option>
|
||||||
|
<option value="3">March</option>
|
||||||
|
<option value="4" selected="selected">April</option>
|
||||||
|
<option value="5">May</option>
|
||||||
|
<option value="6">June</option>
|
||||||
|
<option value="7">July</option>
|
||||||
|
<option value="8">August</option>
|
||||||
|
<option value="9">September</option>
|
||||||
|
<option value="10">October</option>
|
||||||
|
<option value="11">November</option>
|
||||||
|
<option value="12">December</option>
|
||||||
|
</select>
|
||||||
|
<select name="mydate_day">
|
||||||
|
<option value="1">1</option>
|
||||||
|
<option value="2">2</option>
|
||||||
|
<option value="3">3</option>
|
||||||
|
<option value="4">4</option>
|
||||||
|
<option value="5">5</option>
|
||||||
|
<option value="6">6</option>
|
||||||
|
<option value="7">7</option>
|
||||||
|
<option value="8">8</option>
|
||||||
|
<option value="9">9</option>
|
||||||
|
<option value="10">10</option>
|
||||||
|
<option value="11">11</option>
|
||||||
|
<option value="12">12</option>
|
||||||
|
<option value="13">13</option>
|
||||||
|
<option value="14">14</option>
|
||||||
|
<option value="15" selected="selected">15</option>
|
||||||
|
<option value="16">16</option>
|
||||||
|
<option value="17">17</option>
|
||||||
|
<option value="18">18</option>
|
||||||
|
<option value="19">19</option>
|
||||||
|
<option value="20">20</option>
|
||||||
|
<option value="21">21</option>
|
||||||
|
<option value="22">22</option>
|
||||||
|
<option value="23">23</option>
|
||||||
|
<option value="24">24</option>
|
||||||
|
<option value="25">25</option>
|
||||||
|
<option value="26">26</option>
|
||||||
|
<option value="27">27</option>
|
||||||
|
<option value="28">28</option>
|
||||||
|
<option value="29">29</option>
|
||||||
|
<option value="30">30</option>
|
||||||
|
<option value="31">31</option>
|
||||||
|
</select>
|
||||||
|
<select name="mydate_year">
|
||||||
|
<option value="2006">2006</option>
|
||||||
|
<option value="2007">2007</option>
|
||||||
|
<option value="2008">2008</option>
|
||||||
|
<option value="2009">2009</option>
|
||||||
|
<option value="2010" selected="selected">2010</option>
|
||||||
|
<option value="2011">2011</option>
|
||||||
|
<option value="2012">2012</option>
|
||||||
|
<option value="2013">2013</option>
|
||||||
|
<option value="2014">2014</option>
|
||||||
|
<option value="2015">2015</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
Loading…
x
Reference in New Issue
Block a user