1
0
mirror of https://github.com/django/django.git synced 2025-10-24 14:16:09 +00:00

Imported Django from private SVN repository (created from r. 8825)

git-svn-id: http://code.djangoproject.com/svn/django/trunk@3 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Adrian Holovaty
2005-07-13 01:25:57 +00:00
parent 07ffc7d605
commit ed114e1510
114 changed files with 13851 additions and 0 deletions

0
django/core/__init__.py Normal file
View File

255
django/core/cache.py Normal file
View File

@@ -0,0 +1,255 @@
"""
Caching framework.
This module defines set of cache backends that all conform to a simple API.
In a nutshell, a cache is a set of values -- which can be any object that
may be pickled -- identified by string keys. For the complete API, see
the abstract Cache object, below.
Client code should not access a cache backend directly; instead
it should use the get_cache() function. This function will look at
settings.CACHE_BACKEND and use that to create and load a cache object.
The CACHE_BACKEND setting is a quasi-URI; examples are:
memcached://127.0.0.1:11211/ A memcached backend; the server is running
on localhost port 11211.
pgsql://tablename/ A pgsql backend (the pgsql backend uses
the same database/username as the rest of
the CMS, so only a table name is needed.)
file:///var/tmp/django.cache/ A file-based cache at /var/tmp/django.cache
simple:/// A simple single-process memory cache; you
probably don't want to use this except for
testing. Note that this cache backend is
NOT threadsafe!
All caches may take arguments; these are given in query-string style. Valid
arguments are:
timeout
Default timeout, in seconds, to use for the cache. Defaults
to 5 minutes (300 seconds).
max_entries
For the simple, file, and database backends, the maximum number of
entries allowed in the cache before it is cleaned. Defaults to
300.
cull_percentage
The percentage of entries that are culled when max_entries is reached.
The actual percentage is 1/cull_percentage, so set cull_percentage=3 to
cull 1/3 of the entries when max_entries is reached.
A value of 0 for cull_percentage means that the entire cache will be
dumped when max_entries is reached. This makes culling *much* faster
at the expense of more cache misses.
For example:
memcached://127.0.0.1:11211/?timeout=60
pgsql://tablename/?timeout=120&max_entries=500&cull_percentage=4
Invalid arguments are silently ignored, as are invalid values of known
arguments.
So far, only the memcached and simple backend have been implemented; backends
using postgres, and file-system storage are planned.
"""
##############
# Exceptions #
##############
class InvalidCacheBackendError(Exception):
pass
################################
# Abstract base implementation #
################################
class _Cache:
def __init__(self, params):
timeout = params.get('timeout', 300)
try:
timeout = int(timeout)
except (ValueError, TypeError):
timeout = 300
self.default_timeout = timeout
def get(self, key, default=None):
'''
Fetch a given key from the cache. If the key does not exist, return
default, which itself defaults to None.
'''
raise NotImplementedError
def set(self, key, value, timeout=None):
'''
Set a value in the cache. If timeout is given, that timeout will be
used for the key; otherwise the default cache timeout will be used.
'''
raise NotImplementedError
def delete(self, key):
'''
Delete a key from the cache, failing silently.
'''
raise NotImplementedError
def get_many(self, keys):
'''
Fetch a bunch of keys from the cache. For certain backends (memcached,
pgsql) this can be *much* faster when fetching multiple values.
Returns a dict mapping each key in keys to its value. If the given
key is missing, it will be missing from the response dict.
'''
d = {}
for k in keys:
val = self.get(k)
if val is not None:
d[k] = val
return d
def has_key(self, key):
'''
Returns True if the key is in the cache and has not expired.
'''
return self.get(key) is not None
###########################
# memcached cache backend #
###########################
try:
import memcache
except ImportError:
_MemcachedCache = None
else:
class _MemcachedCache(_Cache):
"""Memcached cache backend."""
def __init__(self, server, params):
_Cache.__init__(self, params)
self._cache = memcache.Client([server])
def get(self, key, default=None):
val = self._cache.get(key)
if val is None:
return default
else:
return val
def set(self, key, value, timeout=0):
self._cache.set(key, value, timeout)
def delete(self, key):
self._cache.delete(key)
def get_many(self, keys):
return self._cache.get_multi(keys)
##################################
# Single-process in-memory cache #
##################################
import time
class _SimpleCache(_Cache):
"""Simple single-process in-memory cache"""
def __init__(self, host, params):
_Cache.__init__(self, params)
self._cache = {}
self._expire_info = {}
max_entries = params.get('max_entries', 300)
try:
self._max_entries = int(max_entries)
except (ValueError, TypeError):
self._max_entries = 300
cull_frequency = params.get('cull_frequency', 3)
try:
self._cull_frequency = int(cull_frequency)
except (ValueError, TypeError):
self._cull_frequency = 3
def get(self, key, default=None):
now = time.time()
exp = self._expire_info.get(key, now)
if exp is not None and exp < now:
del self._cache[key]
del self._expire_info[key]
return default
else:
return self._cache.get(key, default)
def set(self, key, value, timeout=None):
if len(self._cache) >= self._max_entries:
self._cull()
if timeout is None:
timeout = self.default_timeout
self._cache[key] = value
self._expire_info[key] = time.time() + timeout
def delete(self, key):
try:
del self._cache[key]
except KeyError:
pass
try:
del self._expire_info[key]
except KeyError:
pass
def has_key(self, key):
return self._cache.has_key(key)
def _cull(self):
if self._cull_frequency == 0:
self._cache.clear()
self._expire_info.clear()
else:
doomed = [k for (i, k) in enumerate(self._cache) if i % self._cull_frequency == 0]
for k in doomed:
self.delete(k)
##########################################
# Read settings and load a cache backend #
##########################################
from cgi import parse_qsl
_BACKENDS = {
'memcached' : _MemcachedCache,
'simple' : _SimpleCache,
}
def get_cache(backend_uri):
if backend_uri.find(':') == -1:
raise InvalidCacheBackendError("Backend URI must start with scheme://")
scheme, rest = backend_uri.split(':', 1)
if not rest.startswith('//'):
raise InvalidCacheBackendError("Backend URI must start with scheme://")
if scheme not in _BACKENDS.keys():
raise InvalidCacheBackendError("%r is not a valid cache backend" % scheme)
host = rest[2:]
qpos = rest.find('?')
if qpos != -1:
params = dict(parse_qsl(rest[qpos+1:]))
host = rest[:qpos]
else:
params = {}
if host.endswith('/'):
host = host[:-1]
return _BACKENDS[scheme](host, params)
from django.conf.settings import CACHE_BACKEND
cache = get_cache(CACHE_BACKEND)

View File

@@ -0,0 +1,28 @@
"""
This is the core database connection.
All CMS code assumes database SELECT statements cast the resulting values as such:
* booleans are mapped to Python booleans
* dates are mapped to Python datetime.date objects
* times are mapped to Python datetime.time objects
* timestamps are mapped to Python datetime.datetime objects
Right now, we're handling this by using psycopg's custom typecast definitions.
If we move to a different database module, we should ensure that it either
performs the appropriate typecasting out of the box, or that it has hooks that
let us do that.
"""
from django.conf.settings import DATABASE_ENGINE
dbmod = __import__('django.core.db.backends.%s' % DATABASE_ENGINE, '', '', [''])
DatabaseError = dbmod.DatabaseError
db = dbmod.DatabaseWrapper()
dictfetchone = dbmod.dictfetchone
dictfetchmany = dbmod.dictfetchmany
dictfetchall = dbmod.dictfetchall
dictfetchall = dbmod.dictfetchall
get_last_insert_id = dbmod.get_last_insert_id
OPERATOR_MAPPING = dbmod.OPERATOR_MAPPING
DATA_TYPES = dbmod.DATA_TYPES

View File

View File

@@ -0,0 +1,107 @@
"""
MySQL database backend for Django.
Requires MySQLdb: http://sourceforge.net/projects/mysql-python
"""
from django.core.db import base, typecasts
import MySQLdb as Database
from MySQLdb.converters import conversions
from MySQLdb.constants import FIELD_TYPE
import types
DatabaseError = Database.DatabaseError
django_conversions = conversions.copy()
django_conversions.update({
types.BooleanType: typecasts.rev_typecast_boolean,
FIELD_TYPE.DATETIME: typecasts.typecast_timestamp,
FIELD_TYPE.DATE: typecasts.typecast_date,
FIELD_TYPE.TIME: typecasts.typecast_time,
})
class DatabaseWrapper:
def __init__(self):
self.connection = None
self.queries = []
def cursor(self):
from django.conf.settings import DATABASE_USER, DATABASE_NAME, DATABASE_HOST, DATABASE_PASSWORD, DEBUG
if self.connection is None:
self.connection = Database.connect(user=DATABASE_USER, db=DATABASE_NAME,
passwd=DATABASE_PASSWORD, host=DATABASE_HOST, conv=django_conversions)
if DEBUG:
return base.CursorDebugWrapper(self.connection.cursor(), self)
return self.connection.cursor()
def commit(self):
pass
def rollback(self):
pass
def close(self):
if self.connection is not None:
self.connection.close()
self.connection = None
def dictfetchone(cursor):
"Returns a row from the cursor as a dict"
raise NotImplementedError
def dictfetchmany(cursor, number):
"Returns a certain number of rows from a cursor as a dict"
raise NotImplementedError
def dictfetchall(cursor):
"Returns all rows from a cursor as a dict"
raise NotImplementedError
def get_last_insert_id(cursor, table_name, pk_name):
cursor.execute("SELECT LAST_INSERT_ID()")
return cursor.fetchone()[0]
OPERATOR_MAPPING = {
'exact': '=',
'iexact': 'LIKE',
'contains': 'LIKE',
'icontains': 'LIKE',
'ne': '!=',
'gt': '>',
'gte': '>=',
'lt': '<',
'lte': '<=',
'startswith': 'LIKE',
'endswith': 'LIKE'
}
# This dictionary maps Field objects to their associated MySQL column
# types, as strings. Column-type strings can contain format strings; they'll
# be interpolated against the values of Field.__dict__ before being output.
# If a column type is set to None, it won't be included in the output.
DATA_TYPES = {
'AutoField': 'mediumint(9) auto_increment',
'BooleanField': 'bool',
'CharField': 'varchar(%(maxlength)s)',
'CommaSeparatedIntegerField': 'varchar(%(maxlength)s)',
'DateField': 'date',
'DateTimeField': 'datetime',
'EmailField': 'varchar(75)',
'FileField': 'varchar(100)',
'FloatField': 'numeric(%(max_digits)s, %(decimal_places)s)',
'ImageField': 'varchar(100)',
'IntegerField': 'integer',
'IPAddressField': 'char(15)',
'ManyToManyField': None,
'NullBooleanField': 'bool',
'PhoneNumberField': 'varchar(20)',
'PositiveIntegerField': 'integer UNSIGNED',
'PositiveSmallIntegerField': 'smallint UNSIGNED',
'SlugField': 'varchar(50)',
'SmallIntegerField': 'smallint',
'TextField': 'text',
'TimeField': 'time',
'URLField': 'varchar(200)',
'USStateField': 'varchar(2)',
'XMLField': 'text',
}

View File

@@ -0,0 +1,109 @@
"""
PostgreSQL database backend for Django.
Requires psycopg 1: http://initd.org/projects/psycopg1
"""
from django.core.db import base, typecasts
import psycopg as Database
DatabaseError = Database.DatabaseError
class DatabaseWrapper:
def __init__(self):
self.connection = None
self.queries = []
def cursor(self):
from django.conf.settings import DATABASE_USER, DATABASE_NAME, DATABASE_HOST, DATABASE_PASSWORD, DEBUG, TIME_ZONE
if self.connection is None:
# Note that "host=" has to be last, because it might be blank.
self.connection = Database.connect("user=%s dbname=%s password=%s host=%s" % \
(DATABASE_USER, DATABASE_NAME, DATABASE_PASSWORD, DATABASE_HOST))
self.connection.set_isolation_level(1) # make transactions transparent to all cursors
cursor = self.connection.cursor()
cursor.execute("SET TIME ZONE %s", [TIME_ZONE])
if DEBUG:
return base.CursorDebugWrapper(cursor, self)
return cursor
def commit(self):
return self.connection.commit()
def rollback(self):
if self.connection:
return self.connection.rollback()
def close(self):
if self.connection is not None:
self.connection.close()
self.connection = None
def dictfetchone(cursor):
"Returns a row from the cursor as a dict"
return cursor.dictfetchone()
def dictfetchmany(cursor, number):
"Returns a certain number of rows from a cursor as a dict"
return cursor.dictfetchmany(number)
def dictfetchall(cursor):
"Returns all rows from a cursor as a dict"
return cursor.dictfetchall()
def get_last_insert_id(cursor, table_name, pk_name):
cursor.execute("SELECT CURRVAL('%s_%s_seq')" % (table_name, pk_name))
return cursor.fetchone()[0]
# Register these custom typecasts, because Django expects dates/times to be
# in Python's native (standard-library) datetime/time format, whereas psycopg
# use mx.DateTime by default.
Database.register_type(Database.new_type((1082,), "DATE", typecasts.typecast_date))
Database.register_type(Database.new_type((1083,1266), "TIME", typecasts.typecast_time))
Database.register_type(Database.new_type((1114,1184), "TIMESTAMP", typecasts.typecast_timestamp))
Database.register_type(Database.new_type((16,), "BOOLEAN", typecasts.typecast_boolean))
OPERATOR_MAPPING = {
'exact': '=',
'iexact': 'ILIKE',
'contains': 'LIKE',
'icontains': 'ILIKE',
'ne': '!=',
'gt': '>',
'gte': '>=',
'lt': '<',
'lte': '<=',
'startswith': 'LIKE',
'endswith': 'LIKE'
}
# This dictionary maps Field objects to their associated PostgreSQL column
# types, as strings. Column-type strings can contain format strings; they'll
# be interpolated against the values of Field.__dict__ before being output.
# If a column type is set to None, it won't be included in the output.
DATA_TYPES = {
'AutoField': 'serial',
'BooleanField': 'boolean',
'CharField': 'varchar(%(maxlength)s)',
'CommaSeparatedIntegerField': 'varchar(%(maxlength)s)',
'DateField': 'date',
'DateTimeField': 'timestamp with time zone',
'EmailField': 'varchar(75)',
'FileField': 'varchar(100)',
'FloatField': 'numeric(%(max_digits)s, %(decimal_places)s)',
'ImageField': 'varchar(100)',
'IntegerField': 'integer',
'IPAddressField': 'inet',
'ManyToManyField': None,
'NullBooleanField': 'boolean',
'PhoneNumberField': 'varchar(20)',
'PositiveIntegerField': 'integer CHECK (%(name)s >= 0)',
'PositiveSmallIntegerField': 'smallint CHECK (%(name)s >= 0)',
'SlugField': 'varchar(50)',
'SmallIntegerField': 'smallint',
'TextField': 'text',
'TimeField': 'time',
'URLField': 'varchar(200)',
'USStateField': 'varchar(2)',
'XMLField': 'text',
}

32
django/core/db/base.py Normal file
View File

@@ -0,0 +1,32 @@
from time import time
class CursorDebugWrapper:
def __init__(self, cursor, db):
self.cursor = cursor
self.db = db
def execute(self, sql, params=[]):
start = time()
result = self.cursor.execute(sql, params)
stop = time()
self.db.queries.append({
'sql': sql % tuple(params),
'time': "%.3f" % (stop - start),
})
return result
def executemany(self, sql, param_list):
start = time()
result = self.cursor.executemany(sql, param_list)
stop = time()
self.db.queries.append({
'sql': 'MANY: ' + sql + ' ' + str(tuple(param_list)),
'time': "%.3f" % (stop - start),
})
return result
def __getattr__(self, attr):
if self.__dict__.has_key(attr):
return self.__dict__[attr]
else:
return getattr(self.cursor, attr)

View File

@@ -0,0 +1,42 @@
import datetime
###############################################
# Converters from database (string) to Python #
###############################################
def typecast_date(s):
return s and datetime.date(*map(int, s.split('-'))) # returns None if s is null
def typecast_time(s): # does NOT store time zone information
if not s: return None
bits = s.split(':')
if len(bits[2].split('.')) > 1: # if there is a decimal (e.g. '11:16:36.181305')
return datetime.time(int(bits[0]), int(bits[1]), int(bits[2].split('.')[0]),
int(bits[2].split('.')[1].split('-')[0]))
else: # no decimal was found (e.g. '12:30:00')
return datetime.time(int(bits[0]), int(bits[1]), int(bits[2].split('.')[0]), 0)
def typecast_timestamp(s): # does NOT store time zone information
if not s: return None
d, t = s.split()
dates = d.split('-')
times = t.split(':')
seconds = times[2]
if '.' in seconds: # check whether seconds have a fractional part
seconds, microseconds = seconds.split('.')
else:
microseconds = '0'
return datetime.datetime(int(dates[0]), int(dates[1]), int(dates[2]),
int(times[0]), int(times[1]), int(seconds.split('-')[0]),
int(microseconds.split('-')[0]))
def typecast_boolean(s):
if s is None: return None
return str(s)[0].lower() == 't'
###############################################
# Converters from Python to database (string) #
###############################################
def rev_typecast_boolean(obj, d):
return obj and '1' or '0'

View File

@@ -0,0 +1,466 @@
"Default variable filters"
import template, re, random
###################
# STRINGS #
###################
def addslashes(value, _):
"Adds slashes - useful for passing strings to JavaScript, for example."
return value.replace('"', '\\"').replace("'", "\\'")
def capfirst(value, _):
"Capitalizes the first character of the value"
value = str(value)
return value and value[0].upper() + value[1:]
def fix_ampersands(value, _):
"Replaces ampersands with ``&amp;`` entities"
from django.utils.html import fix_ampersands
return fix_ampersands(value)
def floatformat(text, _):
"""
Displays a floating point number as 34.2 (with one decimal places) - but
only if there's a point to be displayed
"""
if not text:
return ''
if text - int(text) < 0.1:
return int(text)
return "%.1f" % text
def linenumbers(value, _):
"Displays text with line numbers"
from django.utils.html import escape
lines = value.split('\n')
# Find the maximum width of the line count, for use with zero padding string format command
width = str(len(str(len(lines))))
for i, line in enumerate(lines):
lines[i] = ("%0" + width + "d. %s") % (i + 1, escape(line))
return '\n'.join(lines)
def lower(value, _):
"Converts a string into all lowercase"
return value.lower()
def make_list(value, _):
"""
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.
"""
return list(str(value))
def slugify(value, _):
"Converts to lowercase, removes non-alpha chars and converts spaces to hyphens"
value = re.sub('[^\w\s]', '', value).strip().lower()
return re.sub('\s+', '-', value)
def stringformat(value, arg):
"""
Formats the variable according to the argument, a string formatting specifier.
This specifier uses Python string formating syntax, with the exception that
the leading "%" is dropped.
See http://docs.python.org/lib/typesseq-strings.html for documentation
of Python string formatting
"""
try:
return ("%" + arg) % value
except (ValueError, TypeError):
return ""
def title(value, _):
"Converts a string into titlecase"
return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
def truncatewords(value, arg):
"""
Truncates a string after a certain number of words
Argument: Number of words to truncate after
"""
from django.utils.text import truncate_words
try:
length = int(arg)
except ValueError: # invalid literal for int()
return value # Fail silently.
if not isinstance(value, basestring):
value = str(value)
return truncate_words(value, length)
def upper(value, _):
"Converts a string into all uppercase"
return value.upper()
def urlencode(value, _):
"Escapes a value for use in a URL"
import urllib
return urllib.quote(value)
def urlize(value, _):
"Converts URLs in plain text into clickable links"
from django.utils.html import urlize
return urlize(value, nofollow=True)
def urlizetrunc(value, limit):
"""
Converts URLs into clickable links, truncating URLs to the given character limit
Argument: Length to truncate URLs to.
"""
from django.utils.html import urlize
return urlize(value, trim_url_limit=int(limit), nofollow=True)
def wordcount(value, _):
"Returns the number of words"
return len(value.split())
def wordwrap(value, arg):
"""
Wraps words at specified line length
Argument: number of words to wrap the text at.
"""
from django.utils.text import wrap
return wrap(value, int(arg))
def ljust(value, arg):
"""
Left-aligns the value in a field of a given width
Argument: field size
"""
return str(value).ljust(int(arg))
def rjust(value, arg):
"""
Right-aligns the value in a field of a given width
Argument: field size
"""
return str(value).rjust(int(arg))
def center(value, arg):
"Centers the value in a field of a given width"
return str(value).center(int(arg))
def cut(value, arg):
"Removes all values of arg from the given string"
return value.replace(arg, '')
###################
# HTML STRINGS #
###################
def escape(value, _):
"Escapes a string's HTML"
from django.utils.html import escape
return escape(value)
def linebreaks(value, _):
"Converts newlines into <p> and <br />s"
from django.utils.html import linebreaks
return linebreaks(value)
def linebreaksbr(value, _):
"Converts newlines into <br />s"
return value.replace('\n', '<br />')
def removetags(value, tags):
"Removes a space separated list of [X]HTML tags from the output"
tags = [re.escape(tag) for tag in tags.split()]
tags_re = '(%s)' % '|'.join(tags)
starttag_re = re.compile('<%s(>|(\s+[^>]*>))' % tags_re)
endtag_re = re.compile('</%s>' % tags_re)
value = starttag_re.sub('', value)
value = endtag_re.sub('', value)
return value
def striptags(value, _):
"Strips all [X]HTML tags"
from django.utils.html import strip_tags
if not isinstance(value, basestring):
value = str(value)
return strip_tags(value)
###################
# LISTS #
###################
def dictsort(value, arg):
"""
Takes a list of dicts, returns that list sorted by the property given in
the argument.
"""
decorated = [(template.resolve_variable('var.' + arg, {'var' : item}), item) for item in value]
decorated.sort()
return [item[1] for item in decorated]
def dictsortreversed(value, arg):
"""
Takes a list of dicts, returns that list sorted in reverse order by the
property given in the argument.
"""
decorated = [(template.resolve_variable('var.' + arg, {'var' : item}), item) for item in value]
decorated.sort()
decorated.reverse()
return [item[1] for item in decorated]
def first(value, _):
"Returns the first item in a list"
try:
return value[0]
except IndexError:
return ''
def join(value, arg):
"Joins a list with a string, like Python's ``str.join(list)``"
try:
return arg.join(map(str, value))
except AttributeError: # fail silently but nicely
return value
def length(value, _):
"Returns the length of the value - useful for lists"
return len(value)
def length_is(value, arg):
"Returns a boolean of whether the value's length is the argument"
return len(value) == int(arg)
def random(value, _):
"Returns a random item from the list"
return random.choice(value)
def slice_(value, arg):
"""
Returns a slice of the list.
Uses the same syntax as Python's list slicing; see
http://diveintopython.org/native_data_types/lists.html#odbchelper.list.slice
for an introduction.
"""
try:
start, finish = arg.split(':')
except ValueError: # unpack list of wrong size
return value # fail silently but nicely
try:
if start and finish:
return value[int(start):int(finish)]
if start:
return value[int(start):]
if finish:
return value[:int(finish)]
except TypeError:
pass
return value
def unordered_list(value, _):
"""
Recursively takes a self-nested list and returns an HTML unordered list --
WITHOUT opening and closing <ul> tags.
The list is assumed to be in the proper format. For example, if ``var`` contains
``['States', [['Kansas', [['Lawrence', []], ['Topeka', []]]], ['Illinois', []]]]``,
then ``{{ var|unordered_list }}`` would return::
<li>States
<ul>
<li>Kansas
<ul>
<li>Lawrence</li>
<li>Topeka</li>
</ul>
</li>
<li>Illinois</li>
</ul>
</li>
"""
def _helper(value, tabs):
indent = '\t' * tabs
if value[1]:
return '%s<li>%s\n%s<ul>\n%s\n%s</ul>\n%s</li>' % (indent, value[0], indent,
'\n'.join([unordered_list(v, tabs+1) for v in value[1]]), indent, indent)
else:
return '%s<li>%s</li>' % (indent, value[0])
return _helper(value, 1)
###################
# INTEGERS #
###################
def add(value, arg):
"Adds the arg to the value"
return int(value) + int(arg)
def get_digit(value, arg):
"""
Given a whole number, returns the requested digit of it, where 1 is the
right-most digit, 2 is the second-right-most digit, etc. Returns the
original value for invalid input (if input or argument is not an integer,
or if argument is less than 1). Otherwise, output is always an integer.
"""
try:
arg = int(arg)
value = int(value)
except ValueError:
return value # Fail silently for an invalid argument
if arg < 1:
return value
try:
return int(str(value)[-arg])
except IndexError:
return 0
###################
# DATES #
###################
def date(value, arg):
"Formats a date according to the given format"
from django.utils.dateformat import format
return format(value, arg)
def time(value, arg):
"Formats a time according to the given format"
from django.utils.dateformat import time_format
return time_format(value, arg)
def timesince(value, _):
'Formats a date as the time since that date (i.e. "4 days, 6 hours")'
from django.utils.timesince import timesince
return timesince(value)
###################
# LOGIC #
###################
def default(value, arg):
"If value is unavailable, use given default"
return value or arg
def divisibleby(value, arg):
"Returns true if the value is devisible by the argument"
return int(value) % int(arg) == 0
def yesno(value, arg):
"""
Given a string mapping values for true, false and (optionally) None,
returns one of those strings accoding to the value:
========== ====================== ==================================
Value Argument Outputs
========== ====================== ==================================
``True`` ``"yeah,no,maybe"`` ``yeah``
``False`` ``"yeah,no,maybe"`` ``no``
``None`` ``"yeah,no,maybe"`` ``maybe``
``None`` ``"yeah,no"`` ``"no"`` (converts None to False
if no mapping for None is given.
========== ====================== ==================================
"""
bits = arg.split(',')
if len(bits) < 2:
return value # Invalid arg.
try:
yes, no, maybe = bits
except ValueError: # unpack list of wrong size (no "maybe" value provided)
yes, no, maybe = bits, bits[1]
if value is None:
return maybe
if value:
return yes
return no
###################
# MISC #
###################
def filesizeformat(bytes, _):
"""
Format the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB, 102
bytes, etc).
"""
bytes = float(bytes)
if bytes < 1024:
return "%d byte%s" % (bytes, bytes != 1 and 's' or '')
if bytes < 1024 * 1024:
return "%.1f KB" % (bytes / 1024)
if bytes < 1024 * 1024 * 1024:
return "%.1f MB" % (bytes / (1024 * 1024))
return "%.1f GB" % (bytes / (1024 * 1024 * 1024))
def pluralize(value, _):
"Returns 's' if the value is not 1, for '1 vote' vs. '2 votes'"
try:
if int(value) != 1:
return 's'
except ValueError: # invalid string that's not a number
pass
except TypeError: # value isn't a string or a number; maybe it's a list?
try:
if len(value) != 1:
return 's'
except TypeError: # len() of unsized object
pass
return ''
def phone2numeric(value, _):
"Takes a phone number and converts it in to its numerical equivalent"
from django.utils.text import phone2numeric
return phone2numeric(value)
def pprint(value, _):
"A wrapper around pprint.pprint -- for debugging, really"
from pprint import pformat
return pformat(value)
# Syntax: template.register_filter(name of filter, callback, has_argument)
template.register_filter('add', add, True)
template.register_filter('addslashes', addslashes, False)
template.register_filter('capfirst', capfirst, False)
template.register_filter('center', center, True)
template.register_filter('cut', cut, True)
template.register_filter('date', date, True)
template.register_filter('default', default, True)
template.register_filter('dictsort', dictsort, True)
template.register_filter('dictsortreversed', dictsortreversed, True)
template.register_filter('divisibleby', divisibleby, True)
template.register_filter('escape', escape, False)
template.register_filter('filesizeformat', filesizeformat, False)
template.register_filter('first', first, False)
template.register_filter('fix_ampersands', fix_ampersands, False)
template.register_filter('floatformat', floatformat, False)
template.register_filter('get_digit', get_digit, True)
template.register_filter('join', join, True)
template.register_filter('length', length, False)
template.register_filter('length_is', length_is, True)
template.register_filter('linebreaks', linebreaks, False)
template.register_filter('linebreaksbr', linebreaksbr, False)
template.register_filter('linenumbers', linenumbers, False)
template.register_filter('ljust', ljust, True)
template.register_filter('lower', lower, False)
template.register_filter('make_list', make_list, False)
template.register_filter('phone2numeric', phone2numeric, False)
template.register_filter('pluralize', pluralize, False)
template.register_filter('pprint', pprint, False)
template.register_filter('removetags', removetags, True)
template.register_filter('random', random, False)
template.register_filter('rjust', rjust, True)
template.register_filter('slice', slice_, True)
template.register_filter('slugify', slugify, False)
template.register_filter('stringformat', stringformat, True)
template.register_filter('striptags', striptags, False)
template.register_filter('time', time, True)
template.register_filter('timesince', timesince, False)
template.register_filter('title', title, False)
template.register_filter('truncatewords', truncatewords, True)
template.register_filter('unordered_list', unordered_list, False)
template.register_filter('upper', upper, False)
template.register_filter('urlencode', urlencode, False)
template.register_filter('urlize', urlize, False)
template.register_filter('urlizetrunc', urlizetrunc, True)
template.register_filter('wordcount', wordcount, False)
template.register_filter('wordwrap', wordwrap, True)
template.register_filter('yesno', yesno, True)

743
django/core/defaulttags.py Normal file
View File

@@ -0,0 +1,743 @@
"Default tags used by the template system, available to all templates."
import sys
import template
class CommentNode(template.Node):
def render(self, context):
return ''
class CycleNode(template.Node):
def __init__(self, cyclevars):
self.cyclevars = cyclevars
self.cyclevars_len = len(cyclevars)
self.counter = -1
def render(self, context):
self.counter += 1
return self.cyclevars[self.counter % self.cyclevars_len]
class DebugNode(template.Node):
def render(self, context):
from pprint import pformat
output = [pformat(val) for val in context]
output.append('\n\n')
output.append(pformat(sys.modules))
return ''.join(output)
class FilterNode(template.Node):
def __init__(self, filters, nodelist):
self.filters, self.nodelist = filters, nodelist
def render(self, context):
output = self.nodelist.render(context)
# apply filters
for f in self.filters:
output = template.registered_filters[f[0]][0](output, f[1])
return output
class FirstOfNode(template.Node):
def __init__(self, vars):
self.vars = vars
def render(self, context):
for var in self.vars:
value = template.resolve_variable(var, context)
if value:
return str(value)
return ''
class ForNode(template.Node):
def __init__(self, loopvar, sequence, reversed, nodelist_loop):
self.loopvar, self.sequence = loopvar, sequence
self.reversed = reversed
self.nodelist_loop = nodelist_loop
def __repr__(self):
if self.reversed:
reversed = ' reversed'
else:
reversed = ''
return "<For Node: for %s in %s, tail_len: %d%s>" % \
(self.loopvar, self.sequence, len(self.nodelist_loop), reversed)
def __iter__(self):
for node in self.nodelist_loop:
yield node
def get_nodes_by_type(self, nodetype):
nodes = []
if isinstance(self, nodetype):
nodes.append(self)
nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype))
return nodes
def render(self, context):
nodelist = template.NodeList()
if context.has_key('forloop'):
parentloop = context['forloop']
else:
parentloop = {}
context.push()
try:
values = template.resolve_variable_with_filters(self.sequence, context)
except template.VariableDoesNotExist:
values = []
if values is None:
values = []
len_values = len(values)
if self.reversed:
# From http://www.python.org/doc/current/tut/node11.html
def reverse(data):
for index in range(len(data)-1, -1, -1):
yield data[index]
values = reverse(values)
for i, item in enumerate(values):
context['forloop'] = {
# shortcuts for current loop iteration number
'counter0': i,
'counter': i+1,
# boolean values designating first and last times through loop
'first': (i == 0),
'last': (i == len_values - 1),
'parentloop': parentloop,
}
context[self.loopvar] = item
for node in self.nodelist_loop:
nodelist.append(node.render(context))
context.pop()
return nodelist.render(context)
class IfChangedNode(template.Node):
def __init__(self, nodelist):
self.nodelist = nodelist
self._last_seen = None
def render(self, context):
content = self.nodelist.render(context)
if content != self._last_seen:
firstloop = (self._last_seen == None)
self._last_seen = content
context.push()
context['ifchanged'] = {'firstloop': firstloop}
content = self.nodelist.render(context)
context.pop()
return content
else:
return ''
class IfNotEqualNode(template.Node):
def __init__(self, var1, var2, nodelist):
self.var1, self.var2, self.nodelist = var1, var2, nodelist
def __repr__(self):
return "<IfNotEqualNode>"
def render(self, context):
if template.resolve_variable(self.var1, context) != template.resolve_variable(self.var2, context):
return self.nodelist.render(context)
else:
return ''
class IfNode(template.Node):
def __init__(self, boolvars, nodelist_true, nodelist_false):
self.boolvars = boolvars
self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
def __repr__(self):
return "<If node>"
def __iter__(self):
for node in self.nodelist_true:
yield node
for node in self.nodelist_false:
yield node
def get_nodes_by_type(self, nodetype):
nodes = []
if isinstance(self, nodetype):
nodes.append(self)
nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
return nodes
def render(self, context):
for ifnot, boolvar in self.boolvars:
try:
value = template.resolve_variable_with_filters(boolvar, context)
except template.VariableDoesNotExist:
value = None
if (value and not ifnot) or (ifnot and not value):
return self.nodelist_true.render(context)
return self.nodelist_false.render(context)
class RegroupNode(template.Node):
def __init__(self, target_var, expression, var_name):
self.target_var, self.expression = target_var, expression
self.var_name = var_name
def render(self, context):
obj_list = template.resolve_variable_with_filters(self.target_var, context)
if obj_list == '': # target_var wasn't found in context; fail silently
context[self.var_name] = []
return ''
output = [] # list of dictionaries in the format {'grouper': 'key', 'list': [list of contents]}
for obj in obj_list:
grouper = template.resolve_variable_with_filters('var.%s' % self.expression, \
template.Context({'var': obj}))
if output and repr(output[-1]['grouper']) == repr(grouper):
output[-1]['list'].append(obj)
else:
output.append({'grouper': grouper, 'list': [obj]})
context[self.var_name] = output
return ''
def include_is_allowed(filepath):
from django.conf.settings import ALLOWED_INCLUDE_ROOTS
for root in ALLOWED_INCLUDE_ROOTS:
if filepath.startswith(root):
return True
return False
class SsiNode(template.Node):
def __init__(self, filepath, parsed):
self.filepath, self.parsed = filepath, parsed
def render(self, context):
if not include_is_allowed(self.filepath):
return '' # Fail silently for invalid includes.
try:
fp = open(self.filepath, 'r')
output = fp.read()
fp.close()
except IOError:
output = ''
if self.parsed:
try:
t = template.Template(output)
return t.render(context)
except template.TemplateSyntaxError:
return '' # Fail silently for invalid included templates.
return output
class LoadNode(template.Node):
def __init__(self, taglib):
self.taglib = taglib
def load_taglib(taglib):
return __import__("django.templatetags.%s" % taglib.split('.')[-1], '', '', [''])
load_taglib = staticmethod(load_taglib)
def render(self, context):
"Import the relevant module"
try:
self.__class__.load_taglib(self.taglib)
except ImportError:
pass # Fail silently for invalid loads.
return ''
class NowNode(template.Node):
def __init__(self, format_string):
self.format_string = format_string
def render(self, context):
from datetime import datetime
from django.utils.dateformat import DateFormat
df = DateFormat(datetime.now())
return df.format(self.format_string)
class TemplateTagNode(template.Node):
mapping = {'openblock': template.BLOCK_TAG_START,
'closeblock': template.BLOCK_TAG_END,
'openvariable': template.VARIABLE_TAG_START,
'closevariable': template.VARIABLE_TAG_END}
def __init__(self, tagtype):
self.tagtype = tagtype
def render(self, context):
return self.mapping.get(self.tagtype, '')
class WidthRatioNode(template.Node):
def __init__(self, val_var, max_var, max_width):
self.val_var = val_var
self.max_var = max_var
self.max_width = max_width
def render(self, context):
try:
value = template.resolve_variable_with_filters(self.val_var, context)
maxvalue = template.resolve_variable_with_filters(self.max_var, context)
except template.VariableDoesNotExist:
return ''
try:
value = float(value)
maxvalue = float(maxvalue)
ratio = (value / maxvalue) * int(self.max_width)
except (ValueError, ZeroDivisionError):
return ''
return str(int(round(ratio)))
def do_comment(parser, token):
"""
Ignore everything between ``{% comment %}`` and ``{% endcomment %}``
"""
nodelist = parser.parse(('endcomment',))
parser.delete_first_token()
return CommentNode()
def do_cycle(parser, token):
"""
Cycle among the given strings each time this tag is encountered
Within a loop, cycles among the given strings each time through
the loop::
{% for o in some_list %}
<tr class="{% cycle row1,row2 %}">
...
</tr>
{% endfor %}
Outside of a loop, give the values a unique name the first time you call
it, then use that name each sucessive time through::
<tr class="{% cycle row1,row2,row3 as rowcolors %}">...</tr>
<tr class="{% cycle rowcolors %}">...</tr>
<tr class="{% cycle rowcolors %}">...</tr>
You can use any number of values, seperated by commas. Make sure not to
put spaces between the values -- only commas.
"""
# Note: This returns the exact same node on each {% cycle name %} call; that
# is, the node object returned from {% cycle a,b,c as name %} and the one
# returned from {% cycle name %} are the exact same object. This shouldn't
# cause problems (heh), but if it does, now you know.
#
# Ugly hack warning: this stuffs the named template dict into parser so
# that names are only unique within each template (as opposed to using
# a global variable, which would make cycle names have to be unique across
# *all* templates.
args = token.contents.split()
if len(args) < 2:
raise template.TemplateSyntaxError("'Cycle' statement requires at least two arguments")
elif len(args) == 2 and "," in args[1]:
# {% cycle a,b,c %}
cyclevars = [v for v in args[1].split(",") if v] # split and kill blanks
return CycleNode(cyclevars)
# {% cycle name %}
elif len(args) == 2:
name = args[1]
if not parser._namedCycleNodes.has_key(name):
raise template.TemplateSyntaxError("Named cycle '%s' does not exist" % name)
return parser._namedCycleNodes[name]
elif len(args) == 4:
# {% cycle a,b,c as name %}
if args[2] != 'as':
raise template.TemplateSyntaxError("Second 'cycle' argument must be 'as'")
cyclevars = [v for v in args[1].split(",") if v] # split and kill blanks
name = args[3]
node = CycleNode(cyclevars)
if not hasattr(parser, '_namedCycleNodes'):
parser._namedCycleNodes = {}
parser._namedCycleNodes[name] = node
return node
else:
raise template.TemplateSyntaxError("Invalid arguments to 'cycle': %s" % args)
def do_debug(parser, token):
"Print a whole load of debugging information, including the context and imported modules"
return DebugNode()
def do_filter(parser, token):
"""
Filter the contents of the blog through variable filters.
Filters can also be piped through each other, and they can have
arguments -- just like in variable syntax.
Sample usage::
{% filter escape|lower %}
This text will be HTML-escaped, and will appear in lowercase.
{% endfilter %}
"""
_, rest = token.contents.split(None, 1)
_, filters = template.get_filters_from_token('var|%s' % rest)
nodelist = parser.parse(('endfilter',))
parser.delete_first_token()
return FilterNode(filters, nodelist)
def do_firstof(parser, token):
"""
Outputs the first variable passed that is not False.
Outputs nothing if all the passed variables are False.
Sample usage::
{% firstof var1 var2 var3 %}
This is equivalent to::
{% if var1 %}
{{ var1 }}
{% else %}{% if var2 %}
{{ var2 }}
{% else %}{% if var3 %}
{{ var3 }}
{% endif %}{% endif %}{% endif %}
but obviously much cleaner!
"""
bits = token.contents.split()[1:]
if len(bits) < 1:
raise template.TemplateSyntaxError, "'firstof' statement requires at least one argument"
return FirstOfNode(bits)
def do_for(parser, token):
"""
Loop over each item in an array.
For example, to display a list of athletes given ``athlete_list``::
<ul>
{% for athlete in athlete_list %}
<li>{{ athlete.name }}</li>
{% endfor %}
</ul>
You can also loop over a list in reverse by using
``{% for obj in list reversed %}``.
The for loop sets a number of variables available within the loop:
========================== ================================================
Variable Description
========================== ================================================
``forloop.counter`` The current iteration of the loop (1-indexed)
``forloop.counter0`` The current iteration of the loop (0-indexed)
``forloop.first`` True if this is the first time through the loop
``forloop.last`` True if this is the last time through the loop
``forloop.parentloop`` For nested loops, this is the loop "above" the
current one
========================== ================================================
"""
bits = token.contents.split()
if len(bits) == 5 and bits[4] != 'reversed':
raise template.TemplateSyntaxError, "'for' statements with five words should end in 'reversed': %s" % token.contents
if len(bits) not in (4, 5):
raise template.TemplateSyntaxError, "'for' statements should have either four or five words: %s" % token.contents
if bits[2] != 'in':
raise template.TemplateSyntaxError, "'for' statement must contain 'in' as the second word: %s" % token.contents
loopvar = bits[1]
sequence = bits[3]
reversed = (len(bits) == 5)
nodelist_loop = parser.parse(('endfor',))
parser.delete_first_token()
return ForNode(loopvar, sequence, reversed, nodelist_loop)
def do_ifnotequal(parser, token):
"""
Output the contents of the block if the two arguments do not equal each other.
Example::
{% ifnotequal user.id comment.user_id %}
...
{% endifnotequal %}
"""
bits = token.contents.split()
if len(bits) != 3:
raise template.TemplateSyntaxError, "'ifnotequal' takes two arguments"
nodelist = parser.parse(('endifnotequal',))
parser.delete_first_token()
return IfNotEqualNode(bits[1], bits[2], nodelist)
def do_if(parser, token):
"""
The ``{% if %}`` tag evaluates a variable, and if that variable is "true"
(i.e. exists, is not empty, and is not a false boolean value) the contents
of the block are output::
{% if althlete_list %}
Number of athletes: {{ althete_list|count }}
{% else %}
No athletes.
{% endif %}
In the above, if ``athlete_list`` is not empty, the number of athletes will
be displayed by the ``{{ athlete_list|count }}`` variable.
As you can see, the ``if`` tag can take an option ``{% else %} clause that
will be displayed if the test fails.
``if`` tags may use ``or`` or ``not`` to test a number of variables or to
negate a given variable::
{% if not athlete_list %}
There are no athletes.
{% endif %}
{% if athlete_list or coach_list %}
There are some athletes or some coaches.
{% endif %}
{% if not athlete_list or coach_list %}
There are no athletes or there are some coaches (OK, so
writing English translations of boolean logic sounds
stupid; it's not my fault).
{% endif %}
For simplicity, ``if`` tags do not allow ``and`` clauses; use nested ``if``s
instead::
{% if athlete_list %}
{% if coach_list %}
Number of athletes: {{ athlete_list|count }}.
Number of coaches: {{ coach_list|count }}.
{% endif %}
{% endif %}
"""
bits = token.contents.split()
del bits[0]
if not bits:
raise template.TemplateSyntaxError, "'if' statement requires at least one argument"
# bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d']
boolpairs = ' '.join(bits).split(' or ')
boolvars = []
for boolpair in boolpairs:
if ' ' in boolpair:
not_, boolvar = boolpair.split()
if not_ != 'not':
raise template.TemplateSyntaxError, "Expected 'not' in if statement"
boolvars.append((True, boolvar))
else:
boolvars.append((False, boolpair))
nodelist_true = parser.parse(('else', 'endif'))
token = parser.next_token()
if token.contents == 'else':
nodelist_false = parser.parse(('endif',))
parser.delete_first_token()
else:
nodelist_false = template.NodeList()
return IfNode(boolvars, nodelist_true, nodelist_false)
def do_ifchanged(parser, token):
"""
Check if a value has changed from the last iteration of a loop.
The 'ifchanged' block tag is used within a loop. It checks its own rendered
contents against its previous state and only displays its content if the
value has changed::
<h1>Archive for {{ year }}</h1>
{% for date in days %}
{% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %}
<a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a>
{% endfor %}
"""
bits = token.contents.split()
if len(bits) != 1:
raise template.TemplateSyntaxError, "'ifchanged' tag takes no arguments"
nodelist = parser.parse(('endifchanged',))
parser.delete_first_token()
return IfChangedNode(nodelist)
def do_ssi(parser, token):
"""
Output the contents of a given file into the page.
Like a simple "include" tag, the ``ssi`` tag includes the contents
of another file -- which must be specified using an absolute page --
in the current page::
{% ssi /home/html/ljworld.com/includes/right_generic.html %}
If the optional "parsed" parameter is given, the contents of the included
file are evaluated as template code, with the current context::
{% ssi /home/html/ljworld.com/includes/right_generic.html parsed %}
"""
bits = token.contents.split()
parsed = False
if len(bits) not in (2, 3):
raise template.TemplateSyntaxError, "'ssi' tag takes one argument: the path to the file to be included"
if len(bits) == 3:
if bits[2] == 'parsed':
parsed = True
else:
raise template.TemplateSyntaxError, "Second (optional) argument to %s tag must be 'parsed'" % bits[0]
return SsiNode(bits[1], parsed)
def do_load(parser, token):
"""
Load a custom template tag set.
For example, to load the template tags in ``django/templatetags/news/photos.py``::
{% load news.photos %}
"""
bits = token.contents.split()
if len(bits) != 2:
raise template.TemplateSyntaxError, "'load' statement takes one argument"
taglib = bits[1]
# check at compile time that the module can be imported
try:
LoadNode.load_taglib(taglib)
except ImportError:
raise template.TemplateSyntaxError, "'%s' is not a valid tag library" % taglib
return LoadNode(taglib)
def do_now(parser, token):
"""
Display the date, formatted according to the given string.
Uses the same format as PHP's ``date()`` function; see http://php.net/date
for all the possible values.
Sample usage::
It is {% now "jS F Y H:i" %}
"""
bits = token.contents.split('"')
if len(bits) != 3:
raise template.TemplateSyntaxError, "'now' statement takes one argument"
format_string = bits[1]
return NowNode(format_string)
def do_regroup(parser, token):
"""
Regroup a list of alike objects by a common attribute.
This complex tag is best illustrated by use of an example: say that
``people`` is a list of ``Person`` objects that have ``first_name``,
``last_name``, and ``gender`` attributes, and you'd like to display a list
that looks like:
* Male:
* George Bush
* Bill Clinton
* Female:
* Margaret Thatcher
* Colendeeza Rice
* Unknown:
* Janet Reno
The following snippet of template code would accomplish this dubious task::
{% regroup people by gender as grouped %}
<ul>
{% for group in grouped %}
<li>{{ group.grouper }}
<ul>
{% for item in group.list %}
<li>{{ item }}</li>
{% endfor %}
</ul>
{% endfor %}
</ul>
As you can see, ``{% regroup %}`` populates a variable with a list of
objects with ``grouper`` and ``list`` attributes. ``grouper`` contains the
item that was grouped by; ``list`` contains the list of objects that share
that ``grouper``. In this case, ``grouper`` would be ``Male``, ``Female``
and ``Unknown``, and ``list`` is the list of people with those genders.
Note that `{% regroup %}`` does not work when the list to be grouped is not
sorted by the key you are grouping by! This means that if your list of
people was not sorted by gender, you'd need to make sure it is sorted before
using it, i.e.::
{% regroup people|dictsort:"gender" by gender as grouped %}
"""
firstbits = token.contents.split(None, 3)
if len(firstbits) != 4:
raise template.TemplateSyntaxError, "'regroup' tag takes five arguments"
target_var = firstbits[1]
if firstbits[2] != 'by':
raise template.TemplateSyntaxError, "second argument to 'regroup' tag must be 'by'"
lastbits_reversed = firstbits[3][::-1].split(None, 2)
if lastbits_reversed[1][::-1] != 'as':
raise template.TemplateSyntaxError, "next-to-last argument to 'regroup' tag must be 'as'"
expression = lastbits_reversed[2][::-1]
var_name = lastbits_reversed[0][::-1]
return RegroupNode(target_var, expression, var_name)
def do_templatetag(parser, token):
"""
Output one of the bits used to compose template tags.
Since the template system has no concept of "escaping", to display one of
the bits used in template tags, you must use the ``{% templatetag %}`` tag.
The argument tells which template bit to output:
================== =======
Argument Outputs
================== =======
``openblock`` ``{%``
``closeblock`` ``%}``
``openvariable`` ``{{``
``closevariable`` ``}}``
================== =======
"""
bits = token.contents.split()
if len(bits) != 2:
raise template.TemplateSyntaxError, "'templatetag' statement takes one argument"
tag = bits[1]
if not TemplateTagNode.mapping.has_key(tag):
raise template.TemplateSyntaxError, "Invalid templatetag argument: '%s'. Must be one of: %s" % \
(tag, TemplateTagNode.mapping.keys())
return TemplateTagNode(tag)
def do_widthratio(parser, token):
"""
For creating bar charts and such, this tag calculates the ratio of a given
value to a maximum value, and then applies that ratio to a constant.
For example::
<img src='bar.gif' height='10' width='{% widthratio this_value max_value 100 %}' />
Above, if ``this_value`` is 175 and ``max_value`` is 200, the the image in
the above example will be 88 pixels wide (because 175/200 = .875; .875 *
100 = 87.5 which is rounded up to 88).
"""
bits = token.contents.split()
if len(bits) != 4:
raise template.TemplateSyntaxError("widthratio takes three arguments")
tag, this_value_var, max_value_var, max_width = bits
try:
max_width = int(max_width)
except ValueError:
raise template.TemplateSyntaxError("widthratio final argument must be an integer")
return WidthRatioNode(this_value_var, max_value_var, max_width)
template.register_tag('comment', do_comment)
template.register_tag('cycle', do_cycle)
template.register_tag('debug', do_debug)
template.register_tag('filter', do_filter)
template.register_tag('firstof', do_firstof)
template.register_tag('for', do_for)
template.register_tag('ifnotequal', do_ifnotequal)
template.register_tag('if', do_if)
template.register_tag('ifchanged', do_ifchanged)
template.register_tag('regroup', do_regroup)
template.register_tag('ssi', do_ssi)
template.register_tag('load', do_load)
template.register_tag('now', do_now)
template.register_tag('templatetag', do_templatetag)
template.register_tag('widthratio', do_widthratio)

26
django/core/exceptions.py Normal file
View File

@@ -0,0 +1,26 @@
"Global CMS exceptions"
from django.core.template import SilentVariableFailure
class Http404(Exception):
pass
class ObjectDoesNotExist(SilentVariableFailure):
"The requested object does not exist"
pass
class SuspiciousOperation(Exception):
"The user did something suspicious"
pass
class PermissionDenied(Exception):
"The user did not have permission to do that"
pass
class ViewDoesNotExist(Exception):
"The requested view does not exist"
pass
class MiddlewareNotUsed(Exception):
"This middleware is not used in this server configuration"
pass

79
django/core/extensions.py Normal file
View File

@@ -0,0 +1,79 @@
"Specialized Context and ModPythonRequest classes for our CMS. Use these!"
from django.core.template import Context
from django.utils.httpwrappers import ModPythonRequest
from django.conf.settings import DEBUG, INTERNAL_IPS
from pprint import pformat
class CMSContext(Context):
"""This subclass of template.Context automatically populates 'user' and
'messages' in the context. Use this."""
def __init__(self, request, dict={}):
Context.__init__(self, dict)
self['user'] = request.user
self['messages'] = request.user.get_and_delete_messages()
self['perms'] = PermWrapper(request.user)
if DEBUG and request.META['REMOTE_ADDR'] in INTERNAL_IPS:
self['debug'] = True
from django.core import db
self['sql_queries'] = db.db.queries
# PermWrapper and PermLookupDict proxy the permissions system into objects that
# the template system can understand.
class PermLookupDict:
def __init__(self, user, module_name):
self.user, self.module_name = user, module_name
def __repr__(self):
return str(self.user.get_permissions())
def __getitem__(self, perm_name):
return self.user.has_perm("%s.%s" % (self.module_name, perm_name))
def __nonzero__(self):
return self.user.has_module_perms(self.module_name)
class PermWrapper:
def __init__(self, user):
self.user = user
def __getitem__(self, module_name):
return PermLookupDict(self.user, module_name)
class CMSRequest(ModPythonRequest):
"A special version of ModPythonRequest with support for CMS sessions"
def __init__(self, req):
ModPythonRequest.__init__(self, req)
def __repr__(self):
return '<CMSRequest\npath:%s,\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s,\nuser:%s>' % \
(self.path, pformat(self.GET), pformat(self.POST), pformat(self.COOKIES),
pformat(self.META), pformat(self.user))
def _load_session_and_user(self):
from django.models.auth import sessions
from django.conf.settings import AUTH_SESSION_COOKIE
session_cookie = self.COOKIES.get(AUTH_SESSION_COOKIE, '')
try:
self._session = sessions.get_session_from_cookie(session_cookie)
self._user = self._session.get_user()
except sessions.SessionDoesNotExist:
from django.parts.auth import anonymoususers
self._session = None
self._user = anonymoususers.AnonymousUser()
def _get_session(self):
if not hasattr(self, '_session'):
self._load_session_and_user()
return self._session
def _set_session(self, session):
self._session = session
def _get_user(self):
if not hasattr(self, '_user'):
self._load_session_and_user()
return self._user
def _set_user(self, user):
self._user = user
session = property(_get_session, _set_session)
user = property(_get_user, _set_user)

759
django/core/formfields.py Normal file
View File

@@ -0,0 +1,759 @@
from django.core import validators
from django.core.exceptions import PermissionDenied
from django.utils.html import escape
from django.utils.text import fix_microsoft_characters
FORM_FIELD_ID_PREFIX = 'id_'
class EmptyValue(Exception):
"This is raised when empty data is provided"
pass
class Manipulator:
# List of permission strings. User must have at least one to manipulate.
# None means everybody has permission.
required_permission = ''
def __init__(self):
# List of FormField objects
self.fields = []
def __getitem__(self, field_name):
"Looks up field by field name; raises KeyError on failure"
for field in self.fields:
if field.field_name == field_name:
return field
raise KeyError, "Field %s not found" % field_name
def __delitem__(self, field_name):
"Deletes the field with the given field name; raises KeyError on failure"
for i, field in enumerate(self.fields):
if field.field_name == field_name:
del self.fields[i]
return
raise KeyError, "Field %s not found" % field_name
def check_permissions(self, user):
"""Confirms user has required permissions to use this manipulator; raises
PermissionDenied on failure."""
if self.required_permission is None:
return
if user.has_perm(self.required_permission):
return
raise PermissionDenied
def prepare(self, new_data):
"""
Makes any necessary preparations to new_data, in place, before data has
been validated.
"""
for field in self.fields:
field.prepare(new_data)
def get_validation_errors(self, new_data):
"Returns dictionary mapping field_names to error-message lists"
errors = {}
for field in self.fields:
if field.is_required and not new_data.get(field.field_name, False):
errors.setdefault(field.field_name, []).append('This field is required.')
continue
try:
validator_list = field.validator_list
if hasattr(self, 'validate_%s' % field.field_name):
validator_list.append(getattr(self, 'validate_%s' % field.field_name))
for validator in validator_list:
if field.is_required or new_data.get(field.field_name, False) or hasattr(validator, 'always_test'):
try:
if hasattr(field, 'requires_data_list'):
validator(new_data.getlist(field.field_name), new_data)
else:
validator(new_data.get(field.field_name, ''), new_data)
except validators.ValidationError, e:
errors.setdefault(field.field_name, []).extend(e.messages)
# If a CriticalValidationError is raised, ignore any other ValidationErrors
# for this particular field
except validators.CriticalValidationError, e:
errors.setdefault(field.field_name, []).extend(e.messages)
return errors
def save(self, new_data):
"Saves the changes and returns the new object"
# changes is a dictionary-like object keyed by field_name
raise NotImplementedError
def do_html2python(self, new_data):
"""
Convert the data from HTML data types to Python datatypes, changing the
object in place. This happens after validation but before storage. This
must happen after validation because html2python functions aren't
expected to deal with invalid input.
"""
for field in self.fields:
if new_data.has_key(field.field_name):
new_data.setlist(field.field_name,
[field.__class__.html2python(data) for data in new_data.getlist(field.field_name)])
else:
try:
# individual fields deal with None values themselves
new_data.setlist(field.field_name, [field.__class__.html2python(None)])
except EmptyValue:
new_data.setlist(field.field_name, [])
class FormWrapper:
"""
A wrapper linking a Manipulator to the template system.
This allows dictionary-style lookups of formfields. It also handles feeding
prepopulated data and validation error messages to the formfield objects.
"""
def __init__(self, manipulator, data, error_dict):
self.manipulator, self.data = manipulator, data
self.error_dict = error_dict
def __repr__(self):
return repr(self.data)
def __getitem__(self, key):
for field in self.manipulator.fields:
if field.field_name == key:
if hasattr(field, 'requires_data_list') and hasattr(self.data, 'getlist'):
data = self.data.getlist(field.field_name)
else:
data = self.data.get(field.field_name, None)
if data is None:
data = ''
return FormFieldWrapper(field, data, self.error_dict.get(field.field_name, []))
raise KeyError
def has_errors(self):
return self.error_dict != {}
class FormFieldWrapper:
"A bridge between the template system and an individual form field. Used by FormWrapper."
def __init__(self, formfield, data, error_list):
self.formfield, self.data, self.error_list = formfield, data, error_list
self.field_name = self.formfield.field_name # for convenience in templates
def __str__(self):
"Renders the field"
return str(self.formfield.render(self.data))
def __repr__(self):
return '<FormFieldWrapper for "%s">' % self.formfield.field_name
def field_list(self):
"""
Like __str__(), but returns a list. Use this when the field's render()
method returns a list.
"""
return self.formfield.render(self.data)
def errors(self):
return self.error_list
def html_error_list(self):
if self.errors():
return '<ul class="errorlist"><li>%s</li></ul>' % '</li><li>'.join([escape(e) for e in self.errors()])
else:
return ''
class FormFieldCollection(FormFieldWrapper):
"A utility class that gives the template access to a dict of FormFieldWrappers"
def __init__(self, formfield_dict):
self.formfield_dict = formfield_dict
def __str__(self):
return str(self.formfield_dict)
def __getitem__(self, template_key):
"Look up field by template key; raise KeyError on failure"
return self.formfield_dict[template_key]
def __repr__(self):
return "<FormFieldCollection: %s>" % self.formfield_dict
def errors(self):
"Returns list of all errors in this collection's formfields"
errors = []
for field in self.formfield_dict.values():
errors.extend(field.errors())
return errors
class FormField:
"""Abstract class representing a form field.
Classes that extend FormField should define the following attributes:
field_name
The field's name for use by programs.
validator_list
A list of validation tests (callback functions) that the data for
this field must pass in order to be added or changed.
is_required
A Boolean. Is it a required field?
Subclasses should also implement a render(data) method, which is responsible
for rending the form field in XHTML.
"""
def __str__(self):
return self.render('')
def __repr__(self):
return 'FormField "%s"' % self.field_name
def prepare(self, new_data):
"Hook for doing something to new_data (in place) before validation."
pass
def html2python(data):
"Hook for converting an HTML datatype (e.g. 'on' for checkboxes) to a Python type"
return data
html2python = staticmethod(html2python)
def render(self, data):
raise NotImplementedError
####################
# GENERIC WIDGETS #
####################
class TextField(FormField):
def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=[]):
self.field_name = field_name
self.length, self.maxlength = length, maxlength
self.is_required = is_required
self.validator_list = [self.isValidLength, self.hasNoNewlines] + validator_list
def isValidLength(self, data, form):
if data and self.maxlength and len(data) > self.maxlength:
raise validators.ValidationError, "Ensure your text is less than %s characters." % self.maxlength
def hasNoNewlines(self, data, form):
if data and '\n' in data:
raise validators.ValidationError, "Line breaks are not allowed here."
def render(self, data):
if data is None:
data = ''
maxlength = ''
if self.maxlength:
maxlength = 'maxlength="%s" ' % self.maxlength
if isinstance(data, unicode):
data = data.encode('utf-8')
return '<input type="text" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \
(FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__, self.is_required and ' required' or '',
self.field_name, self.length, escape(data), maxlength)
def html2python(data):
if data:
return fix_microsoft_characters(data)
return data
html2python = staticmethod(html2python)
class PasswordField(TextField):
def render(self, data):
# value is always blank because we never want to redisplay it
return '<input type="password" id="%s" class="v%s%s" name="%s" value="" />' % \
(FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__, self.is_required and ' required' or '',
self.field_name)
class LargeTextField(TextField):
def __init__(self, field_name, rows=10, cols=40, is_required=False, validator_list=[], maxlength=None):
self.field_name = field_name
self.rows, self.cols, self.is_required = rows, cols, is_required
self.validator_list = validator_list[:]
if maxlength:
self.validator_list.append(self.isValidLength)
self.maxlength = maxlength
def render(self, data):
if data is None:
data = ''
if isinstance(data, unicode):
data = data.encode('utf-8')
return '<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \
(FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__, self.is_required and ' required' or '',
self.field_name, self.rows, self.cols, escape(data))
class HiddenField(FormField):
def __init__(self, field_name, is_required=False, validator_list=[]):
self.field_name, self.is_required = field_name, is_required
self.validator_list = validator_list[:]
def render(self, data):
return '<input type="hidden" id="%s" name="%s" value="%s" />' % \
(FORM_FIELD_ID_PREFIX + self.field_name, self.field_name, escape(data))
class CheckboxField(FormField):
def __init__(self, field_name, checked_by_default=False):
self.field_name = field_name
self.checked_by_default = checked_by_default
self.is_required, self.validator_list = False, [] # because the validator looks for these
def render(self, data):
checked_html = ''
if data or (data is '' and self.checked_by_default):
checked_html = ' checked="checked"'
return '<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \
(FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__,
self.field_name, checked_html)
def html2python(data):
"Convert value from browser ('on' or '') to a Python boolean"
if data == 'on':
return True
return False
html2python = staticmethod(html2python)
class SelectField(FormField):
def __init__(self, field_name, choices=[], size=1, is_required=False, validator_list=[]):
self.field_name = field_name
# choices is a list of (value, human-readable key) tuples because order matters
self.choices, self.size, self.is_required = choices, size, is_required
self.validator_list = [self.isValidChoice] + validator_list
def render(self, data):
output = ['<select id="%s" class="v%s%s" name="%s" size="%s">' % \
(FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__, self.is_required and ' required' or '',
self.field_name, self.size)]
str_data = str(data) # normalize to string
for value, display_name in self.choices:
selected_html = ''
if str(value) == str_data:
selected_html = ' selected="selected"'
output.append(' <option value="%s"%s>%s</option>' % (escape(value), selected_html, display_name))
output.append(' </select>')
return '\n'.join(output)
def isValidChoice(self, data, form):
str_data = str(data)
str_choices = [str(item[0]) for item in self.choices]
if str_data not in str_choices:
raise validators.ValidationError, "Select a valid choice; '%s' is not in %s." % (str_data, str_choices)
class NullSelectField(SelectField):
"This SelectField converts blank fields to None"
def html2python(data):
if not data:
return None
return data
html2python = staticmethod(html2python)
class RadioSelectField(FormField):
def __init__(self, field_name, choices=[], ul_class='', is_required=False, validator_list=[]):
self.field_name = field_name
# choices is a list of (value, human-readable key) tuples because order matters
self.choices, self.is_required = choices, is_required
self.validator_list = [self.isValidChoice] + validator_list
self.ul_class = ul_class
def render(self, data):
"""
Returns a special object, RadioFieldRenderer, that is iterable *and*
has a default str() rendered output.
This allows for flexible use in templates. You can just use the default
rendering:
{{ field_name }}
...which will output the radio buttons in an unordered list.
Or, you can manually traverse each radio option for special layout:
{% for option in field_name.field_list %}
{{ option.field }} {{ option.label }}<br />
{% endfor %}
"""
class RadioFieldRenderer:
def __init__(self, datalist, ul_class):
self.datalist, self.ul_class = datalist, ul_class
def __str__(self):
"Default str() output for this radio field -- a <ul>"
output = ['<ul%s>' % (self.ul_class and ' class="%s"' % self.ul_class or '')]
output.extend(['<li>%s %s</li>' % (d['field'], d['label']) for d in self.datalist])
output.append('</ul>')
return ''.join(output)
def __iter__(self):
for d in self.datalist:
yield d
def __len__(self):
return len(self.datalist)
datalist = []
str_data = str(data) # normalize to string
for i, (value, display_name) in enumerate(self.choices):
selected_html = ''
if str(value) == str_data:
selected_html = ' checked="checked"'
datalist.append({
'value': value,
'name': display_name,
'field': '<input type="radio" id="%s" name="%s" value="%s"%s/>' % \
(FORM_FIELD_ID_PREFIX + self.field_name + '_' + str(i), self.field_name, value, selected_html),
'label': '<label for="%s">%s</label>' % \
(FORM_FIELD_ID_PREFIX + self.field_name + '_' + str(i), display_name),
})
return RadioFieldRenderer(datalist, self.ul_class)
def isValidChoice(self, data, form):
str_data = str(data)
str_choices = [str(item[0]) for item in self.choices]
if str_data not in str_choices:
raise validators.ValidationError, "Select a valid choice; '%s' is not in %s." % (str_data, str_choices)
class NullBooleanField(SelectField):
"This SelectField provides 'Yes', 'No' and 'Unknown', mapping results to True, False or None"
def __init__(self, field_name, is_required=False, validator_list=[]):
SelectField.__init__(self, field_name, choices=[('1', 'Unknown'), ('2', 'Yes'), ('3', 'No')],
is_required=is_required, validator_list=validator_list)
def render(self, data):
if data is None: data = '1'
elif data == True: data = '2'
elif data == False: data = '3'
return SelectField.render(self, data)
def html2python(data):
return {'1': None, '2': True, '3': False}[data]
html2python = staticmethod(html2python)
class SelectMultipleField(SelectField):
requires_data_list = True
def render(self, data):
output = ['<select id="%s" class="v%s%s" name="%s" size="%s" multiple="multiple">' % \
(FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__, self.is_required and ' required' or '',
self.field_name, self.size)]
str_data_list = map(str, data) # normalize to strings
for value, choice in self.choices:
selected_html = ''
if str(value) in str_data_list:
selected_html = ' selected="selected"'
output.append(' <option value="%s"%s>%s</option>' % (escape(value), selected_html, choice))
output.append(' </select>')
return '\n'.join(output)
def isValidChoice(self, field_data, all_data):
# data is something like ['1', '2', '3']
str_choices = [str(item[0]) for item in self.choices]
for val in map(str, field_data):
if val not in str_choices:
raise validators.ValidationError, "Select a valid choice; '%s' is not in %s." % (val, str_choices)
def html2python(data):
if data is None:
raise EmptyValue
return data
html2python = staticmethod(html2python)
class CheckboxSelectMultipleField(SelectMultipleField):
"""
This has an identical interface to SelectMultipleField, except the rendered
widget is different. Instead of a <select multiple>, this widget outputs a
<ul> of <input type="checkbox">es.
Of course, that results in multiple form elements for the same "single"
field, so this class's prepare() method flattens the split data elements
back into the single list that validators, renderers and save() expect.
"""
requires_data_list = True
def __init__(self, field_name, choices=[], validator_list=[]):
SelectMultipleField.__init__(self, field_name, choices, size=1, is_required=False, validator_list=validator_list)
def prepare(self, new_data):
# new_data has "split" this field into several fields, so flatten it
# back into a single list.
data_list = []
for value, _ in self.choices:
if new_data.get('%s%s' % (self.field_name, value), '') == 'on':
data_list.append(value)
new_data.setlist(self.field_name, data_list)
def render(self, data):
output = ['<ul>']
str_data_list = map(str, data) # normalize to strings
for value, choice in self.choices:
checked_html = ''
if str(value) in str_data_list:
checked_html = ' checked="checked"'
field_name = '%s%s' % (self.field_name, value)
output.append('<li><input type="checkbox" id="%s%s" class="v%s" name="%s"%s /> <label for="%s%s">%s</label></li>' % \
(FORM_FIELD_ID_PREFIX, field_name, self.__class__.__name__, field_name, checked_html,
FORM_FIELD_ID_PREFIX, field_name, choice))
output.append('</ul>')
return '\n'.join(output)
####################
# FILE UPLOADS #
####################
class FileUploadField(FormField):
def __init__(self, field_name, is_required=False, validator_list=[]):
self.field_name, self.is_required = field_name, is_required
self.validator_list = [self.isNonEmptyFile] + validator_list
def isNonEmptyFile(self, field_data, all_data):
if not field_data['content']:
raise validators.CriticalValidationError, "The submitted file is empty."
def render(self, data):
return '<input type="file" id="%s" class="v%s" name="%s" />' % \
(FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__,
self.field_name)
def html2python(data):
if data is None:
raise EmptyValue
return data
html2python = staticmethod(html2python)
class ImageUploadField(FileUploadField):
"A FileUploadField that raises CriticalValidationError if the uploaded file isn't an image."
def __init__(self, *args, **kwargs):
FileUploadField.__init__(self, *args, **kwargs)
self.validator_list.insert(0, self.isValidImage)
def isValidImage(self, field_data, all_data):
try:
validators.isValidImage(field_data, all_data)
except validators.ValidationError, e:
raise validators.CriticalValidationError, e.messages
####################
# INTEGERS/FLOATS #
####################
class IntegerField(TextField):
def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=[]):
validator_list = [self.isInteger] + validator_list
TextField.__init__(self, field_name, length, maxlength, is_required, validator_list)
def isInteger(self, field_data, all_data):
try:
validators.isInteger(field_data, all_data)
except validators.ValidationError, e:
raise validators.CriticalValidationError, e.messages
def html2python(data):
if data == '' or data is None:
return None
return int(data)
html2python = staticmethod(html2python)
class SmallIntegerField(IntegerField):
def __init__(self, field_name, length=5, maxlength=5, is_required=False, validator_list=[]):
validator_list = [self.isSmallInteger] + validator_list
IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list)
def isSmallInteger(self, field_data, all_data):
if not -32768 <= int(field_data) <= 32767:
raise validators.CriticalValidationError, "Enter a whole number between -32,768 and 32,767."
class PositiveIntegerField(IntegerField):
def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=[]):
validator_list = [self.isPositive] + validator_list
IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list)
def isPositive(self, field_data, all_data):
if int(field_data) < 0:
raise validators.CriticalValidationError, "Enter a positive number."
class PositiveSmallIntegerField(IntegerField):
def __init__(self, field_name, length=5, maxlength=None, is_required=False, validator_list=[]):
validator_list = [self.isPositiveSmall] + validator_list
IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list)
def isPositiveSmall(self, field_data, all_data):
if not 0 <= int(field_data) <= 32767:
raise validators.CriticalValidationError, "Enter a whole number between 0 and 32,767."
class FloatField(TextField):
def __init__(self, field_name, max_digits, decimal_places, is_required=False, validator_list=[]):
self.max_digits, self.decimal_places = max_digits, decimal_places
validator_list = [self.isValidFloat] + validator_list
TextField.__init__(self, field_name, max_digits+1, max_digits+1, is_required, validator_list)
def isValidFloat(self, field_data, all_data):
v = validators.IsValidFloat(self.max_digits, self.decimal_places)
try:
v(field_data, all_data)
except validators.ValidationError, e:
raise validators.CriticalValidationError, e.messages
def html2python(data):
if data == '' or data is None:
return None
return float(data)
html2python = staticmethod(html2python)
####################
# DATES AND TIMES #
####################
class DatetimeField(TextField):
"""A FormField that automatically converts its data to a datetime.datetime object.
The data should be in the format YYYY-MM-DD HH:MM:SS."""
def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=[]):
self.field_name = field_name
self.length, self.maxlength = length, maxlength
self.is_required = is_required
self.validator_list = [validators.isValidANSIDatetime] + validator_list
def html2python(data):
"Converts the field into a datetime.datetime object"
import datetime
date, time = data.split()
y, m, d = date.split('-')
timebits = time.split(':')
h, mn = timebits[:2]
if len(timebits) > 2:
s = int(timebits[2])
else:
s = 0
return datetime.datetime(int(y), int(m), int(d), int(h), int(mn), s)
html2python = staticmethod(html2python)
class DateField(TextField):
"""A FormField that automatically converts its data to a datetime.date object.
The data should be in the format YYYY-MM-DD."""
def __init__(self, field_name, is_required=False, validator_list=[]):
validator_list = [self.isValidDate] + validator_list
TextField.__init__(self, field_name, length=10, maxlength=10,
is_required=is_required, validator_list=validator_list)
def isValidDate(self, field_data, all_data):
try:
validators.isValidANSIDate(field_data, all_data)
except validators.ValidationError, e:
raise validators.CriticalValidationError, e.messages
def html2python(data):
"Converts the field into a datetime.date object"
import time, datetime
try:
time_tuple = time.strptime(data, '%Y-%m-%d')
return datetime.date(*time_tuple[0:3])
except (ValueError, TypeError):
return None
html2python = staticmethod(html2python)
class TimeField(TextField):
"""A FormField that automatically converts its data to a datetime.time object.
The data should be in the format HH:MM:SS."""
def __init__(self, field_name, is_required=False, validator_list=[]):
validator_list = [self.isValidTime] + validator_list
TextField.__init__(self, field_name, length=8, maxlength=8,
is_required=is_required, validator_list=validator_list)
def isValidTime(self, field_data, all_data):
try:
validators.isValidANSITime(field_data, all_data)
except validators.ValidationError, e:
raise validators.CriticalValidationError, e.messages
def html2python(data):
"Converts the field into a datetime.time object"
import time, datetime
try:
try:
time_tuple = time.strptime(data, '%H:%M:%S')
except ValueError: # seconds weren't provided
time_tuple = time.strptime(data, '%H:%M')
return datetime.time(*time_tuple[3:6])
except ValueError:
return None
html2python = staticmethod(html2python)
####################
# INTERNET-RELATED #
####################
class EmailField(TextField):
"A convenience FormField for validating e-mail addresses"
def __init__(self, field_name, length=50, is_required=False, validator_list=[]):
validator_list = [self.isValidEmail] + validator_list
TextField.__init__(self, field_name, length, maxlength=75,
is_required=is_required, validator_list=validator_list)
def isValidEmail(self, field_data, all_data):
try:
validators.isValidEmail(field_data, all_data)
except validators.ValidationError, e:
raise validators.CriticalValidationError, e.messages
class URLField(TextField):
"A convenience FormField for validating URLs"
def __init__(self, field_name, length=50, is_required=False, validator_list=[]):
validator_list = [self.isValidURL] + validator_list
TextField.__init__(self, field_name, length=length, maxlength=200,
is_required=is_required, validator_list=validator_list)
def isValidURL(self, field_data, all_data):
try:
validators.isValidURL(field_data, all_data)
except validators.ValidationError, e:
raise validators.CriticalValidationError, e.messages
class IPAddressField(TextField):
def html2python(data):
return data or None
html2python = staticmethod(html2python)
####################
# MISCELLANEOUS #
####################
class PhoneNumberField(TextField):
"A convenience FormField for validating phone numbers (e.g. '630-555-1234')"
def __init__(self, field_name, is_required=False, validator_list=[]):
validator_list = [self.isValidPhone] + validator_list
TextField.__init__(self, field_name, length=12, maxlength=12,
is_required=is_required, validator_list=validator_list)
def isValidPhone(self, field_data, all_data):
try:
validators.isValidPhone(field_data, all_data)
except validators.ValidationError, e:
raise validators.CriticalValidationError, e.messages
class USStateField(TextField):
"A convenience FormField for validating U.S. states (e.g. 'IL')"
def __init__(self, field_name, is_required=False, validator_list=[]):
validator_list = [self.isValidUSState] + validator_list
TextField.__init__(self, field_name, length=2, maxlength=2,
is_required=is_required, validator_list=validator_list)
def isValidUSState(self, field_data, all_data):
try:
validators.isValidUSState(field_data, all_data)
except validators.ValidationError, e:
raise validators.CriticalValidationError, e.messages
def html2python(data):
return data.upper() # Should always be stored in upper case
html2python = staticmethod(html2python)
class CommaSeparatedIntegerField(TextField):
"A convenience FormField for validating comma-separated integer fields"
def __init__(self, field_name, maxlength=None, is_required=False, validator_list=[]):
validator_list = [self.isCommaSeparatedIntegerList] + validator_list
TextField.__init__(self, field_name, length=20, maxlength=maxlength,
is_required=is_required, validator_list=validator_list)
def isCommaSeparatedIntegerList(self, field_data, all_data):
try:
validators.isCommaSeparatedIntegerList(field_data, all_data)
except validators.ValidationError, e:
raise validators.CriticalValidationError, e.messages
class XMLLargeTextField(LargeTextField):
"""
A LargeTextField with an XML validator. The schema_path argument is the
full path to a Relax NG compact schema to validate against.
"""
def __init__(self, field_name, schema_path, **kwargs):
self.schema_path = schema_path
kwargs.setdefault('validator_list', []).insert(0, self.isValidXML)
LargeTextField.__init__(self, field_name, **kwargs)
def isValidXML(self, field_data, all_data):
v = validators.RelaxNGCompact(self.schema_path)
try:
v(field_data, all_data)
except validators.ValidationError, e:
raise validators.CriticalValidationError, e.messages

157
django/core/handler.py Normal file
View File

@@ -0,0 +1,157 @@
import os
from django.utils import httpwrappers
# NOTE: do *not* import settings (or any module which eventually imports
# settings) until after CoreHandler has been called; otherwise os.environ
# won't be set up correctly (with respect to settings).
class ImproperlyConfigured(Exception):
pass
class CoreHandler:
def __init__(self):
self._request_middleware = self._view_middleware = self._response_middleware = None
def __call__(self, req):
# mod_python fakes the environ, and thus doesn't process SetEnv. This fixes that
os.environ.update(req.subprocess_env)
# now that the environ works we can see the correct settings, so imports
# that use settings now can work
from django.conf import settings
from django.core import db
# if we need to set up middleware, now that settings works we can do it now.
if self._request_middleware is None:
self.load_middleware()
try:
request = self.get_request(req)
response = self.get_response(req.uri, request)
finally:
db.db.close()
# Apply response middleware
for middleware_method in self._response_middleware:
response = middleware_method(request, response)
# Convert our custom HttpResponse object back into the mod_python req.
httpwrappers.populate_apache_request(response, req)
return 0 # mod_python.apache.OK
def load_middleware(self):
"""
Populate middleware lists from settings.MIDDLEWARE_CLASSES.
Must be called after the environment is fixed (see __call__).
"""
from django.conf import settings
from django.core import exceptions
self._request_middleware = []
self._view_middleware = []
self._response_middleware = []
for middleware_path in settings.MIDDLEWARE_CLASSES:
dot = middleware_path.rindex('.')
mw_module, mw_classname = middleware_path[:dot], middleware_path[dot+1:]
try:
mod = __import__(mw_module, '', '', [''])
except ImportError, e:
raise ImproperlyConfigured, 'Error importing middleware %s: "%s"' % (mw_module, e)
try:
mw_class = getattr(mod, mw_classname)
except AttributeError:
raise ImproperlyConfigured, 'Middleware module "%s" does not define a "%s" class' % (mw_module, mw_classname)
try:
mw_instance = mw_class()
except exceptions.MiddlewareNotUsed:
continue
if hasattr(mw_instance, 'process_request'):
self._request_middleware.append(mw_instance.process_request)
if hasattr(mw_instance, 'process_view'):
self._view_middleware.append(mw_instance.process_view)
if hasattr(mw_instance, 'process_response'):
self._response_middleware.insert(0, mw_instance.process_response)
def get_request(self, req):
"Returns an HttpRequest object for the given mod_python req object"
from django.core.extensions import CMSRequest
return CMSRequest(req)
def get_response(self, path, request):
"Returns an HttpResponse object for the given HttpRequest"
from django.core import db, exceptions, urlresolvers
from django.core.mail import mail_admins
from django.conf.settings import DEBUG, INTERNAL_IPS, ROOT_URLCONF
# Apply request middleware
for middleware_method in self._request_middleware:
response = middleware_method(request)
if response:
return response
conf_module = __import__(ROOT_URLCONF, '', '', [''])
resolver = urlresolvers.RegexURLResolver(conf_module.urlpatterns)
try:
callback, param_dict = resolver.resolve(path)
# Apply view middleware
for middleware_method in self._view_middleware:
response = middleware_method(request, callback, param_dict)
if response:
return response
return callback(request, **param_dict)
except exceptions.Http404:
if DEBUG:
return self.get_technical_error_response(is404=True)
else:
resolver = urlresolvers.Error404Resolver(conf_module.handler404)
callback, param_dict = resolver.resolve()
return callback(request, **param_dict)
except db.DatabaseError:
db.db.rollback()
if DEBUG:
return self.get_technical_error_response()
else:
subject = 'Database error (%s IP)' % (request.META['REMOTE_ADDR'] in INTERNAL_IPS and 'internal' or 'EXTERNAL')
message = "%s\n\n%s" % (self._get_traceback(), request)
mail_admins(subject, message, fail_silently=True)
return self.get_friendly_error_response(request, conf_module)
except exceptions.PermissionDenied:
return httpwrappers.HttpResponseForbidden('<h1>Permission denied</h1>')
except: # Handle everything else, including SuspiciousOperation, etc.
if DEBUG:
return self.get_technical_error_response()
else:
subject = 'Coding error (%s IP)' % (request.META['REMOTE_ADDR'] in INTERNAL_IPS and 'internal' or 'EXTERNAL')
message = "%s\n\n%s" % (self._get_traceback(), request)
mail_admins(subject, message, fail_silently=True)
return self.get_friendly_error_response(request, conf_module)
def get_friendly_error_response(self, request, conf_module):
"""
Returns an HttpResponse that displays a PUBLIC error message for a
fundamental database or coding error.
"""
from django.core import urlresolvers
resolver = urlresolvers.Error404Resolver(conf_module.handler500)
callback, param_dict = resolver.resolve()
return callback(request, **param_dict)
def get_technical_error_response(self, is404=False):
"""
Returns an HttpResponse that displays a TECHNICAL error message for a
fundamental database or coding error.
"""
error_string = "<pre>There's been an error:\n\n%s</pre>" % self._get_traceback()
responseClass = is404 and httpwrappers.HttpResponseNotFound or httpwrappers.HttpResponseServerError
return responseClass(error_string)
def _get_traceback(self):
"Helper function to return the traceback as a string"
import sys, traceback
return '\n'.join(traceback.format_exception(*sys.exc_info()))
def handler(req):
return CoreHandler()(req)

51
django/core/mail.py Normal file
View File

@@ -0,0 +1,51 @@
"""
Use this for e-mailing
"""
from django.conf.settings import DEFAULT_FROM_EMAIL, EMAIL_HOST
from email.MIMEText import MIMEText
import smtplib
def send_mail(subject, message, from_email, recipient_list, fail_silently=False):
"""
Easy wrapper for sending a single message to a recipient list. All members
of the recipient list will see the other recipients in the 'To' field.
"""
return send_mass_mail([[subject, message, from_email, recipient_list]], fail_silently)
def send_mass_mail(datatuple, fail_silently=False):
"""
Given a datatuple of (subject, message, from_email, recipient_list), sends
each message to each recipient list. Returns the number of e-mails sent.
If from_email is None, the DEFAULT_FROM_EMAIL setting is used.
"""
try:
server = smtplib.SMTP(EMAIL_HOST)
except:
if fail_silently:
return
raise
num_sent = 0
for subject, message, from_email, recipient_list in datatuple:
if not recipient_list:
continue
from_email = from_email or DEFAULT_FROM_EMAIL
msg = MIMEText(message)
msg['Subject'] = subject
msg['From'] = from_email
msg['To'] = ', '.join(recipient_list)
server.sendmail(from_email, recipient_list, msg.as_string())
num_sent += 1
server.quit()
return num_sent
def mail_admins(subject, message, fail_silently=False):
"Sends a message to the admins, as defined by the ADMINS constant in settings.py."
from django.conf.settings import ADMINS, SERVER_EMAIL
send_mail('[CMS] ' + subject, message, SERVER_EMAIL, [a[1] for a in ADMINS], fail_silently)
def mail_managers(subject, message, fail_silently=False):
"Sends a message to the managers, as defined by the MANAGERS constant in settings.py"
from django.conf.settings import MANAGERS, SERVER_EMAIL
send_mail('[CMS] ' + subject, message, SERVER_EMAIL, [a[1] for a in MANAGERS], fail_silently)

2142
django/core/meta.py Normal file

File diff suppressed because it is too large Load Diff

76
django/core/paginator.py Normal file
View File

@@ -0,0 +1,76 @@
from copy import copy
from math import ceil
class InvalidPage(Exception):
pass
class ObjectPaginator:
"""
This class makes pagination easy. Feed it a module (an object with
get_count() and get_list() methods) and a dictionary of arguments
to be passed to those methods, plus the number of objects you want
on each page. Then read the hits and pages properties to see how
many pages it involves. Call get_page with a page number (starting
at 0) to get back a list of objects for that page.
Finally, check if a page number has a next/prev page using
has_next_page(page_number) and has_previous_page(page_number).
"""
def __init__(self, module, args, num_per_page, count_method='get_count', list_method='get_list'):
self.module, self.args = module, args
self.num_per_page = num_per_page
self.count_method, self.list_method = count_method, list_method
self._hits, self._pages = None, None
self._has_next = {} # Caches page_number -> has_next_boolean
def get_page(self, page_number):
try:
page_number = int(page_number)
except ValueError:
raise InvalidPage
if page_number < 0:
raise InvalidPage
args = copy(self.args)
args['offset'] = page_number * self.num_per_page
# Retrieve one extra record, and check for the existence of that extra
# record to determine whether there's a next page.
args['limit'] = self.num_per_page + 1
object_list = getattr(self.module, self.list_method)(**args)
if not object_list:
raise InvalidPage
self._has_next[page_number] = (len(object_list) > self.num_per_page)
return object_list[:self.num_per_page]
def has_next_page(self, page_number):
"Does page $page_number have a 'next' page?"
if not self._has_next.has_key(page_number):
if self._pages is None:
args = copy(self.args)
args['offset'] = (page_number + 1) * self.num_per_page
args['limit'] = 1
object_list = getattr(self.module, self.list_method)(**args)
self._has_next[page_number] = (object_list != [])
else:
self._has_next[page_number] = page_number < (self.pages - 1)
return self._has_next[page_number]
def has_previous_page(self, page_number):
return page_number > 0
def _get_hits(self):
if self._hits is None:
order_args = copy(self.args)
if order_args.has_key('ordering_tuple'):
del order_args['ordering_tuple']
if order_args.has_key('select_related'):
del order_args['select_related']
self._hits = getattr(self.module, self.count_method)(**order_args)
return self._hits
def _get_pages(self):
if self._pages is None:
self._pages = int(ceil(self.hits / float(self.num_per_page)))
return self._pages
hits = property(_get_hits)
pages = property(_get_pages)

136
django/core/rss.py Normal file
View File

@@ -0,0 +1,136 @@
from django.core import template_loader
from django.core.exceptions import ObjectDoesNotExist
from django.core.template import Context
from django.models.core import sites
from django.utils import feedgenerator
from django.conf.settings import LANGUAGE_CODE, SETTINGS_MODULE
class FeedConfiguration:
def __init__(self, slug, title_cb, link_cb, description_cb, get_list_func_cb, get_list_kwargs,
param_func=None, param_kwargs_cb=None, get_list_kwargs_cb=None,
enc_url=None, enc_length=None, enc_mime_type=None):
"""
slug -- Normal Python string. Used to register the feed.
title_cb, link_cb, description_cb -- Functions that take the param
(if applicable) and return a normal Python string.
get_list_func_cb -- Function that takes the param and returns a
function to use in retrieving items.
get_list_kwargs -- Dictionary of kwargs to pass to the function
returned by get_list_func_cb.
param_func -- Function to use in retrieving the param (if applicable).
param_kwargs_cb -- Function that takes the slug and returns a
dictionary of kwargs to use in param_func.
get_list_kwargs_cb -- Function that takes the param and returns a
dictionary to use in addition to get_list_kwargs (if applicable).
The three enc_* parameters are strings representing methods or
attributes to call on a particular item to get its enclosure
information. Each of those methods/attributes should return a normal
Python string.
"""
self.slug = slug
self.title_cb, self.link_cb = title_cb, link_cb
self.description_cb = description_cb
self.get_list_func_cb = get_list_func_cb
self.get_list_kwargs = get_list_kwargs
self.param_func, self.param_kwargs_cb = param_func, param_kwargs_cb
self.get_list_kwargs_cb = get_list_kwargs_cb
assert (None == enc_url == enc_length == enc_mime_type) or (enc_url is not None and enc_length is not None and enc_mime_type is not None)
self.enc_url = enc_url
self.enc_length = enc_length
self.enc_mime_type = enc_mime_type
def get_feed(self, param_slug=None):
"""
Returns a utils.feedgenerator.DefaultRssFeed object, fully populated,
representing this FeedConfiguration.
"""
if param_slug:
try:
param = self.param_func(**self.param_kwargs_cb(param_slug))
except ObjectDoesNotExist:
raise FeedIsNotRegistered
else:
param = None
current_site = sites.get_current()
f = self._get_feed_generator_object(param)
title_template = template_loader.get_template('rss/%s_title' % self.slug)
description_template = template_loader.get_template('rss/%s_description' % self.slug)
kwargs = self.get_list_kwargs.copy()
if param and self.get_list_kwargs_cb:
kwargs.update(self.get_list_kwargs_cb(param))
get_list_func = self.get_list_func_cb(param)
for obj in get_list_func(**kwargs):
link = obj.get_absolute_url()
if not link.startswith('http://'):
link = u'http://%s%s' % (current_site.domain, link)
enc = None
if self.enc_url:
enc_url = getattr(obj, self.enc_url)
enc_length = getattr(obj, self.enc_length)
enc_mime_type = getattr(obj, self.enc_mime_type)
try:
enc_url = enc_url()
except TypeError:
pass
try:
enc_length = enc_length()
except TypeError:
pass
try:
enc_mime_type = enc_mime_type()
except TypeError:
pass
enc = feedgenerator.Enclosure(enc_url.decode('utf-8'),
(enc_length and str(enc_length).decode('utf-8') or ''), enc_mime_type.decode('utf-8'))
f.add_item(
title = title_template.render(Context({'obj': obj, 'site': current_site})).decode('utf-8'),
link = link,
description = description_template.render(Context({'obj': obj, 'site': current_site})).decode('utf-8'),
unique_id=link,
enclosure=enc,
)
return f
def _get_feed_generator_object(self, param):
current_site = sites.get_current()
link = self.link_cb(param).decode()
if not link.startswith('http://'):
link = u'http://%s%s' % (current_site.domain, link)
return feedgenerator.DefaultRssFeed(
title = self.title_cb(param).decode(),
link = link,
description = self.description_cb(param).decode(),
language = LANGUAGE_CODE.decode(),
)
# global dict used by register_feed and get_registered_feed
_registered_feeds = {}
class FeedIsNotRegistered(Exception):
pass
class FeedRequiresParam(Exception):
pass
def register_feed(feed):
_registered_feeds[feed.slug] = feed
def get_registered_feed(slug):
# try to load a RSS settings module so that feeds can be registered
try:
__import__(SETTINGS_MODULE + '_rss', '', '', [''])
except (KeyError, ImportError, ValueError):
pass
try:
return _registered_feeds[slug]
except KeyError:
raise FeedIsNotRegistered

488
django/core/template.py Normal file
View File

@@ -0,0 +1,488 @@
"""
This is the CMS common templating system, shared among all CMS modules that
require control over output.
How it works:
The tokenize() function converts a template string (i.e., a string containing
markup with custom template tags) to tokens, which can be either plain text
(TOKEN_TEXT), variables (TOKEN_VAR) or block statements (TOKEN_BLOCK).
The Parser() class takes a list of tokens in its constructor, and its parse()
method returns a compiled template -- which is, under the hood, a list of
Node objects.
Each Node is responsible for creating some sort of output -- e.g. simple text
(TextNode), variable values in a given context (VariableNode), results of basic
logic (IfNode), results of looping (ForNode), or anything else. The core Node
types are TextNode, VariableNode, IfNode and ForNode, but plugin modules can
define their own custom node types.
Each Node has a render() method, which takes a Context and returns a string of
the rendered node. For example, the render() method of a Variable Node returns
the variable's value as a string. The render() method of an IfNode returns the
rendered output of whatever was inside the loop, recursively.
The Template class is a convenient wrapper that takes care of template
compilation and rendering.
Usage:
The only thing you should ever use directly in this file is the Template class.
Create a compiled template object with a template_string, then call render()
with a context. In the compilation stage, the TemplateSyntaxError exception
will be raised if the template doesn't have proper syntax.
Sample code:
>>> import template
>>> s = '''
... <html>
... {% if test %}
... <h1>{{ varvalue }}</h1>
... {% endif %}
... </html>
... '''
>>> t = template.Template(s)
(t is now a compiled template, and its render() method can be called multiple
times with multiple contexts)
>>> c = template.Context({'test':True, 'varvalue': 'Hello'})
>>> t.render(c)
'\n<html>\n\n <h1>Hello</h1>\n\n</html>\n'
>>> c = template.Context({'test':False, 'varvalue': 'Hello'})
>>> t.render(c)
'\n<html>\n\n</html>\n'
"""
import re
__all__ = ('Template','Context','compile_string')
TOKEN_TEXT = 0
TOKEN_VAR = 1
TOKEN_BLOCK = 2
# template syntax constants
FILTER_SEPARATOR = '|'
FILTER_ARGUMENT_SEPARATOR = ':'
VARIABLE_ATTRIBUTE_SEPARATOR = '.'
BLOCK_TAG_START = '{%'
BLOCK_TAG_END = '%}'
VARIABLE_TAG_START = '{{'
VARIABLE_TAG_END = '}}'
ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.'
# match a variable or block tag and capture the entire tag, including start/end delimiters
tag_re = re.compile('(%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END),
re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END)))
# global dict used by register_tag; maps custom tags to callback functions
registered_tags = {}
# global dict used by register_filter; maps custom filters to callback functions
registered_filters = {}
class TemplateSyntaxError(Exception):
pass
class ContextPopException(Exception):
"pop() has been called more times than push()"
pass
class TemplateDoesNotExist(Exception):
pass
class VariableDoesNotExist(Exception):
pass
class SilentVariableFailure(Exception):
"Any function raising this exception will be ignored by resolve_variable"
pass
class Template:
def __init__(self, template_string):
"Compilation stage"
self.nodelist = compile_string(template_string)
def __iter__(self):
for node in self.nodelist:
for subnode in node:
yield subnode
def render(self, context):
"Display stage -- can be called many times"
return self.nodelist.render(context)
def compile_string(template_string):
"Compiles template_string into NodeList ready for rendering"
tokens = tokenize(template_string)
parser = Parser(tokens)
return parser.parse()
class Context:
"A stack container for variable context"
def __init__(self, dict={}):
self.dicts = [dict]
def __repr__(self):
return repr(self.dicts)
def __iter__(self):
for d in self.dicts:
yield d
def push(self):
self.dicts = [{}] + self.dicts
def pop(self):
if len(self.dicts) == 1:
raise ContextPopException
del self.dicts[0]
def __setitem__(self, key, value):
"Set a variable in the current context"
self.dicts[0][key] = value
def __getitem__(self, key):
"Get a variable's value, starting at the current context and going upward"
for dict in self.dicts:
if dict.has_key(key):
return dict[key]
return ''
def __delitem__(self, key):
"Delete a variable from the current context"
del self.dicts[0][key]
def has_key(self, key):
for dict in self.dicts:
if dict.has_key(key):
return True
return False
def update(self, other_dict):
"Like dict.update(). Pushes an entire dictionary's keys and values onto the context."
self.dicts = [other_dict] + self.dicts
class Token:
def __init__(self, token_type, contents):
"The token_type must be TOKEN_TEXT, TOKEN_VAR or TOKEN_BLOCK"
self.token_type, self.contents = token_type, contents
def __str__(self):
return '<%s token: "%s...">' % (
{TOKEN_TEXT:'Text', TOKEN_VAR:'Var', TOKEN_BLOCK:'Block'}[self.token_type],
self.contents[:20].replace('\n', '')
)
def tokenize(template_string):
"Return a list of tokens from a given template_string"
# remove all empty strings, because the regex has a tendency to add them
bits = filter(None, tag_re.split(template_string))
return map(create_token, bits)
def create_token(token_string):
"Convert the given token string into a new Token object and return it"
if token_string.startswith(VARIABLE_TAG_START):
return Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip())
elif token_string.startswith(BLOCK_TAG_START):
return Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip())
else:
return Token(TOKEN_TEXT, token_string)
class Parser:
def __init__(self, tokens):
self.tokens = tokens
def parse(self, parse_until=[]):
nodelist = NodeList()
while self.tokens:
token = self.next_token()
if token.token_type == TOKEN_TEXT:
nodelist.append(TextNode(token.contents))
elif token.token_type == TOKEN_VAR:
if not token.contents:
raise TemplateSyntaxError, "Empty variable tag"
nodelist.append(VariableNode(token.contents))
elif token.token_type == TOKEN_BLOCK:
if token.contents in parse_until:
# put token back on token list so calling code knows why it terminated
self.prepend_token(token)
return nodelist
try:
command = token.contents.split()[0]
except IndexError:
raise TemplateSyntaxError, "Empty block tag"
try:
# execute callback function for this tag and append resulting node
nodelist.append(registered_tags[command](self, token))
except KeyError:
raise TemplateSyntaxError, "Invalid block tag: '%s'" % command
if parse_until:
raise TemplateSyntaxError, "Unclosed tag(s): '%s'" % ', '.join(parse_until)
return nodelist
def next_token(self):
return self.tokens.pop(0)
def prepend_token(self, token):
self.tokens.insert(0, token)
def delete_first_token(self):
del self.tokens[0]
class FilterParser:
"""Parse a variable token and its optional filters (all as a single string),
and return a list of tuples of the filter name and arguments.
Sample:
>>> token = 'variable|default:"Default value"|date:"Y-m-d"'
>>> p = FilterParser(token)
>>> p.filters
[('default', 'Default value'), ('date', 'Y-m-d')]
>>> p.var
'variable'
This class should never be instantiated outside of the
get_filters_from_token helper function.
"""
def __init__(self, s):
self.s = s
self.i = -1
self.current = ''
self.filters = []
self.current_filter_name = None
self.current_filter_arg = None
# First read the variable part
self.var = self.read_alphanumeric_token()
if not self.var:
raise TemplateSyntaxError, "Could not read variable name: '%s'" % self.s
if self.var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or self.var[0] == '_':
raise TemplateSyntaxError, "Variables and attributes may not begin with underscores: '%s'" % self.var
# Have we reached the end?
if self.current is None:
return
if self.current != FILTER_SEPARATOR:
raise TemplateSyntaxError, "Bad character (expecting '%s') '%s'" % (FILTER_SEPARATOR, self.current)
# We have a filter separator; start reading the filters
self.read_filters()
def next_char(self):
self.i = self.i + 1
try:
self.current = self.s[self.i]
except IndexError:
self.current = None
def read_alphanumeric_token(self):
"""Read a variable name or filter name, which are continuous strings of
alphanumeric characters + the underscore"""
var = ''
while 1:
self.next_char()
if self.current is None:
break
if self.current not in ALLOWED_VARIABLE_CHARS:
break
var += self.current
return var
def read_filters(self):
while 1:
filter_name, arg = self.read_filter()
if not registered_filters.has_key(filter_name):
raise TemplateSyntaxError, "Invalid filter: '%s'" % filter_name
if registered_filters[filter_name][1] == True and arg is None:
raise TemplateSyntaxError, "Filter '%s' requires an argument" % filter_name
if registered_filters[filter_name][1] == False and arg is not None:
raise TemplateSyntaxError, "Filter '%s' should not have an argument" % filter_name
self.filters.append((filter_name, arg))
if self.current is None:
break
def read_filter(self):
self.current_filter_name = self.read_alphanumeric_token()
# Have we reached the end?
if self.current is None:
return (self.current_filter_name, None)
# Does the filter have an argument?
if self.current == FILTER_ARGUMENT_SEPARATOR:
self.current_filter_arg = self.read_arg()
return (self.current_filter_name, self.current_filter_arg)
# Next thing MUST be a pipe
if self.current != FILTER_SEPARATOR:
raise TemplateSyntaxError, "Bad character (expecting '%s') '%s'" % (FILTER_SEPARATOR, self.current)
return (self.current_filter_name, self.current_filter_arg)
def read_arg(self):
# First read a "
self.next_char()
if self.current != '"':
raise TemplateSyntaxError, "Bad character (expecting '\"') '%s'" % self.current
self.escaped = False
arg = ''
while 1:
self.next_char()
if self.current == '"' and not self.escaped:
break
if self.current == '\\' and not self.escaped:
self.escaped = True
continue
if self.current == '\\' and self.escaped:
arg += '\\'
self.escaped = False
continue
if self.current == '"' and self.escaped:
arg += '"'
self.escaped = False
continue
if self.escaped and self.current not in '\\"':
raise TemplateSyntaxError, "Unescaped backslash in '%s'" % self.s
if self.current is None:
raise TemplateSyntaxError, "Unexpected end of argument in '%s'" % self.s
arg += self.current
# self.current must now be '"'
self.next_char()
return arg
def get_filters_from_token(token):
"Convenient wrapper for FilterParser"
p = FilterParser(token)
return (p.var, p.filters)
def resolve_variable(path, context):
"""
Returns the resolved variable, which may contain attribute syntax, within
the given context.
>>> c = {'article': {'section':'News'}}
>>> resolve_variable('article.section', c)
'News'
>>> resolve_variable('article', c)
{'section': 'News'}
>>> class AClass: pass
>>> c = AClass()
>>> c.article = AClass()
>>> c.article.section = 'News'
>>> resolve_variable('article.section', c)
'News'
(The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.')
"""
current = context
bits = path.split(VARIABLE_ATTRIBUTE_SEPARATOR)
while bits:
try: # dictionary lookup
current = current[bits[0]]
except (TypeError, AttributeError, KeyError):
try: # attribute lookup
current = getattr(current, bits[0])
if callable(current):
if getattr(current, 'alters_data', False):
current = ''
else:
try: # method call (assuming no args required)
current = current()
except SilentVariableFailure:
current = ''
except TypeError: # arguments *were* required
current = '' # invalid method call
except (TypeError, AttributeError):
try: # list-index lookup
current = current[int(bits[0])]
except (IndexError, ValueError, KeyError):
raise VariableDoesNotExist, "Failed lookup for key [%s] in %r" % (bits[0], current) # missing attribute
del bits[0]
return current
def resolve_variable_with_filters(var_string, context):
"""
var_string is a full variable expression with optional filters, like:
a.b.c|lower|date:"y/m/d"
This function resolves the variable in the context, applies all filters and
returns the object.
"""
var, filters = get_filters_from_token(var_string)
try:
obj = resolve_variable(var, context)
except VariableDoesNotExist:
obj = ''
for name, arg in filters:
obj = registered_filters[name][0](obj, arg)
return obj
class Node:
def render(self, context):
"Return the node rendered as a string"
pass
def __iter__(self):
yield self
def get_nodes_by_type(self, nodetype):
"Return a list of all nodes (within this node and its nodelist) of the given type"
nodes = []
if isinstance(self, nodetype):
nodes.append(self)
if hasattr(self, 'nodelist'):
nodes.extend(self.nodelist.get_nodes_by_type(nodetype))
return nodes
class NodeList(list):
def render(self, context):
bits = []
for node in self:
if isinstance(node, Node):
bits.append(node.render(context))
else:
bits.append(node)
return ''.join(bits)
def get_nodes_by_type(self, nodetype):
"Return a list of all nodes of the given type"
nodes = []
for node in self:
nodes.extend(node.get_nodes_by_type(nodetype))
return nodes
class TextNode(Node):
def __init__(self, s):
self.s = s
def __repr__(self):
return "<Text Node: '%s'>" % self.s[:25]
def render(self, context):
return self.s
class VariableNode(Node):
def __init__(self, var_string):
self.var_string = var_string
def __repr__(self):
return "<Variable Node: %s>" % self.var_string
def render(self, context):
output = resolve_variable_with_filters(self.var_string, context)
# Check type so that we don't run str() on a Unicode object
if not isinstance(output, basestring):
output = str(output)
elif isinstance(output, unicode):
output = output.encode('utf-8')
return output
def register_tag(token_command, callback_function):
registered_tags[token_command] = callback_function
def unregister_tag(token_command):
del registered_tags[token_command]
def register_filter(filter_name, callback_function, has_arg):
registered_filters[filter_name] = (callback_function, has_arg)
def unregister_filter(filter_name):
del registered_filters[filter_name]
import defaulttags
import defaultfilters

View File

@@ -0,0 +1,18 @@
"Wrapper for loading templates from files"
from django.conf.settings import TEMPLATE_DIRS
from template import TemplateDoesNotExist
import os
TEMPLATE_FILE_EXTENSION = '.html'
def load_template_source(template_name, template_dirs=None):
if not template_dirs:
template_dirs = TEMPLATE_DIRS
tried = []
for template_dir in template_dirs:
filepath = os.path.join(template_dir, template_name) + TEMPLATE_FILE_EXTENSION
try:
return open(filepath).read()
except IOError:
tried.append(filepath)
raise TemplateDoesNotExist, str(tried)

View File

@@ -0,0 +1,142 @@
"Wrapper for loading templates from storage of some sort (e.g. files or db)"
import template
from template_file import load_template_source
class ExtendsError(Exception):
pass
def get_template(template_name):
"""
Returns a compiled template.Template object for the given template name,
handling template inheritance recursively.
"""
return get_template_from_string(load_template_source(template_name))
def get_template_from_string(source):
"""
Returns a compiled template.Template object for the given template code,
handling template inheritance recursively.
"""
return template.Template(source)
def select_template(template_name_list):
"Given a list of template names, returns the first that can be loaded."
for template_name in template_name_list:
try:
return get_template(template_name)
except template.TemplateDoesNotExist:
continue
# If we get here, none of the templates could be loaded
raise template.TemplateDoesNotExist, ', '.join(template_name_list)
class SuperBlock:
"This implements the ability for {{ block.super }} to render the parent block's contents"
def __init__(self, context, nodelist):
self.context, self.nodelist = context, nodelist
def super(self):
if self.nodelist:
return self.nodelist.render(self.context)
else:
return ''
class BlockNode(template.Node):
def __init__(self, name, nodelist):
self.name, self.nodelist = name, nodelist
def __repr__(self):
return "<Block Node: %s. Contents: %r>" % (self.name, self.nodelist)
def render(self, context):
context.push()
nodelist = hasattr(self, 'original_node_list') and self.original_node_list or None
context['block'] = SuperBlock(context, nodelist)
result = self.nodelist.render(context)
context.pop()
return result
class ExtendsNode(template.Node):
def __init__(self, nodelist, parent_name, parent_name_var, template_dirs=None):
self.nodelist = nodelist
self.parent_name, self.parent_name_var = parent_name, parent_name_var
self.template_dirs = template_dirs
def get_parent(self, context):
if self.parent_name_var:
self.parent_name = template.resolve_variable_with_filters(self.parent_name_var, context)
parent = self.parent_name
if not parent:
error_msg = "Invalid template name in 'extends' tag: %r." % parent
if self.parent_name_var:
error_msg += " Got this from the %r variable." % self.parent_name_var
raise template.TemplateSyntaxError, error_msg
try:
return get_template_from_string(load_template_source(parent, self.template_dirs))
except template.TemplateDoesNotExist:
raise template.TemplateSyntaxError, "Template %r cannot be extended, because it doesn't exist" % parent
def render(self, context):
compiled_parent = self.get_parent(context)
parent_is_child = isinstance(compiled_parent.nodelist[0], ExtendsNode)
parent_blocks = dict([(n.name, n) for n in compiled_parent.nodelist.get_nodes_by_type(BlockNode)])
for block_node in self.nodelist.get_nodes_by_type(BlockNode):
# Check for a BlockNode with this node's name, and replace it if found.
try:
parent_block = parent_blocks[block_node.name]
except KeyError:
# This BlockNode wasn't found in the parent template, but the
# parent block might be defined in the parent's *parent*, so we
# add this BlockNode to the parent's ExtendsNode nodelist, so
# it'll be checked when the parent node's render() is called.
if parent_is_child:
compiled_parent.nodelist[0].nodelist.append(block_node)
else:
# Save the original nodelist. It's used by BlockNode.
parent_block.original_node_list = parent_block.nodelist
parent_block.nodelist = block_node.nodelist
return compiled_parent.render(context)
def do_block(parser, token):
"""
Define a block that can be overridden by child templates.
"""
bits = token.contents.split()
if len(bits) != 2:
raise template.TemplateSyntaxError, "'%s' tag takes only one argument" % bits[0]
block_name = bits[1]
# Keep track of the names of BlockNodes found in this template, so we can
# check for duplication.
try:
if block_name in parser.__loaded_blocks:
raise template.TemplateSyntaxError, "'%s' tag with name '%s' appears more than once" % (bits[0], block_name)
parser.__loaded_blocks.append(block_name)
except AttributeError: # parser._loaded_blocks isn't a list yet
parser.__loaded_blocks = [block_name]
nodelist = parser.parse(('endblock',))
parser.delete_first_token()
return BlockNode(block_name, nodelist)
def do_extends(parser, token):
"""
Signal that this template extends a parent template.
This tag may be used in two ways: ``{% extends "base" %}`` (with quotes)
uses the literal value "base" as the name of the parent template to extend,
or ``{% entends variable %}`` uses the value of ``variable`` as the name
of the parent template to extend.
"""
bits = token.contents.split()
if len(bits) != 2:
raise template.TemplateSyntaxError, "'%s' takes one argument" % bits[0]
parent_name, parent_name_var = None, None
if (bits[1].startswith('"') and bits[1].endswith('"')) or (bits[1].startswith("'") and bits[1].endswith("'")):
parent_name = bits[1][1:-1]
else:
parent_name_var = bits[1]
nodelist = parser.parse()
if nodelist.get_nodes_by_type(ExtendsNode):
raise template.TemplateSyntaxError, "'%s' cannot appear more than once in the same template" % bits[0]
return ExtendsNode(nodelist, parent_name, parent_name_var)
template.register_tag('block', do_block)
template.register_tag('extends', do_extends)

View File

@@ -0,0 +1,96 @@
"""
This module converts requested URLs to callback view functions.
RegexURLResolver is the main class here. Its resolve() method takes a URL (as
a string) and returns a tuple in this format:
(view_function, dict_of_view_function_args)
"""
from django.core.exceptions import Http404, ViewDoesNotExist
import re
def get_mod_func(callback):
# Converts 'django.views.news.stories.story_detail' to
# ['django.views.news.stories', 'story_detail']
dot = callback.rindex('.')
return callback[:dot], callback[dot+1:]
class RegexURLPattern:
def __init__(self, regex, callback, default_args=None):
self.regex = re.compile(regex)
# callback is something like 'foo.views.news.stories.story_detail',
# which represents the path to a module and a view function name.
self.callback = callback
self.default_args = default_args or {}
def search(self, path):
match = self.regex.search(path)
if match:
args = dict(match.groupdict(), **self.default_args)
try: # Lazily load self.func.
return self.func, args
except AttributeError:
self.func = self.get_callback()
return self.func, args
def get_callback(self):
mod_name, func_name = get_mod_func(self.callback)
try:
return getattr(__import__(mod_name, '', '', ['']), func_name)
except (ImportError, AttributeError):
raise ViewDoesNotExist, self.callback
class RegexURLMultiplePattern:
def __init__(self, regex, urlconf_module):
self.regex = re.compile(regex)
# urlconf_module is a string representing the module containing urlconfs.
self.urlconf_module = urlconf_module
def search(self, path):
match = self.regex.search(path)
if match:
new_path = path[match.end():]
try: # Lazily load self.url_patterns.
self.url_patterns
except AttributeError:
self.url_patterns = self.get_url_patterns()
for pattern in self.url_patterns:
sub_match = pattern.search(new_path)
if sub_match:
return sub_match
def get_url_patterns(self):
return __import__(self.urlconf_module, '', '', ['']).urlpatterns
class RegexURLResolver:
def __init__(self, url_patterns):
# url_patterns is a list of RegexURLPattern or RegexURLMultiplePattern objects.
self.url_patterns = url_patterns
def resolve(self, app_path):
# app_path is the full requested Web path. This is assumed to have a
# leading slash but doesn't necessarily have a trailing slash.
# Examples:
# "/news/2005/may/"
# "/news/"
# "/polls/latest"
# A home (root) page is represented by "/".
app_path = app_path[1:] # Trim leading slash.
for pattern in self.url_patterns:
match = pattern.search(app_path)
if match:
return match
# None of the regexes matched, so raise a 404.
raise Http404, app_path
class Error404Resolver:
def __init__(self, callback):
self.callback = callback
def resolve(self):
mod_name, func_name = get_mod_func(self.callback)
try:
return getattr(__import__(mod_name, '', '', ['']), func_name), {}
except (ImportError, AttributeError):
raise ViewDoesNotExist, self.callback

420
django/core/validators.py Normal file
View File

@@ -0,0 +1,420 @@
"""
A library of validators that return None and raise ValidationError when the
provided data isn't valid.
Validators may be callable classes, and they may have an 'always_test'
attribute. If an 'always_test' attribute exists (regardless of value), the
validator will *always* be run, regardless of whether its associated
form field is required.
"""
import re
_datere = r'\d{4}-((?:0?[1-9])|(?:1[0-2]))-((?:0?[1-9])|(?:[12][0-9])|(?:3[0-1]))'
_timere = r'(?:[01]?[0-9]|2[0-3]):[0-5][0-9](?::[0-5][0-9])?'
alnum_re = re.compile(r'^\w+$')
alnumurl_re = re.compile(r'^[\w/]+$')
ansi_date_re = re.compile('^%s$' % _datere)
ansi_time_re = re.compile('^%s$' % _timere)
ansi_datetime_re = re.compile('^%s %s$' % (_datere, _timere))
email_re = re.compile(r'^[-\w.+]+@\w[\w.-]+$')
integer_re = re.compile(r'^-?\d+$')
phone_re = re.compile(r'^[A-PR-Y0-9]{3}-[A-PR-Y0-9]{3}-[A-PR-Y0-9]{4}$', re.IGNORECASE)
url_re = re.compile(r'^http://\S+$')
JING = '/usr/bin/jing'
class ValidationError(Exception):
def __init__(self, message):
"ValidationError can be passed a string or a list."
if isinstance(message, list):
self.messages = message
else:
assert isinstance(message, basestring), ("%s should be a string" % repr(message))
self.messages = [message]
def __str__(self):
# This is needed because, without a __str__(), printing an exception
# instance would result in this:
# AttributeError: ValidationError instance has no attribute 'args'
# See http://www.python.org/doc/current/tut/node10.html#handling
return str(self.messages)
class CriticalValidationError(Exception):
def __init__(self, message):
"ValidationError can be passed a string or a list."
if isinstance(message, list):
self.messages = message
else:
assert isinstance(message, basestring), ("'%s' should be a string" % message)
self.messages = [message]
def __str__(self):
return str(self.messages)
def isAlphaNumeric(field_data, all_data):
if not alnum_re.search(field_data):
raise ValidationError, "This value must contain only letters, numbers and underscores."
def isAlphaNumericURL(field_data, all_data):
if not alnumurl_re.search(field_data):
raise ValidationError, "This value must contain only letters, numbers, underscores and slashes."
def isLowerCase(field_data, all_data):
if field_data.lower() != field_data:
raise ValidationError, "Uppercase letters are not allowed here."
def isUpperCase(field_data, all_data):
if field_data.upper() != field_data:
raise ValidationError, "Lowercase letters are not allowed here."
def isCommaSeparatedIntegerList(field_data, all_data):
for supposed_int in field_data.split(','):
try:
int(supposed_int)
except ValueError:
raise ValidationError, "Enter only digits separated by commas."
def isCommaSeparatedEmailList(field_data, all_data):
"""
Checks that field_data is a string of e-mail addresses separated by commas.
Blank field_data values will not throw a validation error, and whitespace
is allowed around the commas.
"""
for supposed_email in field_data.split(','):
try:
isValidEmail(supposed_email.strip(), '')
except ValidationError:
raise ValidationError, "Enter valid e-mail addresses separated by commas."
def isNotEmpty(field_data, all_data):
if field_data.strip() == '':
raise ValidationError, "Empty values are not allowed here."
def isOnlyDigits(field_data, all_data):
if not field_data.isdigit():
raise ValidationError, "Non-numeric characters aren't allowed here."
def isNotOnlyDigits(field_data, all_data):
if field_data.isdigit():
raise ValidationError, "This value can't be comprised solely of digits."
def isInteger(field_data, all_data):
# This differs from isOnlyDigits because this accepts the negative sign
if not integer_re.search(field_data):
raise ValidationError, "Enter a whole number."
def isOnlyLetters(field_data, all_data):
if not field_data.isalpha():
raise ValidationError, "Only alphabetical characters are allowed here."
def isValidANSIDate(field_data, all_data):
if not ansi_date_re.search(field_data):
raise ValidationError, 'Enter a valid date in YYYY-MM-DD format.'
def isValidANSITime(field_data, all_data):
if not ansi_time_re.search(field_data):
raise ValidationError, 'Enter a valid time in HH:MM format.'
def isValidANSIDatetime(field_data, all_data):
if not ansi_datetime_re.search(field_data):
raise ValidationError, 'Enter a valid date/time in YYYY-MM-DD HH:MM format.'
def isValidEmail(field_data, all_data):
if not email_re.search(field_data):
raise ValidationError, 'Enter a valid e-mail address.'
def isValidImage(field_data, all_data):
"""
Checks that the file-upload field data contains a valid image (GIF, JPG,
PNG, possibly others -- whatever the Python Imaging Library supports).
"""
from PIL import Image
from cStringIO import StringIO
try:
Image.open(StringIO(field_data['content']))
except IOError: # Python Imaging Library doesn't recognize it as an image
raise ValidationError, "Upload a valid image. The file you uploaded was either not an image or a corrupted image."
def isValidImageURL(field_data, all_data):
uc = URLMimeTypeCheck(('image/jpeg', 'image/gif', 'image/png'))
try:
uc(field_data, all_data)
except URLMimeTypeCheck.InvalidContentType:
raise ValidationError, "The URL %s does not point to a valid image." % field_data
def isValidPhone(field_data, all_data):
if not phone_re.search(field_data):
raise ValidationError, 'Phone numbers must be in XXX-XXX-XXXX format. "%s" is invalid.' % field_data
def isValidQuicktimeVideoURL(field_data, all_data):
"Checks that the given URL is a video that can be played by QuickTime (qt, mpeg)"
uc = URLMimeTypeCheck(('video/quicktime', 'video/mpeg',))
try:
uc(field_data, all_data)
except URLMimeTypeCheck.InvalidContentType:
raise ValidationError, "The URL %s does not point to a valid QuickTime video." % field_data
def isValidURL(field_data, all_data):
if not url_re.search(field_data):
raise ValidationError, "A valid URL is required."
def isWellFormedXml(field_data, all_data):
from xml.dom.minidom import parseString
try:
parseString(field_data)
except Exception, e: # Naked except because we're not sure what will be thrown
raise ValidationError, "Badly formed XML: %s" % str(e)
def isWellFormedXmlFragment(field_data, all_data):
isWellFormedXml('<root>%s</root>' % field_data, all_data)
def isExistingURL(field_data, all_data):
import urllib2
try:
u = urllib2.urlopen(field_data)
except ValueError:
raise ValidationError, "Invalid URL: %s" % field_data
except: # urllib2.HTTPError, urllib2.URLError, httplib.InvalidURL, etc.
raise ValidationError, "The URL %s is a broken link." % field_data
def isValidUSState(field_data, all_data):
"Checks that the given string is a valid two-letter U.S. state abbreviation"
states = ['AA', 'AE', 'AK', 'AL', 'AP', 'AR', 'AS', 'AZ', 'CA', 'CO', 'CT', 'DC', 'DE', 'FL', 'FM', 'GA', 'GU', 'HI', 'IA', 'ID', 'IL', 'IN', 'KS', 'KY', 'LA', 'MA', 'MD', 'ME', 'MH', 'MI', 'MN', 'MO', 'MP', 'MS', 'MT', 'NC', 'ND', 'NE', 'NH', 'NJ', 'NM', 'NV', 'NY', 'OH', 'OK', 'OR', 'PA', 'PR', 'PW', 'RI', 'SC', 'SD', 'TN', 'TX', 'UT', 'VA', 'VI', 'VT', 'WA', 'WI', 'WV', 'WY']
if field_data.upper() not in states:
raise ValidationError, "Enter a valid U.S. state abbreviation."
def hasNoProfanities(field_data, all_data):
"""
Checks that the given string has no profanities in it. This does a simple
check for whether each profanity exists within the string, so 'fuck' will
catch 'motherfucker' as well. Raises a ValidationError such as:
Watch your mouth! The words "f--k" and "s--t" are not allowed here.
"""
bad_words = ['asshat', 'asshead', 'asshole', 'cunt', 'fuck', 'gook', 'nigger', 'shit'] # all in lower case
field_data = field_data.lower() # normalize
words_seen = [w for w in bad_words if field_data.find(w) > -1]
if words_seen:
from django.utils.text import get_text_list
plural = len(words_seen) > 1
raise ValidationError, "Watch your mouth! The word%s %s %s not allowed here." % \
(plural and 's' or '',
get_text_list(['"%s%s%s"' % (i[0], '-'*(len(i)-2), i[-1]) for i in words_seen], 'and'),
plural and 'are' or 'is')
class AlwaysMatchesOtherField:
def __init__(self, other_field_name, error_message=None):
self.other = other_field_name
self.error_message = error_message or "This field must match the '%s' field." % self.other
self.always_test = True
def __call__(self, field_data, all_data):
if field_data != all_data[self.other]:
raise ValidationError, self.error_message
class RequiredIfOtherFieldGiven:
def __init__(self, other_field_name, error_message=None):
self.other = other_field_name
self.error_message = error_message or "Please enter both fields or leave them both empty."
self.always_test = True
def __call__(self, field_data, all_data):
if all_data[self.other] and not field_data:
raise ValidationError, self.error_message
class RequiredIfOtherFieldNotGiven:
def __init__(self, other_field_name, error_message=None):
self.other = other_field_name
self.error_message = error_message or "Please enter something for at least one field."
self.always_test = True
def __call__(self, field_data, all_data):
if not all_data.get(self.other, False) and not field_data:
raise ValidationError, self.error_message
class RequiredIfOtherFieldsGiven:
"Like RequiredIfOtherFieldGiven, but takes a list of required field names instead of a single field name"
def __init__(self, other_field_names, error_message=None):
self.other = other_field_names
self.error_message = error_message or "Please enter both fields or leave them both empty."
self.always_test = True
def __call__(self, field_data, all_data):
for field in self.other:
if all_data.has_key(field) and all_data[field] and not field_data:
raise ValidationError, self.error_message
class RequiredIfOtherFieldEquals:
def __init__(self, other_field, other_value, error_message=None):
self.other_field = other_field
self.other_value = other_value
self.error_message = error_message or "This field must be given if %s is %s" % (other_field, other_value)
self.always_test = True
def __call__(self, field_data, all_data):
if all_data.has_key(self.other_field) and all_data[self.other_field] == self.other_value and not field_data:
raise ValidationError(self.error_message)
class RequiredIfOtherFieldDoesNotEqual:
def __init__(self, other_field, other_value, error_message=None):
self.other_field = other_field
self.other_value = other_value
self.error_message = error_message or "This field must be given if %s is not %s" % (other_field, other_value)
self.always_test = True
def __call__(self, field_data, all_data):
if all_data.has_key(self.other_field) and all_data[self.other_field] != self.other_value and not field_data:
raise ValidationError(self.error_message)
class IsLessThanOtherField:
def __init__(self, other_field_name, error_message):
self.other, self.error_message = other_field_name, error_message
def __call__(self, field_data, all_data):
if field_data > all_data[self.other]:
raise ValidationError, self.error_message
class UniqueAmongstFieldsWithPrefix:
def __init__(self, field_name, prefix, error_message):
self.field_name, self.prefix = field_name, prefix
self.error_message = error_message or "Duplicate values are not allowed."
def __call__(self, field_data, all_data):
for field_name, value in all_data.items():
if field_name != self.field_name and value == field_data:
raise ValidationError, self.error_message
class IsAPowerOf:
"""
>>> v = IsAPowerOf(2)
>>> v(4, None)
>>> v(8, None)
>>> v(16, None)
>>> v(17, None)
django.core.validators.ValidationError: ['This value must be a power of 2.']
"""
def __init__(self, power_of):
self.power_of = power_of
def __call__(self, field_data, all_data):
from math import log
val = log(int(field_data)) / log(self.power_of)
if val != int(val):
raise ValidationError, "This value must be a power of %s." % self.power_of
class IsValidFloat:
def __init__(self, max_digits, decimal_places):
self.max_digits, self.decimal_places = max_digits, decimal_places
def __call__(self, field_data, all_data):
data = str(field_data)
try:
float(data)
except ValueError:
raise ValidationError, "Please enter a valid decimal number."
if len(data) > (self.max_digits + 1):
raise ValidationError, "Please enter a valid decimal number with at most %s total digit%s." % \
(self.max_digits, self.max_digits > 1 and 's' or '')
if '.' in data and len(data.split('.')[1]) > self.decimal_places:
raise ValidationError, "Please enter a valid decimal number with at most %s decimal place%s." % \
(self.decimal_places, self.decimal_places > 1 and 's' or '')
class HasAllowableSize:
"""
Checks that the file-upload field data is a certain size. min_size and
max_size are measurements in bytes.
"""
def __init__(self, min_size=None, max_size=None, min_error_message=None, max_error_message=None):
self.min_size, self.max_size = min_size, max_size
self.min_error_message = min_error_message or "Make sure your uploaded file is at least %s bytes big." % min_size
self.max_error_message = max_error_message or "Make sure your uploaded file is at most %s bytes big." % min_size
def __call__(self, field_data, all_data):
if self.min_size is not None and len(field_data['content']) < self.min_size:
raise ValidationError, self.min_error_message
if self.max_size is not None and len(field_data['content']) > self.max_size:
raise ValidationError, self.max_error_message
class URLMimeTypeCheck:
"Checks that the provided URL points to a document with a listed mime type"
class CouldNotRetrieve(ValidationError):
pass
class InvalidContentType(ValidationError):
pass
def __init__(self, mime_type_list):
self.mime_type_list = mime_type_list
def __call__(self, field_data, all_data):
import urllib2
try:
isValidURL(field_data, all_data)
except ValidationError:
raise
try:
info = urllib2.urlopen(field_data).info()
except (urllib2.HTTPError, urllib2.URLError):
raise URLMimeTypeCheck.CouldNotRetrieve, "Could not retrieve anything from %s." % field_data
content_type = info['content-type']
if content_type not in self.mime_type_list:
raise URLMimeTypeCheck.InvalidContentType, "The URL %s returned the invalid Content-Type header '%s'." % (field_data, content_type)
class RelaxNGCompact:
"Validate against a Relax NG compact schema"
def __init__(self, schema_path, additional_root_element=None):
self.schema_path = schema_path
self.additional_root_element = additional_root_element
def __call__(self, field_data, all_data):
import os, tempfile
if self.additional_root_element:
field_data = '<%(are)s>%(data)s\n</%(are)s>' % {
'are': self.additional_root_element,
'data': field_data
}
filename = tempfile.mktemp() # Insecure, but nothing else worked
fp = open(filename, 'w')
fp.write(field_data)
fp.close()
if not os.path.exists(JING):
raise Exception, "%s not found!" % JING
p = os.popen('%s -c %s %s' % (JING, self.schema_path, filename))
errors = [line.strip() for line in p.readlines()]
p.close()
os.unlink(filename)
display_errors = []
lines = field_data.split('\n')
for error in errors:
_, line, level, message = error.split(':', 3)
# Scrape the Jing error messages to reword them more nicely.
m = re.search(r'Expected "(.*?)" to terminate element starting on line (\d+)', message)
if m:
display_errors.append('Please close the unclosed %s tag from line %s. (Line starts with "%s".)' % \
(m.group(1).replace('/', ''), m.group(2), lines[int(m.group(2)) - 1][:30]))
continue
if message.strip() == 'text not allowed here':
display_errors.append('Some text starting on line %s is not allowed in that context. (Line starts with "%s".)' % \
(line, lines[int(line) - 1][:30]))
continue
m = re.search(r'\s*attribute "(.*?)" not allowed at this point; ignored', message)
if m:
display_errors.append('"%s" on line %s is an invalid attribute. (Line starts with "%s".)' % \
(m.group(1), line, lines[int(line) - 1][:30]))
continue
m = re.search(r'\s*unknown element "(.*?)"', message)
if m:
display_errors.append('"<%s>" on line %s is an invalid tag. (Line starts with "%s".)' % \
(m.group(1), line, lines[int(line) - 1][:30]))
continue
if message.strip() == 'required attributes missing':
display_errors.append('A tag on line %s is missing one or more required attributes. (Line starts with "%s".)' % \
(line, lines[int(line) - 1][:30]))
continue
m = re.search(r'\s*bad value for attribute "(.*?)"', message)
if m:
display_errors.append('The "%s" attribute on line %s has an invalid value. (Line starts with "%s".)' % \
(m.group(1), line, lines[int(line) - 1][:30]))
continue
# Failing all those checks, use the default error message.
display_error = 'Line %s: %s [%s]' % (line, message, level.strip())
display_errors.append(display_error)
if len(display_errors) > 0:
raise ValidationError, display_errors

22
django/core/xheaders.py Normal file
View File

@@ -0,0 +1,22 @@
"""
Some pages in our CMS are served up with custom HTTP headers containing useful
information about those pages -- namely, the contenttype and object ID.
This module contains utility functions for retrieving and doing interesting
things with these special "X-Headers" (so called because the HTTP spec demands
that custom headers are prefxed with "X-".)
Next time you're at slashdot.org, watch out for X-Fry and X-Bender. :)
"""
def populate_xheaders(request, response, package, python_module_name, object_id):
"""
Adds the "X-Object-Type" and "X-Object-Id" headers to the given
HttpResponse according to the given package, python_module_name and
object_id -- but only if the given HttpRequest object has an IP address
within the INTERNAL_IPS setting.
"""
from django.conf.settings import INTERNAL_IPS
if request.META['REMOTE_ADDR'] in INTERNAL_IPS:
response['X-Object-Type'] = "%s.%s" % (package, python_module_name)
response['X-Object-Id'] = str(object_id)