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:
0
django/core/__init__.py
Normal file
0
django/core/__init__.py
Normal file
255
django/core/cache.py
Normal file
255
django/core/cache.py
Normal 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)
|
||||
28
django/core/db/__init__.py
Normal file
28
django/core/db/__init__.py
Normal 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
|
||||
0
django/core/db/backends/__init__.py
Normal file
0
django/core/db/backends/__init__.py
Normal file
107
django/core/db/backends/mysql.py
Normal file
107
django/core/db/backends/mysql.py
Normal 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',
|
||||
}
|
||||
109
django/core/db/backends/postgresql.py
Normal file
109
django/core/db/backends/postgresql.py
Normal 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
32
django/core/db/base.py
Normal 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)
|
||||
42
django/core/db/typecasts.py
Normal file
42
django/core/db/typecasts.py
Normal 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'
|
||||
466
django/core/defaultfilters.py
Normal file
466
django/core/defaultfilters.py
Normal 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 ``&`` 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
743
django/core/defaulttags.py
Normal 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
26
django/core/exceptions.py
Normal 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
79
django/core/extensions.py
Normal 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
759
django/core/formfields.py
Normal 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
157
django/core/handler.py
Normal 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
51
django/core/mail.py
Normal 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
2142
django/core/meta.py
Normal file
File diff suppressed because it is too large
Load Diff
76
django/core/paginator.py
Normal file
76
django/core/paginator.py
Normal 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
136
django/core/rss.py
Normal 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
488
django/core/template.py
Normal 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
|
||||
18
django/core/template_file.py
Normal file
18
django/core/template_file.py
Normal 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)
|
||||
142
django/core/template_loader.py
Normal file
142
django/core/template_loader.py
Normal 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)
|
||||
96
django/core/urlresolvers.py
Normal file
96
django/core/urlresolvers.py
Normal 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
420
django/core/validators.py
Normal 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
22
django/core/xheaders.py
Normal 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)
|
||||
Reference in New Issue
Block a user