1
0
mirror of https://github.com/django/django.git synced 2025-07-04 09:49:12 +00:00

newforms-admin: Merged to [4563]

git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@4565 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Adrian Holovaty 2007-02-25 05:50:24 +00:00
parent 67e0248806
commit 8f1fb6da3a
14 changed files with 548 additions and 35 deletions

View File

@ -125,7 +125,7 @@ answer newbie questions, and generally made Django that much better:
Martin Maney <http://www.chipy.org/Martin_Maney> Martin Maney <http://www.chipy.org/Martin_Maney>
masonsimon+django@gmail.com masonsimon+django@gmail.com
Manuzhai Manuzhai
Petar Marić Petar Marić <http://www.petarmaric.com/>
mark@junklight.com mark@junklight.com
Yasushi Masuda <whosaysni@gmail.com> Yasushi Masuda <whosaysni@gmail.com>
mattycakes@gmail.com mattycakes@gmail.com
@ -166,7 +166,7 @@ answer newbie questions, and generally made Django that much better:
smurf@smurf.noris.de smurf@smurf.noris.de
sopel sopel
Georgi Stanojevski <glisha@gmail.com> Georgi Stanojevski <glisha@gmail.com>
Thomas Steinacher <tom@eggdrop.ch> Thomas Steinacher <http://www.eggdrop.ch/>
nowell strite nowell strite
Radek Švarz <http://www.svarz.cz/translate/> Radek Švarz <http://www.svarz.cz/translate/>
Swaroop C H <http://www.swaroopch.info> Swaroop C H <http://www.swaroopch.info>
@ -183,6 +183,7 @@ answer newbie questions, and generally made Django that much better:
Milton Waddams Milton Waddams
wam-djangobug@wamber.net wam-djangobug@wamber.net
Dan Watson <http://theidioteque.net/> Dan Watson <http://theidioteque.net/>
Chris Wesseling <Chris.Wesseling@cwi.nl>
Rachel Willmer <http://www.willmer.com/kb/> Rachel Willmer <http://www.willmer.com/kb/>
Gary Wilson <gary.wilson@gmail.com> Gary Wilson <gary.wilson@gmail.com>
wojtek wojtek

View File

@ -4,7 +4,11 @@ USA-specific Form helpers
from django.newforms import ValidationError from django.newforms import ValidationError
from django.newforms.fields import Field, RegexField, Select, EMPTY_VALUES from django.newforms.fields import Field, RegexField, Select, EMPTY_VALUES
from django.newforms.util import smart_unicode
from django.utils.translation import gettext from django.utils.translation import gettext
import re
phone_digits_re = re.compile(r'^(?:1-?)?(\d{3})[-\.]?(\d{3})[-\.]?(\d{4})$')
class USZipCodeField(RegexField): class USZipCodeField(RegexField):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -13,6 +17,17 @@ class USZipCodeField(RegexField):
error_message=gettext(u'Enter a zip code in the format XXXXX or XXXXX-XXXX.'), error_message=gettext(u'Enter a zip code in the format XXXXX or XXXXX-XXXX.'),
*args, **kwargs) *args, **kwargs)
class USPhoneNumberField(Field):
def clean(self, value):
super(USPhoneNumberField, self).clean(value)
if value in EMPTY_VALUES:
return u''
value = re.sub('(\(|\)|\s+)', '', smart_unicode(value))
m = phone_digits_re.search(value)
if m:
return u'%s-%s-%s' % (m.group(1), m.group(2), m.group(3))
raise ValidationError(u'Phone numbers must be in XXX-XXX-XXXX format.')
class USStateField(Field): class USStateField(Field):
""" """
A form field that validates its input is a U.S. state name or abbreviation. A form field that validates its input is a U.S. state name or abbreviation.

View File

@ -750,6 +750,12 @@ class PhoneNumberField(IntegerField):
def validate(self, field_data, all_data): def validate(self, field_data, all_data):
validators.isValidPhone(field_data, all_data) validators.isValidPhone(field_data, all_data)
def formfield(self, **kwargs):
from django.contrib.localflavor.usa.forms import USPhoneNumberField
defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text}
defaults.update(kwargs)
return USPhoneNumberField(**defaults)
class PositiveIntegerField(IntegerField): class PositiveIntegerField(IntegerField):
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [oldforms.PositiveIntegerField] return [oldforms.PositiveIntegerField]

View File

@ -545,9 +545,9 @@ class ForeignKey(RelatedField, Field):
setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related)) setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related))
def formfield(self, **kwargs): def formfield(self, **kwargs):
defaults = {'choices': self.get_choices_default(), 'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text} defaults = {'queryset': self.rel.to._default_manager.all(), 'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text}
defaults.update(kwargs) defaults.update(kwargs)
return forms.ChoiceField(**defaults) return forms.ModelChoiceField(**defaults)
class OneToOneField(RelatedField, IntegerField): class OneToOneField(RelatedField, IntegerField):
def __init__(self, to, to_field=None, **kwargs): def __init__(self, to, to_field=None, **kwargs):
@ -606,9 +606,9 @@ class OneToOneField(RelatedField, IntegerField):
cls._meta.one_to_one_field = self cls._meta.one_to_one_field = self
def formfield(self, **kwargs): def formfield(self, **kwargs):
defaults = {'choices': self.get_choices_default(), 'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text} defaults = {'queryset': self.rel.to._default_manager.all(), 'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text}
defaults.update(kwargs) defaults.update(kwargs)
return forms.ChoiceField(**kwargs) return forms.ModelChoiceField(**kwargs)
class ManyToManyField(RelatedField, Field): class ManyToManyField(RelatedField, Field):
def __init__(self, to, **kwargs): def __init__(self, to, **kwargs):
@ -716,9 +716,9 @@ class ManyToManyField(RelatedField, Field):
# MultipleChoiceField takes a list of IDs. # MultipleChoiceField takes a list of IDs.
if kwargs.get('initial') is not None: if kwargs.get('initial') is not None:
kwargs['initial'] = [i._get_pk_val() for i in kwargs['initial']] kwargs['initial'] = [i._get_pk_val() for i in kwargs['initial']]
defaults = {'choices': self.get_choices_default(), 'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text} defaults = {'queryset' : self.rel.to._default_manager.all(), 'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text}
defaults.update(kwargs) defaults.update(kwargs)
return forms.MultipleChoiceField(**defaults) return forms.ModelMultipleChoiceField(**defaults)
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,

View File

@ -194,7 +194,17 @@ class QuerySet(object):
yield obj yield obj
def count(self): def count(self):
"Performs a SELECT COUNT() and returns the number of records as an integer." """
Performs a SELECT COUNT() and returns the number of records as an
integer.
If the queryset is already cached (i.e. self._result_cache is set) this
simply returns the length of the cached results set to avoid multiple
SELECT COUNT(*) calls.
"""
if self._result_cache is not None:
return len(self._result_cache)
counter = self._clone() counter = self._clone()
counter._order_by = () counter._order_by = ()
counter._select_related = False counter._select_related = False

View File

@ -339,8 +339,9 @@ class ChoiceField(Field):
def _set_choices(self, value): def _set_choices(self, value):
# Setting choices also sets the choices on the widget. # Setting choices also sets the choices on the widget.
self._choices = value # choices can be any iterable, but we call list() on it because
self.widget.choices = value # it will be consumed more than once.
self._choices = self.widget.choices = list(value)
choices = property(_get_choices, _set_choices) choices = property(_get_choices, _set_choices)

View File

@ -3,9 +3,14 @@ Helper functions for creating Form classes from Django models
and database field objects. and database field objects.
""" """
from django.utils.translation import gettext
from util import ValidationError
from forms import BaseForm, DeclarativeFieldsMetaclass, SortedDictFromList from forms import BaseForm, DeclarativeFieldsMetaclass, SortedDictFromList
from fields import Field, ChoiceField
from widgets import Select, SelectMultiple, MultipleHiddenInput
__all__ = ('save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields') __all__ = ('save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields',
'ModelChoiceField', 'ModelMultipleChoiceField')
def model_save(self, commit=True): def model_save(self, commit=True):
""" """
@ -31,9 +36,9 @@ def save_instance(form, instance, commit=True):
raise ValueError("The %s could not be changed because the data didn't validate." % opts.object_name) raise ValueError("The %s could not be changed because the data didn't validate." % opts.object_name)
clean_data = form.clean_data clean_data = form.clean_data
for f in opts.fields: for f in opts.fields:
if isinstance(f, models.AutoField): if not f.editable or isinstance(f, models.AutoField):
continue continue
setattr(instance, f.attname, clean_data[f.name]) setattr(instance, f.name, clean_data[f.name])
if commit: if commit:
instance.save() instance.save()
for f in opts.many_to_many: for f in opts.many_to_many:
@ -63,6 +68,8 @@ def form_for_model(model, form=BaseForm, formfield_callback=lambda f: f.formfiel
opts = model._meta opts = model._meta
field_list = [] field_list = []
for f in opts.fields + opts.many_to_many: for f in opts.fields + opts.many_to_many:
if not f.editable:
continue
formfield = formfield_callback(f) formfield = formfield_callback(f)
if formfield: if formfield:
field_list.append((f.name, formfield)) field_list.append((f.name, formfield))
@ -84,6 +91,8 @@ def form_for_instance(instance, form=BaseForm, formfield_callback=lambda f, **kw
opts = model._meta opts = model._meta
field_list = [] field_list = []
for f in opts.fields + opts.many_to_many: for f in opts.fields + opts.many_to_many:
if not f.editable:
continue
current_value = f.value_from_object(instance) current_value = f.value_from_object(instance)
formfield = formfield_callback(f, initial=current_value) formfield = formfield_callback(f, initial=current_value)
if formfield: if formfield:
@ -94,5 +103,88 @@ def form_for_instance(instance, form=BaseForm, formfield_callback=lambda f, **kw
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."
fields = SortedDictFromList([(f.name, f.formfield()) for f in field_list]) fields = SortedDictFromList([(f.name, f.formfield()) for f in field_list if f.editable])
return type('FormForFields', (BaseForm,), {'base_fields': fields}) return type('FormForFields', (BaseForm,), {'base_fields': fields})
class QuerySetIterator(object):
def __init__(self, queryset, empty_label, cache_choices):
self.queryset, self.empty_label, self.cache_choices = queryset, empty_label, cache_choices
def __iter__(self):
if self.empty_label is not None:
yield (u"", self.empty_label)
for obj in self.queryset:
yield (obj._get_pk_val(), str(obj))
# Clear the QuerySet cache if required.
if not self.cache_choices:
self.queryset._result_cache = None
class ModelChoiceField(ChoiceField):
"A ChoiceField whose choices are a model QuerySet."
# This class is a subclass of ChoiceField for purity, but it doesn't
# actually use any of ChoiceField's implementation.
def __init__(self, queryset, empty_label=u"---------", cache_choices=False,
required=True, widget=Select, label=None, initial=None, help_text=None):
self.queryset = queryset
self.empty_label = empty_label
self.cache_choices = cache_choices
# Call Field instead of ChoiceField __init__() because we don't need
# ChoiceField.__init__().
Field.__init__(self, required, widget, label, initial, help_text)
self.widget.choices = self.choices
def _get_choices(self):
# If self._choices is set, then somebody must have manually set
# the property self.choices. In this case, just return self._choices.
if hasattr(self, '_choices'):
return self._choices
# Otherwise, execute the QuerySet in self.queryset to determine the
# choices dynamically. Return a fresh QuerySetIterator that has not
# been consumed. Note that we're instantiating a new QuerySetIterator
# *each* time _get_choices() is called (and, thus, each time
# self.choices is accessed) so that we can ensure the QuerySet has not
# been consumed.
return QuerySetIterator(self.queryset, self.empty_label, self.cache_choices)
def _set_choices(self, value):
# This method is copied from ChoiceField._set_choices(). It's necessary
# because property() doesn't allow a subclass to overwrite only
# _get_choices without implementing _set_choices.
self._choices = self.widget.choices = list(value)
choices = property(_get_choices, _set_choices)
def clean(self, value):
Field.clean(self, value)
if value in ('', None):
return None
try:
value = self.queryset.model._default_manager.get(pk=value)
except self.queryset.model.DoesNotExist:
raise ValidationError(gettext(u'Select a valid choice. That choice is not one of the available choices.'))
return value
class ModelMultipleChoiceField(ModelChoiceField):
"A MultipleChoiceField whose choices are a model QuerySet."
hidden_widget = MultipleHiddenInput
def __init__(self, queryset, cache_choices=False, required=True,
widget=SelectMultiple, label=None, initial=None, help_text=None):
super(ModelMultipleChoiceField, self).__init__(queryset, None, cache_choices,
required, widget, label, initial, help_text)
def clean(self, value):
if self.required and not value:
raise ValidationError(gettext(u'This field is required.'))
elif not self.required and not value:
return []
if not isinstance(value, (list, tuple)):
raise ValidationError(gettext(u'Enter a list of values.'))
final_values = []
for val in value:
try:
obj = self.queryset.model._default_manager.get(pk=val)
except self.queryset.model.DoesNotExist:
raise ValidationError(gettext(u'Select a valid choice. %s is not one of the available choices.') % val)
else:
final_values.append(obj)
return final_values

View File

@ -580,6 +580,8 @@ class FilterExpression(object):
def args_check(name, func, provided): def args_check(name, func, provided):
provided = list(provided) provided = list(provided)
plen = len(provided) plen = len(provided)
# Check to see if a decorator is providing the real function.
func = getattr(func, '_decorated_function', func)
args, varargs, varkw, defaults = getargspec(func) args, varargs, varkw, defaults = getargspec(func)
# First argument is filter input. # First argument is filter input.
args.pop(0) args.pop(0)
@ -812,7 +814,7 @@ class Library(object):
raise InvalidTemplateLibrary, "Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function) raise InvalidTemplateLibrary, "Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function)
def tag_function(self,func): def tag_function(self,func):
self.tags[func.__name__] = func self.tags[getattr(func, "_decorated_function", func).__name__] = func
return func return func
def filter(self, name=None, filter_func=None): def filter(self, name=None, filter_func=None):
@ -836,7 +838,7 @@ class Library(object):
raise InvalidTemplateLibrary, "Unsupported arguments to Library.filter: (%r, %r)", (name, filter_func) raise InvalidTemplateLibrary, "Unsupported arguments to Library.filter: (%r, %r)", (name, filter_func)
def filter_function(self, func): def filter_function(self, func):
self.filters[func.__name__] = func self.filters[getattr(func, "_decorated_function", func).__name__] = func
return func return func
def simple_tag(self,func): def simple_tag(self,func):
@ -850,9 +852,9 @@ class Library(object):
resolved_vars = [resolve_variable(var, context) for var in self.vars_to_resolve] resolved_vars = [resolve_variable(var, context) for var in self.vars_to_resolve]
return func(*resolved_vars) return func(*resolved_vars)
compile_func = curry(generic_tag_compiler, params, defaults, func.__name__, SimpleNode) compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, SimpleNode)
compile_func.__doc__ = func.__doc__ compile_func.__doc__ = func.__doc__
self.tag(func.__name__, compile_func) self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
return func return func
def inclusion_tag(self, file_name, context_class=Context, takes_context=False): def inclusion_tag(self, file_name, context_class=Context, takes_context=False):
@ -886,9 +888,9 @@ class Library(object):
self.nodelist = t.nodelist self.nodelist = t.nodelist
return self.nodelist.render(context_class(dict)) return self.nodelist.render(context_class(dict))
compile_func = curry(generic_tag_compiler, params, defaults, func.__name__, InclusionNode) compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, InclusionNode)
compile_func.__doc__ = func.__doc__ compile_func.__doc__ = func.__doc__
self.tag(func.__name__, compile_func) self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
return func return func
return dec return dec

View File

@ -8,6 +8,38 @@ import random as random_module
register = Library() register = Library()
#######################
# STRING DECORATOR #
#######################
def smart_string(obj):
# FUTURE: Unicode strings should probably be normalized to a specific
# encoding and non-unicode strings should be converted to unicode too.
# if isinstance(obj, unicode):
# obj = obj.encode(settings.DEFAULT_CHARSET)
# else:
# obj = unicode(obj, settings.DEFAULT_CHARSET)
# FUTURE: Replace dumb string logic below with cool unicode logic above.
if not isinstance(obj, basestring):
obj = str(obj)
return obj
def stringfilter(func):
"""
Decorator for filters which should only receive strings. The object passed
as the first positional argument will be converted to a string.
"""
def _dec(*args, **kwargs):
if args:
args = list(args)
args[0] = smart_string(args[0])
return func(*args, **kwargs)
# Include a reference to the real function (used to check original
# arguments by the template parser).
_dec._decorated_function = getattr(func, '_decorated_function', func)
return _dec
################### ###################
# STRINGS # # STRINGS #
################### ###################
@ -16,16 +48,18 @@ register = Library()
def addslashes(value): def addslashes(value):
"Adds slashes - useful for passing strings to JavaScript, for example." "Adds slashes - useful for passing strings to JavaScript, for example."
return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'") return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")
addslashes = stringfilter(addslashes)
def capfirst(value): def capfirst(value):
"Capitalizes the first character of the value" "Capitalizes the first character of the value"
value = str(value)
return value and value[0].upper() + value[1:] return value and value[0].upper() + value[1:]
capfirst = stringfilter(capfirst)
def fix_ampersands(value): def fix_ampersands(value):
"Replaces ampersands with ``&amp;`` entities" "Replaces ampersands with ``&amp;`` entities"
from django.utils.html import fix_ampersands from django.utils.html import fix_ampersands
return fix_ampersands(value) return fix_ampersands(value)
fix_ampersands = stringfilter(fix_ampersands)
def floatformat(text, arg=-1): def floatformat(text, arg=-1):
""" """
@ -52,7 +86,7 @@ def floatformat(text, arg=-1):
try: try:
d = int(arg) d = int(arg)
except ValueError: except ValueError:
return str(f) return smart_string(f)
m = f - int(f) m = f - int(f)
if not m and d < 0: if not m and d < 0:
return '%d' % int(f) return '%d' % int(f)
@ -69,22 +103,26 @@ def linenumbers(value):
for i, line in enumerate(lines): for i, line in enumerate(lines):
lines[i] = ("%0" + width + "d. %s") % (i + 1, escape(line)) lines[i] = ("%0" + width + "d. %s") % (i + 1, escape(line))
return '\n'.join(lines) return '\n'.join(lines)
linenumbers = stringfilter(linenumbers)
def lower(value): def lower(value):
"Converts a string into all lowercase" "Converts a string into all lowercase"
return value.lower() return value.lower()
lower = stringfilter(lower)
def make_list(value): def make_list(value):
""" """
Returns the value turned into a list. For an integer, it's a list of Returns the value turned into a list. For an integer, it's a list of
digits. For a string, it's a list of characters. digits. For a string, it's a list of characters.
""" """
return list(str(value)) return list(value)
make_list = stringfilter(make_list)
def slugify(value): def slugify(value):
"Converts to lowercase, removes non-alpha chars and converts spaces to hyphens" "Converts to lowercase, removes non-alpha chars and converts spaces to hyphens"
value = re.sub('[^\w\s-]', '', value).strip().lower() value = re.sub('[^\w\s-]', '', value).strip().lower()
return re.sub('[-\s]+', '-', value) return re.sub('[-\s]+', '-', value)
slugify = stringfilter(slugify)
def stringformat(value, arg): def stringformat(value, arg):
""" """
@ -96,13 +134,14 @@ def stringformat(value, arg):
of Python string formatting of Python string formatting
""" """
try: try:
return ("%" + arg) % value return ("%" + str(arg)) % value
except (ValueError, TypeError): except (ValueError, TypeError):
return "" return ""
def title(value): def title(value):
"Converts a string into titlecase" "Converts a string into titlecase"
return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title()) return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
title = stringfilter(title)
def truncatewords(value, arg): def truncatewords(value, arg):
""" """
@ -118,6 +157,7 @@ def truncatewords(value, arg):
if not isinstance(value, basestring): if not isinstance(value, basestring):
value = str(value) value = str(value)
return truncate_words(value, length) return truncate_words(value, length)
truncatewords = stringfilter(truncatewords)
def truncatewords_html(value, arg): def truncatewords_html(value, arg):
""" """
@ -133,10 +173,12 @@ def truncatewords_html(value, arg):
if not isinstance(value, basestring): if not isinstance(value, basestring):
value = str(value) value = str(value)
return truncate_html_words(value, length) return truncate_html_words(value, length)
truncatewords_html = stringfilter(truncatewords_html)
def upper(value): def upper(value):
"Converts a string into all uppercase" "Converts a string into all uppercase"
return value.upper() return value.upper()
upper = stringfilter(upper)
def urlencode(value): def urlencode(value):
"Escapes a value for use in a URL" "Escapes a value for use in a URL"
@ -144,11 +186,13 @@ def urlencode(value):
if not isinstance(value, basestring): if not isinstance(value, basestring):
value = str(value) value = str(value)
return urllib.quote(value) return urllib.quote(value)
urlencode = stringfilter(urlencode)
def urlize(value): def urlize(value):
"Converts URLs in plain text into clickable links" "Converts URLs in plain text into clickable links"
from django.utils.html import urlize from django.utils.html import urlize
return urlize(value, nofollow=True) return urlize(value, nofollow=True)
urlize = stringfilter(urlize)
def urlizetrunc(value, limit): def urlizetrunc(value, limit):
""" """
@ -159,10 +203,12 @@ def urlizetrunc(value, limit):
""" """
from django.utils.html import urlize from django.utils.html import urlize
return urlize(value, trim_url_limit=int(limit), nofollow=True) return urlize(value, trim_url_limit=int(limit), nofollow=True)
urlizetrunc = stringfilter(urlizetrunc)
def wordcount(value): def wordcount(value):
"Returns the number of words" "Returns the number of words"
return len(value.split()) return len(value.split())
wordcount = stringfilter(wordcount)
def wordwrap(value, arg): def wordwrap(value, arg):
""" """
@ -171,7 +217,8 @@ def wordwrap(value, arg):
Argument: number of characters to wrap the text at. Argument: number of characters to wrap the text at.
""" """
from django.utils.text import wrap from django.utils.text import wrap
return wrap(str(value), int(arg)) return wrap(value, int(arg))
wordwrap = stringfilter(wordwrap)
def ljust(value, arg): def ljust(value, arg):
""" """
@ -179,7 +226,8 @@ def ljust(value, arg):
Argument: field size Argument: field size
""" """
return str(value).ljust(int(arg)) return value.ljust(int(arg))
ljust = stringfilter(ljust)
def rjust(value, arg): def rjust(value, arg):
""" """
@ -187,15 +235,18 @@ def rjust(value, arg):
Argument: field size Argument: field size
""" """
return str(value).rjust(int(arg)) return value.rjust(int(arg))
rjust = stringfilter(rjust)
def center(value, arg): def center(value, arg):
"Centers the value in a field of a given width" "Centers the value in a field of a given width"
return str(value).center(int(arg)) return value.center(int(arg))
center = stringfilter(center)
def cut(value, arg): def cut(value, arg):
"Removes all values of arg from the given string" "Removes all values of arg from the given string"
return value.replace(arg, '') return value.replace(arg, '')
cut = stringfilter(cut)
################### ###################
# HTML STRINGS # # HTML STRINGS #
@ -205,15 +256,18 @@ def escape(value):
"Escapes a string's HTML" "Escapes a string's HTML"
from django.utils.html import escape from django.utils.html import escape
return escape(value) return escape(value)
escape = stringfilter(escape)
def linebreaks(value): def linebreaks(value):
"Converts newlines into <p> and <br />s" "Converts newlines into <p> and <br />s"
from django.utils.html import linebreaks from django.utils.html import linebreaks
return linebreaks(value) return linebreaks(value)
linebreaks = stringfilter(linebreaks)
def linebreaksbr(value): def linebreaksbr(value):
"Converts newlines into <br />s" "Converts newlines into <br />s"
return value.replace('\n', '<br />') return value.replace('\n', '<br />')
linebreaksbr = stringfilter(linebreaksbr)
def removetags(value, tags): def removetags(value, tags):
"Removes a space separated list of [X]HTML tags from the output" "Removes a space separated list of [X]HTML tags from the output"
@ -224,13 +278,13 @@ def removetags(value, tags):
value = starttag_re.sub('', value) value = starttag_re.sub('', value)
value = endtag_re.sub('', value) value = endtag_re.sub('', value)
return value return value
removetags = stringfilter(removetags)
def striptags(value): def striptags(value):
"Strips all [X]HTML tags" "Strips all [X]HTML tags"
from django.utils.html import strip_tags from django.utils.html import strip_tags
if not isinstance(value, basestring):
value = str(value)
return strip_tags(value) return strip_tags(value)
striptags = stringfilter(striptags)
################### ###################
# LISTS # # LISTS #
@ -265,7 +319,7 @@ def first(value):
def join(value, arg): def join(value, arg):
"Joins a list with a string, like Python's ``str.join(list)``" "Joins a list with a string, like Python's ``str.join(list)``"
try: try:
return arg.join(map(str, value)) return arg.join(map(smart_string, value))
except AttributeError: # fail silently but nicely except AttributeError: # fail silently but nicely
return value return value

View File

@ -654,6 +654,18 @@ decorator instead::
If you leave off the ``name`` argument, as in the second example above, Django If you leave off the ``name`` argument, as in the second example above, Django
will use the function's name as the filter name. will use the function's name as the filter name.
Template filters which expect strings
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you are writing a template filter which only expects a string as the first
argument, you should use the included decorator ``stringfilter`` which will convert
an object to it's string value before being passed to your function::
from django import template
@template.stringfilter
def lower(value):
return value.lower()
Writing custom template tags Writing custom template tags
---------------------------- ----------------------------

View File

@ -28,7 +28,7 @@ for dirpath, dirnames, filenames in os.walk(django_dir):
# Small hack for working with bdist_wininst. # Small hack for working with bdist_wininst.
# See http://mail.python.org/pipermail/distutils-sig/2004-August/004134.html # See http://mail.python.org/pipermail/distutils-sig/2004-August/004134.html
if sys.argv[1] == 'bdist_wininst': if len(sys.argv) > 1 and sys.argv[1] == 'bdist_wininst':
for file_info in data_files: for file_info in data_files:
file_info[0] = '/PURELIB/%s' % file_info[0] file_info[0] = '/PURELIB/%s' % file_info[0]

View File

@ -40,13 +40,27 @@ class Writer(models.Model):
class Article(models.Model): class Article(models.Model):
headline = models.CharField(maxlength=50) headline = models.CharField(maxlength=50)
pub_date = models.DateField() pub_date = models.DateField()
created = models.DateField(editable=False)
writer = models.ForeignKey(Writer) writer = models.ForeignKey(Writer)
article = models.TextField() article = models.TextField()
categories = models.ManyToManyField(Category, blank=True) categories = models.ManyToManyField(Category, blank=True)
def save(self):
import datetime
if not self.id:
self.created = datetime.date.today()
return super(Article, self).save()
def __str__(self): def __str__(self):
return self.headline return self.headline
class PhoneNumber(models.Model):
phone = models.PhoneNumberField()
description = models.CharField(maxlength=20)
def __str__(self):
return self.phone
__test__ = {'API_TESTS': """ __test__ = {'API_TESTS': """
>>> from django.newforms import form_for_model, form_for_instance, save_instance, BaseForm, Form, CharField >>> from django.newforms import form_for_model, form_for_instance, save_instance, BaseForm, Form, CharField
>>> import datetime >>> import datetime
@ -281,4 +295,170 @@ existing Category instance.
<Category: Third> <Category: Third>
>>> Category.objects.get(id=3) >>> Category.objects.get(id=3)
<Category: Third> <Category: Third>
Here, we demonstrate that choices for a ForeignKey ChoiceField are determined
at runtime, based on the data in the database when the form is displayed, not
the data in the database when the form is instantiated.
>>> ArticleForm = form_for_model(Article)
>>> f = ArticleForm(auto_id=False)
>>> print f.as_ul()
<li>Headline: <input type="text" name="headline" maxlength="50" /></li>
<li>Pub date: <input type="text" name="pub_date" /></li>
<li>Writer: <select name="writer">
<option value="" selected="selected">---------</option>
<option value="1">Mike Royko</option>
<option value="2">Bob Woodward</option>
</select></li>
<li>Article: <textarea name="article"></textarea></li>
<li>Categories: <select multiple="multiple" name="categories">
<option value="1">Entertainment</option>
<option value="2">It&#39;s a test</option>
<option value="3">Third</option>
</select> Hold down "Control", or "Command" on a Mac, to select more than one.</li>
>>> Category.objects.create(name='Fourth', url='4th')
<Category: Fourth>
>>> Writer.objects.create(name='Carl Bernstein')
<Writer: Carl Bernstein>
>>> print f.as_ul()
<li>Headline: <input type="text" name="headline" maxlength="50" /></li>
<li>Pub date: <input type="text" name="pub_date" /></li>
<li>Writer: <select name="writer">
<option value="" selected="selected">---------</option>
<option value="1">Mike Royko</option>
<option value="2">Bob Woodward</option>
<option value="3">Carl Bernstein</option>
</select></li>
<li>Article: <textarea name="article"></textarea></li>
<li>Categories: <select multiple="multiple" name="categories">
<option value="1">Entertainment</option>
<option value="2">It&#39;s a test</option>
<option value="3">Third</option>
<option value="4">Fourth</option>
</select> Hold down "Control", or "Command" on a Mac, to select more than one.</li>
# ModelChoiceField ############################################################
>>> from django.newforms import ModelChoiceField, ModelMultipleChoiceField
>>> f = ModelChoiceField(Category.objects.all())
>>> f.clean('')
Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
>>> f.clean(None)
Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
>>> f.clean(0)
Traceback (most recent call last):
...
ValidationError: [u'Select a valid choice. That choice is not one of the available choices.']
>>> f.clean(3)
<Category: Third>
>>> f.clean(2)
<Category: It's a test>
# Add a Category object *after* the ModelChoiceField has already been
# instantiated. This proves clean() checks the database during clean() rather
# than caching it at time of instantiation.
>>> Category.objects.create(name='Fifth', url='5th')
<Category: Fifth>
>>> f.clean(5)
<Category: Fifth>
# Delete a Category object *after* the ModelChoiceField has already been
# instantiated. This proves clean() checks the database during clean() rather
# than caching it at time of instantiation.
>>> Category.objects.get(url='5th').delete()
>>> f.clean(5)
Traceback (most recent call last):
...
ValidationError: [u'Select a valid choice. That choice is not one of the available choices.']
>>> f = ModelChoiceField(Category.objects.filter(pk=1), required=False)
>>> print f.clean('')
None
>>> f.clean('')
>>> f.clean('1')
<Category: Entertainment>
>>> f.clean('100')
Traceback (most recent call last):
...
ValidationError: [u'Select a valid choice. That choice is not one of the available choices.']
# ModelMultipleChoiceField ####################################################
>>> f = ModelMultipleChoiceField(Category.objects.all())
>>> f.clean(None)
Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
>>> f.clean([])
Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
>>> f.clean([1])
[<Category: Entertainment>]
>>> f.clean([2])
[<Category: It's a test>]
>>> f.clean(['1'])
[<Category: Entertainment>]
>>> f.clean(['1', '2'])
[<Category: Entertainment>, <Category: It's a test>]
>>> f.clean([1, '2'])
[<Category: Entertainment>, <Category: It's a test>]
>>> f.clean((1, '2'))
[<Category: Entertainment>, <Category: It's a test>]
>>> f.clean(['100'])
Traceback (most recent call last):
...
ValidationError: [u'Select a valid choice. 100 is not one of the available choices.']
>>> f.clean('hello')
Traceback (most recent call last):
...
ValidationError: [u'Enter a list of values.']
# Add a Category object *after* the ModelChoiceField has already been
# instantiated. This proves clean() checks the database during clean() rather
# than caching it at time of instantiation.
>>> Category.objects.create(id=6, name='Sixth', url='6th')
<Category: Sixth>
>>> f.clean([6])
[<Category: Sixth>]
# Delete a Category object *after* the ModelChoiceField has already been
# instantiated. This proves clean() checks the database during clean() rather
# than caching it at time of instantiation.
>>> Category.objects.get(url='6th').delete()
>>> f.clean([6])
Traceback (most recent call last):
...
ValidationError: [u'Select a valid choice. 6 is not one of the available choices.']
>>> f = ModelMultipleChoiceField(Category.objects.all(), required=False)
>>> f.clean([])
[]
>>> f.clean(())
[]
>>> f.clean(['10'])
Traceback (most recent call last):
...
ValidationError: [u'Select a valid choice. 10 is not one of the available choices.']
>>> f.clean(['3', '10'])
Traceback (most recent call last):
...
ValidationError: [u'Select a valid choice. 10 is not one of the available choices.']
>>> f.clean(['1', '10'])
Traceback (most recent call last):
...
ValidationError: [u'Select a valid choice. 10 is not one of the available choices.']
# PhoneNumberField ############################################################
>>> PhoneNumberForm = form_for_model(PhoneNumber)
>>> f = PhoneNumberForm({'phone': '(312) 555-1212', 'description': 'Assistance'})
>>> f.is_valid()
True
>>> f.clean_data
{'phone': u'312-555-1212', 'description': u'Assistance'}
"""} """}

View File

@ -388,7 +388,53 @@ False
>>> phone2numeric('0800 flowers') >>> phone2numeric('0800 flowers')
'0800 3569377' '0800 3569377'
# Filters shouldn't break if passed non-strings
>>> addslashes(123)
'123'
>>> linenumbers(123)
'1. 123'
>>> lower(123)
'123'
>>> make_list(123)
['1', '2', '3']
>>> slugify(123)
'123'
>>> title(123)
'123'
>>> truncatewords(123, 2)
'123'
>>> upper(123)
'123'
>>> urlencode(123)
'123'
>>> urlize(123)
'123'
>>> urlizetrunc(123, 1)
'123'
>>> wordcount(123)
1
>>> wordwrap(123, 2)
'123'
>>> ljust('123', 4)
'123 '
>>> rjust('123', 4)
' 123'
>>> center('123', 5)
' 123 '
>>> center('123', 6)
' 123 '
>>> cut(123, '2')
'13'
>>> escape(123)
'123'
>>> linebreaks(123)
'<p>123</p>'
>>> linebreaksbr(123)
'123'
>>> removetags(123, 'a')
'123'
>>> striptags(123)
'123'
""" """

View File

@ -318,6 +318,7 @@ The value is compared to its str():
</select> </select>
The 'choices' argument can be any iterable: The 'choices' argument can be any iterable:
>>> from itertools import chain
>>> def get_choices(): >>> def get_choices():
... for i in range(5): ... for i in range(5):
... yield (i, i) ... yield (i, i)
@ -329,6 +330,17 @@ The 'choices' argument can be any iterable:
<option value="3">3</option> <option value="3">3</option>
<option value="4">4</option> <option value="4">4</option>
</select> </select>
>>> things = ({'id': 1, 'name': 'And Boom'}, {'id': 2, 'name': 'One More Thing!'})
>>> class SomeForm(Form):
... somechoice = ChoiceField(choices=chain((('', '-'*9),), [(thing['id'], thing['name']) for thing in things]))
>>> f = SomeForm()
>>> f.as_table()
u'<tr><th><label for="id_somechoice">Somechoice:</label></th><td><select name="somechoice" id="id_somechoice">\n<option value="" selected="selected">---------</option>\n<option value="1">And Boom</option>\n<option value="2">One More Thing!</option>\n</select></td></tr>'
>>> f.as_table()
u'<tr><th><label for="id_somechoice">Somechoice:</label></th><td><select name="somechoice" id="id_somechoice">\n<option value="" selected="selected">---------</option>\n<option value="1">And Boom</option>\n<option value="2">One More Thing!</option>\n</select></td></tr>'
>>> f = SomeForm({'somechoice': 2})
>>> f.as_table()
u'<tr><th><label for="id_somechoice">Somechoice:</label></th><td><select name="somechoice" id="id_somechoice">\n<option value="">---------</option>\n<option value="1">And Boom</option>\n<option value="2" selected="selected">One More Thing!</option>\n</select></td></tr>'
You can also pass 'choices' to the constructor: You can also pass 'choices' to the constructor:
>>> w = Select(choices=[(1, 1), (2, 2), (3, 3)]) >>> w = Select(choices=[(1, 1), (2, 2), (3, 3)])
@ -1999,6 +2011,19 @@ For a form with a <select>, use ChoiceField:
<option value="J">Java</option> <option value="J">Java</option>
</select> </select>
A subtlety: If one of the choices' value is the empty string and the form is
unbound, then the <option> for the empty-string choice will get selected="selected".
>>> class FrameworkForm(Form):
... name = CharField()
... language = ChoiceField(choices=[('', '------'), ('P', 'Python'), ('J', 'Java')])
>>> f = FrameworkForm(auto_id=False)
>>> print f['language']
<select name="language">
<option value="" selected="selected">------</option>
<option value="P">Python</option>
<option value="J">Java</option>
</select>
You can specify widget attributes in the Widget constructor. You can specify widget attributes in the Widget constructor.
>>> class FrameworkForm(Form): >>> class FrameworkForm(Form):
... name = CharField() ... name = CharField()
@ -3304,6 +3329,75 @@ u''
>>> f.clean('') >>> f.clean('')
u'' u''
# USPhoneNumberField ##########################################################
USPhoneNumberField validates that the data is a valid U.S. phone number,
including the area code. It's normalized to XXX-XXX-XXXX format.
>>> from django.contrib.localflavor.usa.forms import USPhoneNumberField
>>> f = USPhoneNumberField()
>>> f.clean('312-555-1212')
u'312-555-1212'
>>> f.clean('3125551212')
u'312-555-1212'
>>> f.clean('312 555-1212')
u'312-555-1212'
>>> f.clean('(312) 555-1212')
u'312-555-1212'
>>> f.clean('312 555 1212')
u'312-555-1212'
>>> f.clean('312.555.1212')
u'312-555-1212'
>>> f.clean('312.555-1212')
u'312-555-1212'
>>> f.clean(' (312) 555.1212 ')
u'312-555-1212'
>>> f.clean('555-1212')
Traceback (most recent call last):
...
ValidationError: [u'Phone numbers must be in XXX-XXX-XXXX format.']
>>> f.clean('312-55-1212')
Traceback (most recent call last):
...
ValidationError: [u'Phone numbers must be in XXX-XXX-XXXX format.']
>>> f.clean(None)
Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
>>> f.clean('')
Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
>>> f = USPhoneNumberField(required=False)
>>> f.clean('312-555-1212')
u'312-555-1212'
>>> f.clean('3125551212')
u'312-555-1212'
>>> f.clean('312 555-1212')
u'312-555-1212'
>>> f.clean('(312) 555-1212')
u'312-555-1212'
>>> f.clean('312 555 1212')
u'312-555-1212'
>>> f.clean('312.555.1212')
u'312-555-1212'
>>> f.clean('312.555-1212')
u'312-555-1212'
>>> f.clean(' (312) 555.1212 ')
u'312-555-1212'
>>> f.clean('555-1212')
Traceback (most recent call last):
...
ValidationError: [u'Phone numbers must be in XXX-XXX-XXXX format.']
>>> f.clean('312-55-1212')
Traceback (most recent call last):
...
ValidationError: [u'Phone numbers must be in XXX-XXX-XXXX format.']
>>> f.clean(None)
u''
>>> f.clean('')
u''
# USStateField ################################################################ # USStateField ################################################################
USStateField validates that the data is either an abbreviation or name of a USStateField validates that the data is either an abbreviation or name of a