mirror of
https://github.com/django/django.git
synced 2025-10-24 14:16:09 +00:00
MERGED MAGIC-REMOVAL BRANCH TO TRUNK. This change is highly backwards-incompatible. Please read http://code.djangoproject.com/wiki/RemovingTheMagic for upgrade instructions.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@2809 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
16
django/core/cache/backends/db.py
vendored
16
django/core/cache/backends/db.py
vendored
@@ -1,7 +1,7 @@
|
||||
"Database cache backend."
|
||||
|
||||
from django.core.cache.backends.base import BaseCache
|
||||
from django.core.db import db, DatabaseError
|
||||
from django.db import connection, transaction
|
||||
import base64, time
|
||||
from datetime import datetime
|
||||
try:
|
||||
@@ -25,7 +25,7 @@ class CacheClass(BaseCache):
|
||||
self._cull_frequency = 3
|
||||
|
||||
def get(self, key, default=None):
|
||||
cursor = db.cursor()
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("SELECT cache_key, value, expires FROM %s WHERE cache_key = %%s" % self._table, [key])
|
||||
row = cursor.fetchone()
|
||||
if row is None:
|
||||
@@ -33,14 +33,14 @@ class CacheClass(BaseCache):
|
||||
now = datetime.now()
|
||||
if row[2] < now:
|
||||
cursor.execute("DELETE FROM %s WHERE cache_key = %%s" % self._table, [key])
|
||||
db.commit()
|
||||
transaction.commit_unless_managed()
|
||||
return default
|
||||
return pickle.loads(base64.decodestring(row[1]))
|
||||
|
||||
def set(self, key, value, timeout=None):
|
||||
if timeout is None:
|
||||
timeout = self.default_timeout
|
||||
cursor = db.cursor()
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("SELECT COUNT(*) FROM %s" % self._table)
|
||||
num = cursor.fetchone()[0]
|
||||
now = datetime.now().replace(microsecond=0)
|
||||
@@ -58,15 +58,15 @@ class CacheClass(BaseCache):
|
||||
# To be threadsafe, updates/inserts are allowed to fail silently
|
||||
pass
|
||||
else:
|
||||
db.commit()
|
||||
transaction.commit_unless_managed()
|
||||
|
||||
def delete(self, key):
|
||||
cursor = db.cursor()
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("DELETE FROM %s WHERE cache_key = %%s" % self._table, [key])
|
||||
db.commit()
|
||||
transaction.commit_unless_managed()
|
||||
|
||||
def has_key(self, key):
|
||||
cursor = db.cursor()
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("SELECT cache_key FROM %s WHERE cache_key = %%s" % self._table, [key])
|
||||
return cursor.fetchone() is not None
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@ template context. Each function takes the request object as its only parameter
|
||||
and returns a dictionary to add to the context.
|
||||
|
||||
These are referenced from the setting TEMPLATE_CONTEXT_PROCESSORS and used by
|
||||
DjangoContext.
|
||||
RequestContext.
|
||||
"""
|
||||
|
||||
from django.conf.settings import DEBUG, INTERNAL_IPS, LANGUAGES, LANGUAGE_CODE
|
||||
from django.conf import settings
|
||||
|
||||
def auth(request):
|
||||
"""
|
||||
@@ -23,19 +23,19 @@ def auth(request):
|
||||
def debug(request):
|
||||
"Returns context variables helpful for debugging."
|
||||
context_extras = {}
|
||||
if DEBUG and request.META.get('REMOTE_ADDR') in INTERNAL_IPS:
|
||||
if settings.DEBUG and request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS:
|
||||
context_extras['debug'] = True
|
||||
from django.core import db
|
||||
context_extras['sql_queries'] = db.db.queries
|
||||
from django.db import connection
|
||||
context_extras['sql_queries'] = connection.queries
|
||||
return context_extras
|
||||
|
||||
def i18n(request):
|
||||
context_extras = {}
|
||||
context_extras['LANGUAGES'] = LANGUAGES
|
||||
context_extras['LANGUAGES'] = settings.LANGUAGES
|
||||
if hasattr(request, 'LANGUAGE_CODE'):
|
||||
context_extras['LANGUAGE_CODE'] = request.LANGUAGE_CODE
|
||||
else:
|
||||
context_extras['LANGUAGE_CODE'] = LANGUAGE_CODE
|
||||
context_extras['LANGUAGE_CODE'] = settings.LANGUAGE_CODE
|
||||
return context_extras
|
||||
|
||||
def request(request):
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
"""
|
||||
This is the core database connection.
|
||||
|
||||
All Django 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
|
||||
"""
|
||||
|
||||
from django.conf.settings import DATABASE_ENGINE
|
||||
|
||||
try:
|
||||
dbmod = __import__('django.core.db.backends.%s' % DATABASE_ENGINE, '', '', [''])
|
||||
except ImportError, exc:
|
||||
# The database backend wasn't found. Display a helpful error message
|
||||
# listing all possible database backends.
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
import os
|
||||
backend_dir = os.path.join(__path__[0], 'backends')
|
||||
available_backends = [f[:-3] for f in os.listdir(backend_dir) if f.endswith('.py') and not f.startswith('__init__')]
|
||||
available_backends.sort()
|
||||
raise ImproperlyConfigured, "Could not load database backend: %s. Is your DATABASE_ENGINE setting (currently, %r) spelled correctly? Available options are: %s" % \
|
||||
(exc, DATABASE_ENGINE, ", ".join(map(repr, available_backends)))
|
||||
|
||||
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
|
||||
get_date_extract_sql = dbmod.get_date_extract_sql
|
||||
get_date_trunc_sql = dbmod.get_date_trunc_sql
|
||||
get_limit_offset_sql = dbmod.get_limit_offset_sql
|
||||
get_random_function_sql = dbmod.get_random_function_sql
|
||||
get_table_list = dbmod.get_table_list
|
||||
get_table_description = dbmod.get_table_description
|
||||
get_relations = dbmod.get_relations
|
||||
get_indexes = dbmod.get_indexes
|
||||
OPERATOR_MAPPING = dbmod.OPERATOR_MAPPING
|
||||
DATA_TYPES = dbmod.DATA_TYPES
|
||||
DATA_TYPES_REVERSE = dbmod.DATA_TYPES_REVERSE
|
||||
@@ -1,174 +0,0 @@
|
||||
"""
|
||||
ADO MSSQL database backend for Django.
|
||||
|
||||
Requires adodbapi 2.0.1: http://adodbapi.sourceforge.net/
|
||||
"""
|
||||
|
||||
from django.core.db import base
|
||||
from django.core.db.dicthelpers import *
|
||||
import adodbapi as Database
|
||||
import datetime
|
||||
try:
|
||||
import mx
|
||||
except ImportError:
|
||||
mx = None
|
||||
|
||||
DatabaseError = Database.DatabaseError
|
||||
|
||||
# We need to use a special Cursor class because adodbapi expects question-mark
|
||||
# param style, but Django expects "%s". This cursor converts question marks to
|
||||
# format-string style.
|
||||
class Cursor(Database.Cursor):
|
||||
def executeHelper(self, operation, isStoredProcedureCall, parameters=None):
|
||||
if parameters is not None and "%s" in operation:
|
||||
operation = operation.replace("%s", "?")
|
||||
Database.Cursor.executeHelper(self, operation, isStoredProcedureCall, parameters)
|
||||
|
||||
class Connection(Database.Connection):
|
||||
def cursor(self):
|
||||
return Cursor(self)
|
||||
Database.Connection = Connection
|
||||
|
||||
origCVtoP = Database.convertVariantToPython
|
||||
def variantToPython(variant, adType):
|
||||
if type(variant) == bool and adType == 11:
|
||||
return variant # bool not 1/0
|
||||
res = origCVtoP(variant, adType)
|
||||
if mx is not None and type(res) == mx.DateTime.mxDateTime.DateTimeType:
|
||||
# Convert ms.DateTime objects to Python datetime.datetime objects.
|
||||
tv = list(res.tuple()[:7])
|
||||
tv[-2] = int(tv[-2])
|
||||
return datetime.datetime(*tuple(tv))
|
||||
if type(res) == float and str(res)[-2:] == ".0":
|
||||
return int(res) # If float but int, then int.
|
||||
return res
|
||||
Database.convertVariantToPython = variantToPython
|
||||
|
||||
try:
|
||||
# Only exists in python 2.4+
|
||||
from threading import local
|
||||
except ImportError:
|
||||
# Import copy of _thread_local.py from python 2.4
|
||||
from django.utils._threading_local import local
|
||||
|
||||
class DatabaseWrapper(local):
|
||||
def __init__(self):
|
||||
self.connection = None
|
||||
self.queries = []
|
||||
|
||||
def cursor(self):
|
||||
from django.conf.settings import DATABASE_USER, DATABASE_NAME, DATABASE_HOST, DATABASE_PORT, DATABASE_PASSWORD, DEBUG
|
||||
if self.connection is None:
|
||||
if DATABASE_NAME == '' or DATABASE_USER == '':
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
raise ImproperlyConfigured, "You need to specify both DATABASE_NAME and DATABASE_USER in your Django settings file."
|
||||
if not DATABASE_HOST:
|
||||
DATABASE_HOST = "127.0.0.1"
|
||||
# TODO: Handle DATABASE_PORT.
|
||||
conn_string = "PROVIDER=SQLOLEDB;DATA SOURCE=%s;UID=%s;PWD=%s;DATABASE=%s" % (DATABASE_HOST, DATABASE_USER, DATABASE_PASSWORD, DATABASE_NAME)
|
||||
self.connection = Database.connect(conn_string)
|
||||
cursor = self.connection.cursor()
|
||||
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 quote_name(self, name):
|
||||
if name.startswith('[') and name.endswith(']'):
|
||||
return name # Quoting once is enough.
|
||||
return '[%s]' % name
|
||||
|
||||
def get_last_insert_id(cursor, table_name, pk_name):
|
||||
cursor.execute("SELECT %s FROM %s WHERE %s = @@IDENTITY" % (pk_name, table_name, pk_name))
|
||||
return cursor.fetchone()[0]
|
||||
|
||||
def get_date_extract_sql(lookup_type, table_name):
|
||||
# lookup_type is 'year', 'month', 'day'
|
||||
return "DATEPART(%s, %s)" % (lookup_type, table_name)
|
||||
|
||||
def get_date_trunc_sql(lookup_type, field_name):
|
||||
# lookup_type is 'year', 'month', 'day'
|
||||
if lookup_type=='year':
|
||||
return "Convert(datetime, Convert(varchar, DATEPART(year, %s)) + '/01/01')" % field_name
|
||||
if lookup_type=='month':
|
||||
return "Convert(datetime, Convert(varchar, DATEPART(year, %s)) + '/' + Convert(varchar, DATEPART(month, %s)) + '/01')" % (field_name, field_name)
|
||||
if lookup_type=='day':
|
||||
return "Convert(datetime, Convert(varchar(12), %s))" % field_name
|
||||
|
||||
def get_limit_offset_sql(limit, offset=None):
|
||||
# TODO: This is a guess. Make sure this is correct.
|
||||
sql = "LIMIT %s" % limit
|
||||
if offset and offset != 0:
|
||||
sql += " OFFSET %s" % offset
|
||||
return sql
|
||||
|
||||
def get_random_function_sql():
|
||||
return "RAND()"
|
||||
|
||||
def get_table_list(cursor):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_table_description(cursor, table_name):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_relations(cursor, table_name):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_indexes(cursor, table_name):
|
||||
raise NotImplementedError
|
||||
|
||||
OPERATOR_MAPPING = {
|
||||
'exact': '= %s',
|
||||
'iexact': 'LIKE %s',
|
||||
'contains': 'LIKE %s',
|
||||
'icontains': 'LIKE %s',
|
||||
'ne': '!= %s',
|
||||
'gt': '> %s',
|
||||
'gte': '>= %s',
|
||||
'lt': '< %s',
|
||||
'lte': '<= %s',
|
||||
'startswith': 'LIKE %s',
|
||||
'endswith': 'LIKE %s',
|
||||
'istartswith': 'LIKE %s',
|
||||
'iendswith': 'LIKE %s',
|
||||
}
|
||||
|
||||
DATA_TYPES = {
|
||||
'AutoField': 'int IDENTITY (1, 1)',
|
||||
'BooleanField': 'bit',
|
||||
'CharField': 'varchar(%(maxlength)s)',
|
||||
'CommaSeparatedIntegerField': 'varchar(%(maxlength)s)',
|
||||
'DateField': 'smalldatetime',
|
||||
'DateTimeField': 'smalldatetime',
|
||||
'FileField': 'varchar(100)',
|
||||
'FilePathField': 'varchar(100)',
|
||||
'FloatField': 'numeric(%(max_digits)s, %(decimal_places)s)',
|
||||
'ImageField': 'varchar(100)',
|
||||
'IntegerField': 'int',
|
||||
'IPAddressField': 'char(15)',
|
||||
'ManyToManyField': None,
|
||||
'NullBooleanField': 'bit',
|
||||
'OneToOneField': 'int',
|
||||
'PhoneNumberField': 'varchar(20)',
|
||||
'PositiveIntegerField': 'int CONSTRAINT [CK_int_pos_%(column)s] CHECK ([%(column)s] > 0)',
|
||||
'PositiveSmallIntegerField': 'smallint CONSTRAINT [CK_smallint_pos_%(column)s] CHECK ([%(column)s] > 0)',
|
||||
'SlugField': 'varchar(%(maxlength)s)',
|
||||
'SmallIntegerField': 'smallint',
|
||||
'TextField': 'text',
|
||||
'TimeField': 'time',
|
||||
'URLField': 'varchar(200)',
|
||||
'USStateField': 'varchar(2)',
|
||||
}
|
||||
|
||||
DATA_TYPES_REVERSE = {}
|
||||
@@ -1,235 +0,0 @@
|
||||
"""
|
||||
MySQL database backend for Django.
|
||||
|
||||
Requires MySQLdb: http://sourceforge.net/projects/mysql-python
|
||||
"""
|
||||
|
||||
from django.core.db import base, typecasts
|
||||
from django.core.db.dicthelpers import *
|
||||
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,
|
||||
})
|
||||
|
||||
# This is an extra debug layer over MySQL queries, to display warnings.
|
||||
# It's only used when DEBUG=True.
|
||||
class MysqlDebugWrapper:
|
||||
def __init__(self, cursor):
|
||||
self.cursor = cursor
|
||||
|
||||
def execute(self, sql, params=()):
|
||||
try:
|
||||
return self.cursor.execute(sql, params)
|
||||
except Database.Warning, w:
|
||||
self.cursor.execute("SHOW WARNINGS")
|
||||
raise Database.Warning, "%s: %s" % (w, self.cursor.fetchall())
|
||||
|
||||
def executemany(self, sql, param_list):
|
||||
try:
|
||||
return self.cursor.executemany(sql, param_list)
|
||||
except Database.Warning:
|
||||
self.cursor.execute("SHOW WARNINGS")
|
||||
raise Database.Warning, "%s: %s" % (w, self.cursor.fetchall())
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if self.__dict__.has_key(attr):
|
||||
return self.__dict__[attr]
|
||||
else:
|
||||
return getattr(self.cursor, attr)
|
||||
|
||||
try:
|
||||
# Only exists in python 2.4+
|
||||
from threading import local
|
||||
except ImportError:
|
||||
# Import copy of _thread_local.py from python 2.4
|
||||
from django.utils._threading_local import local
|
||||
|
||||
class DatabaseWrapper(local):
|
||||
def __init__(self):
|
||||
self.connection = None
|
||||
self.queries = []
|
||||
|
||||
def _valid_connection(self):
|
||||
if self.connection is not None:
|
||||
try:
|
||||
self.connection.ping()
|
||||
return True
|
||||
except DatabaseError:
|
||||
self.connection.close()
|
||||
self.connection = None
|
||||
return False
|
||||
|
||||
def cursor(self):
|
||||
from django.conf.settings import DATABASE_USER, DATABASE_NAME, DATABASE_HOST, DATABASE_PORT, DATABASE_PASSWORD, DEBUG
|
||||
if not self._valid_connection():
|
||||
kwargs = {
|
||||
'user': DATABASE_USER,
|
||||
'db': DATABASE_NAME,
|
||||
'passwd': DATABASE_PASSWORD,
|
||||
'host': DATABASE_HOST,
|
||||
'conv': django_conversions,
|
||||
}
|
||||
if DATABASE_PORT:
|
||||
kwargs['port'] = DATABASE_PORT
|
||||
self.connection = Database.connect(**kwargs)
|
||||
cursor = self.connection.cursor()
|
||||
if self.connection.get_server_info() >= '4.1':
|
||||
cursor.execute("SET NAMES utf8")
|
||||
if DEBUG:
|
||||
return base.CursorDebugWrapper(MysqlDebugWrapper(cursor), self)
|
||||
return cursor
|
||||
|
||||
def commit(self):
|
||||
self.connection.commit()
|
||||
|
||||
def rollback(self):
|
||||
if self.connection:
|
||||
try:
|
||||
self.connection.rollback()
|
||||
except Database.NotSupportedError:
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
if self.connection is not None:
|
||||
self.connection.close()
|
||||
self.connection = None
|
||||
|
||||
def quote_name(self, name):
|
||||
if name.startswith("`") and name.endswith("`"):
|
||||
return name # Quoting once is enough.
|
||||
return "`%s`" % name
|
||||
|
||||
def get_last_insert_id(cursor, table_name, pk_name):
|
||||
return cursor.lastrowid
|
||||
|
||||
def get_date_extract_sql(lookup_type, table_name):
|
||||
# lookup_type is 'year', 'month', 'day'
|
||||
# http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html
|
||||
return "EXTRACT(%s FROM %s)" % (lookup_type.upper(), table_name)
|
||||
|
||||
def get_date_trunc_sql(lookup_type, field_name):
|
||||
# lookup_type is 'year', 'month', 'day'
|
||||
# http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html
|
||||
# MySQL doesn't support DATE_TRUNC, so we fake it by subtracting intervals.
|
||||
# If you know of a better way to do this, please file a Django ticket.
|
||||
# Note that we can't use DATE_FORMAT directly because that causes the output
|
||||
# to be a string rather than a datetime object, and we need MySQL to return
|
||||
# a date so that it's typecasted properly into a Python datetime object.
|
||||
subtractions = ["interval (DATE_FORMAT(%s, '%%%%s')) second - interval (DATE_FORMAT(%s, '%%%%i')) minute - interval (DATE_FORMAT(%s, '%%%%H')) hour" % (field_name, field_name, field_name)]
|
||||
if lookup_type in ('year', 'month'):
|
||||
subtractions.append(" - interval (DATE_FORMAT(%s, '%%%%e')-1) day" % field_name)
|
||||
if lookup_type == 'year':
|
||||
subtractions.append(" - interval (DATE_FORMAT(%s, '%%%%m')-1) month" % field_name)
|
||||
return "(%s - %s)" % (field_name, ''.join(subtractions))
|
||||
|
||||
def get_limit_offset_sql(limit, offset=None):
|
||||
sql = "LIMIT "
|
||||
if offset and offset != 0:
|
||||
sql += "%s," % offset
|
||||
return sql + str(limit)
|
||||
|
||||
def get_random_function_sql():
|
||||
return "RAND()"
|
||||
|
||||
def get_table_list(cursor):
|
||||
"Returns a list of table names in the current database."
|
||||
cursor.execute("SHOW TABLES")
|
||||
return [row[0] for row in cursor.fetchall()]
|
||||
|
||||
def get_table_description(cursor, table_name):
|
||||
"Returns a description of the table, with the DB-API cursor.description interface."
|
||||
cursor.execute("SELECT * FROM %s LIMIT 1" % DatabaseWrapper().quote_name(table_name))
|
||||
return cursor.description
|
||||
|
||||
def get_relations(cursor, table_name):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_indexes(cursor, table_name):
|
||||
"""
|
||||
Returns a dictionary of fieldname -> infodict for the given table,
|
||||
where each infodict is in the format:
|
||||
{'primary_key': boolean representing whether it's the primary key,
|
||||
'unique': boolean representing whether it's a unique index}
|
||||
"""
|
||||
cursor.execute("SHOW INDEX FROM %s" % DatabaseWrapper().quote_name(table_name))
|
||||
indexes = {}
|
||||
for row in cursor.fetchall():
|
||||
indexes[row[4]] = {'primary_key': (row[2] == 'PRIMARY'), 'unique': not bool(row[1])}
|
||||
return indexes
|
||||
|
||||
OPERATOR_MAPPING = {
|
||||
'exact': '= %s',
|
||||
'iexact': 'LIKE %s',
|
||||
'contains': 'LIKE BINARY %s',
|
||||
'icontains': 'LIKE %s',
|
||||
'ne': '!= %s',
|
||||
'gt': '> %s',
|
||||
'gte': '>= %s',
|
||||
'lt': '< %s',
|
||||
'lte': '<= %s',
|
||||
'startswith': 'LIKE BINARY %s',
|
||||
'endswith': 'LIKE BINARY %s',
|
||||
'istartswith': 'LIKE %s',
|
||||
'iendswith': 'LIKE %s',
|
||||
}
|
||||
|
||||
# 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': 'integer AUTO_INCREMENT',
|
||||
'BooleanField': 'bool',
|
||||
'CharField': 'varchar(%(maxlength)s)',
|
||||
'CommaSeparatedIntegerField': 'varchar(%(maxlength)s)',
|
||||
'DateField': 'date',
|
||||
'DateTimeField': 'datetime',
|
||||
'FileField': 'varchar(100)',
|
||||
'FilePathField': 'varchar(100)',
|
||||
'FloatField': 'numeric(%(max_digits)s, %(decimal_places)s)',
|
||||
'ImageField': 'varchar(100)',
|
||||
'IntegerField': 'integer',
|
||||
'IPAddressField': 'char(15)',
|
||||
'ManyToManyField': None,
|
||||
'NullBooleanField': 'bool',
|
||||
'OneToOneField': 'integer',
|
||||
'PhoneNumberField': 'varchar(20)',
|
||||
'PositiveIntegerField': 'integer UNSIGNED',
|
||||
'PositiveSmallIntegerField': 'smallint UNSIGNED',
|
||||
'SlugField': 'varchar(%(maxlength)s)',
|
||||
'SmallIntegerField': 'smallint',
|
||||
'TextField': 'longtext',
|
||||
'TimeField': 'time',
|
||||
'URLField': 'varchar(200)',
|
||||
'USStateField': 'varchar(2)',
|
||||
}
|
||||
|
||||
DATA_TYPES_REVERSE = {
|
||||
FIELD_TYPE.BLOB: 'TextField',
|
||||
FIELD_TYPE.CHAR: 'CharField',
|
||||
FIELD_TYPE.DECIMAL: 'FloatField',
|
||||
FIELD_TYPE.DATE: 'DateField',
|
||||
FIELD_TYPE.DATETIME: 'DateTimeField',
|
||||
FIELD_TYPE.DOUBLE: 'FloatField',
|
||||
FIELD_TYPE.FLOAT: 'FloatField',
|
||||
FIELD_TYPE.INT24: 'IntegerField',
|
||||
FIELD_TYPE.LONG: 'IntegerField',
|
||||
FIELD_TYPE.LONGLONG: 'IntegerField',
|
||||
FIELD_TYPE.SHORT: 'IntegerField',
|
||||
FIELD_TYPE.STRING: 'TextField',
|
||||
FIELD_TYPE.TIMESTAMP: 'DateTimeField',
|
||||
FIELD_TYPE.TINY_BLOB: 'TextField',
|
||||
FIELD_TYPE.MEDIUM_BLOB: 'TextField',
|
||||
FIELD_TYPE.LONG_BLOB: 'TextField',
|
||||
FIELD_TYPE.VAR_STRING: 'CharField',
|
||||
}
|
||||
@@ -1,238 +0,0 @@
|
||||
"""
|
||||
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
|
||||
|
||||
try:
|
||||
# Only exists in python 2.4+
|
||||
from threading import local
|
||||
except ImportError:
|
||||
# Import copy of _thread_local.py from python 2.4
|
||||
from django.utils._threading_local import local
|
||||
|
||||
class DatabaseWrapper(local):
|
||||
def __init__(self):
|
||||
self.connection = None
|
||||
self.queries = []
|
||||
|
||||
def cursor(self):
|
||||
from django.conf.settings import DATABASE_USER, DATABASE_NAME, DATABASE_HOST, DATABASE_PORT, DATABASE_PASSWORD, DEBUG, TIME_ZONE
|
||||
if self.connection is None:
|
||||
if DATABASE_NAME == '':
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
raise ImproperlyConfigured, "You need to specify DATABASE_NAME in your Django settings file."
|
||||
conn_string = "dbname=%s" % DATABASE_NAME
|
||||
if DATABASE_USER:
|
||||
conn_string = "user=%s %s" % (DATABASE_USER, conn_string)
|
||||
if DATABASE_PASSWORD:
|
||||
conn_string += " password='%s'" % DATABASE_PASSWORD
|
||||
if DATABASE_HOST:
|
||||
conn_string += " host=%s" % DATABASE_HOST
|
||||
if DATABASE_PORT:
|
||||
conn_string += " port=%s" % DATABASE_PORT
|
||||
self.connection = Database.connect(conn_string)
|
||||
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 quote_name(self, name):
|
||||
if name.startswith('"') and name.endswith('"'):
|
||||
return name # Quoting once is enough.
|
||||
return '"%s"' % name
|
||||
|
||||
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]
|
||||
|
||||
def get_date_extract_sql(lookup_type, table_name):
|
||||
# lookup_type is 'year', 'month', 'day'
|
||||
# http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-EXTRACT
|
||||
return "EXTRACT('%s' FROM %s)" % (lookup_type, table_name)
|
||||
|
||||
def get_date_trunc_sql(lookup_type, field_name):
|
||||
# lookup_type is 'year', 'month', 'day'
|
||||
# http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC
|
||||
return "DATE_TRUNC('%s', %s)" % (lookup_type, field_name)
|
||||
|
||||
def get_limit_offset_sql(limit, offset=None):
|
||||
sql = "LIMIT %s" % limit
|
||||
if offset and offset != 0:
|
||||
sql += " OFFSET %s" % offset
|
||||
return sql
|
||||
|
||||
def get_random_function_sql():
|
||||
return "RANDOM()"
|
||||
|
||||
def get_table_list(cursor):
|
||||
"Returns a list of table names in the current database."
|
||||
cursor.execute("""
|
||||
SELECT c.relname
|
||||
FROM pg_catalog.pg_class c
|
||||
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
||||
WHERE c.relkind IN ('r', 'v', '')
|
||||
AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
|
||||
AND pg_catalog.pg_table_is_visible(c.oid)""")
|
||||
return [row[0] for row in cursor.fetchall()]
|
||||
|
||||
def get_table_description(cursor, table_name):
|
||||
"Returns a description of the table, with the DB-API cursor.description interface."
|
||||
cursor.execute("SELECT * FROM %s LIMIT 1" % DatabaseWrapper().quote_name(table_name))
|
||||
return cursor.description
|
||||
|
||||
def get_relations(cursor, table_name):
|
||||
"""
|
||||
Returns a dictionary of {field_index: (field_index_other_table, other_table)}
|
||||
representing all relationships to the given table. Indexes are 0-based.
|
||||
"""
|
||||
cursor.execute("""
|
||||
SELECT con.conkey, con.confkey, c2.relname
|
||||
FROM pg_constraint con, pg_class c1, pg_class c2
|
||||
WHERE c1.oid = con.conrelid
|
||||
AND c2.oid = con.confrelid
|
||||
AND c1.relname = %s
|
||||
AND con.contype = 'f'""", [table_name])
|
||||
relations = {}
|
||||
for row in cursor.fetchall():
|
||||
try:
|
||||
# row[0] and row[1] are like "{2}", so strip the curly braces.
|
||||
relations[int(row[0][1:-1]) - 1] = (int(row[1][1:-1]) - 1, row[2])
|
||||
except ValueError:
|
||||
continue
|
||||
return relations
|
||||
|
||||
def get_indexes(cursor, table_name):
|
||||
"""
|
||||
Returns a dictionary of fieldname -> infodict for the given table,
|
||||
where each infodict is in the format:
|
||||
{'primary_key': boolean representing whether it's the primary key,
|
||||
'unique': boolean representing whether it's a unique index}
|
||||
"""
|
||||
# Get the table description because we only have the column indexes, and we
|
||||
# need the column names.
|
||||
desc = get_table_description(cursor, table_name)
|
||||
# This query retrieves each index on the given table.
|
||||
cursor.execute("""
|
||||
SELECT idx.indkey, idx.indisunique, idx.indisprimary
|
||||
FROM pg_catalog.pg_class c, pg_catalog.pg_class c2,
|
||||
pg_catalog.pg_index idx
|
||||
WHERE c.oid = idx.indrelid
|
||||
AND idx.indexrelid = c2.oid
|
||||
AND c.relname = %s""", [table_name])
|
||||
indexes = {}
|
||||
for row in cursor.fetchall():
|
||||
# row[0] (idx.indkey) is stored in the DB as an array. It comes out as
|
||||
# a string of space-separated integers. This designates the field
|
||||
# indexes (1-based) of the fields that have indexes on the table.
|
||||
# Here, we skip any indexes across multiple fields.
|
||||
if ' ' in row[0]:
|
||||
continue
|
||||
col_name = desc[int(row[0])-1][0]
|
||||
indexes[col_name] = {'primary_key': row[2], 'unique': row[1]}
|
||||
return indexes
|
||||
|
||||
# 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.
|
||||
try:
|
||||
Database.register_type(Database.new_type((1082,), "DATE", typecasts.typecast_date))
|
||||
except AttributeError:
|
||||
raise Exception, "You appear to be using psycopg version 2, which isn't supported yet, because it's still in beta. Use psycopg version 1 instead: http://initd.org/projects/psycopg1"
|
||||
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': '= %s',
|
||||
'iexact': 'ILIKE %s',
|
||||
'contains': 'LIKE %s',
|
||||
'icontains': 'ILIKE %s',
|
||||
'ne': '!= %s',
|
||||
'gt': '> %s',
|
||||
'gte': '>= %s',
|
||||
'lt': '< %s',
|
||||
'lte': '<= %s',
|
||||
'startswith': 'LIKE %s',
|
||||
'endswith': 'LIKE %s',
|
||||
'istartswith': 'ILIKE %s',
|
||||
'iendswith': 'ILIKE %s',
|
||||
}
|
||||
|
||||
# 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',
|
||||
'FileField': 'varchar(100)',
|
||||
'FilePathField': 'varchar(100)',
|
||||
'FloatField': 'numeric(%(max_digits)s, %(decimal_places)s)',
|
||||
'ImageField': 'varchar(100)',
|
||||
'IntegerField': 'integer',
|
||||
'IPAddressField': 'inet',
|
||||
'ManyToManyField': None,
|
||||
'NullBooleanField': 'boolean',
|
||||
'OneToOneField': 'integer',
|
||||
'PhoneNumberField': 'varchar(20)',
|
||||
'PositiveIntegerField': 'integer CHECK ("%(column)s" >= 0)',
|
||||
'PositiveSmallIntegerField': 'smallint CHECK ("%(column)s" >= 0)',
|
||||
'SlugField': 'varchar(%(maxlength)s)',
|
||||
'SmallIntegerField': 'smallint',
|
||||
'TextField': 'text',
|
||||
'TimeField': 'time',
|
||||
'URLField': 'varchar(200)',
|
||||
'USStateField': 'varchar(2)',
|
||||
}
|
||||
|
||||
# Maps type codes to Django Field types.
|
||||
DATA_TYPES_REVERSE = {
|
||||
16: 'BooleanField',
|
||||
21: 'SmallIntegerField',
|
||||
23: 'IntegerField',
|
||||
25: 'TextField',
|
||||
869: 'IPAddressField',
|
||||
1043: 'CharField',
|
||||
1082: 'DateField',
|
||||
1083: 'TimeField',
|
||||
1114: 'DateTimeField',
|
||||
1184: 'DateTimeField',
|
||||
1266: 'TimeField',
|
||||
1700: 'FloatField',
|
||||
}
|
||||
@@ -1,230 +0,0 @@
|
||||
"""
|
||||
SQLite3 backend for django. Requires pysqlite2 (http://pysqlite.org/).
|
||||
"""
|
||||
|
||||
from django.core.db import base, typecasts
|
||||
from django.core.db.dicthelpers import *
|
||||
from pysqlite2 import dbapi2 as Database
|
||||
DatabaseError = Database.DatabaseError
|
||||
|
||||
# Register adaptors ###########################################################
|
||||
|
||||
Database.register_converter("bool", lambda s: str(s) == '1')
|
||||
Database.register_converter("time", typecasts.typecast_time)
|
||||
Database.register_converter("date", typecasts.typecast_date)
|
||||
Database.register_converter("datetime", typecasts.typecast_timestamp)
|
||||
|
||||
# Database wrapper ############################################################
|
||||
|
||||
def utf8rowFactory(cursor, row):
|
||||
def utf8(s):
|
||||
if type(s) == unicode:
|
||||
return s.encode("utf-8")
|
||||
else:
|
||||
return s
|
||||
return [utf8(r) for r in row]
|
||||
|
||||
try:
|
||||
# Only exists in python 2.4+
|
||||
from threading import local
|
||||
except ImportError:
|
||||
# Import copy of _thread_local.py from python 2.4
|
||||
from django.utils._threading_local import local
|
||||
|
||||
class DatabaseWrapper(local):
|
||||
def __init__(self):
|
||||
self.connection = None
|
||||
self.queries = []
|
||||
|
||||
def cursor(self):
|
||||
from django.conf.settings import DATABASE_NAME, DEBUG
|
||||
if self.connection is None:
|
||||
self.connection = Database.connect(DATABASE_NAME, detect_types=Database.PARSE_DECLTYPES)
|
||||
# register extract and date_trun functions
|
||||
self.connection.create_function("django_extract", 2, _sqlite_extract)
|
||||
self.connection.create_function("django_date_trunc", 2, _sqlite_date_trunc)
|
||||
cursor = self.connection.cursor(factory=SQLiteCursorWrapper)
|
||||
cursor.row_factory = utf8rowFactory
|
||||
if DEBUG:
|
||||
return base.CursorDebugWrapper(cursor, self)
|
||||
else:
|
||||
return cursor
|
||||
|
||||
def commit(self):
|
||||
self.connection.commit()
|
||||
|
||||
def rollback(self):
|
||||
if self.connection:
|
||||
self.connection.rollback()
|
||||
|
||||
def close(self):
|
||||
if self.connection is not None:
|
||||
self.connection.close()
|
||||
self.connection = None
|
||||
|
||||
def quote_name(self, name):
|
||||
if name.startswith('"') and name.endswith('"'):
|
||||
return name # Quoting once is enough.
|
||||
return '"%s"' % name
|
||||
|
||||
class SQLiteCursorWrapper(Database.Cursor):
|
||||
"""
|
||||
Django uses "format" style placeholders, but pysqlite2 uses "qmark" style.
|
||||
This fixes it -- but note that if you want to use a literal "%s" in a query,
|
||||
you'll need to use "%%s" (which I belive is true of other wrappers as well).
|
||||
"""
|
||||
|
||||
def execute(self, query, params=[]):
|
||||
query = self.convert_query(query, len(params))
|
||||
return Database.Cursor.execute(self, query, params)
|
||||
|
||||
def executemany(self, query, params=[]):
|
||||
query = self.convert_query(query, len(params[0]))
|
||||
return Database.Cursor.executemany(self, query, params)
|
||||
|
||||
def convert_query(self, query, num_params):
|
||||
# XXX this seems too simple to be correct... is this right?
|
||||
return query % tuple("?" * num_params)
|
||||
|
||||
# Helper functions ############################################################
|
||||
|
||||
def get_last_insert_id(cursor, table_name, pk_name):
|
||||
return cursor.lastrowid
|
||||
|
||||
def get_date_extract_sql(lookup_type, table_name):
|
||||
# lookup_type is 'year', 'month', 'day'
|
||||
# sqlite doesn't support extract, so we fake it with the user-defined
|
||||
# function _sqlite_extract that's registered in connect(), above.
|
||||
return 'django_extract("%s", %s)' % (lookup_type.lower(), table_name)
|
||||
|
||||
def _sqlite_extract(lookup_type, dt):
|
||||
try:
|
||||
dt = typecasts.typecast_timestamp(dt)
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
return str(getattr(dt, lookup_type))
|
||||
|
||||
def get_date_trunc_sql(lookup_type, field_name):
|
||||
# lookup_type is 'year', 'month', 'day'
|
||||
# sqlite doesn't support DATE_TRUNC, so we fake it as above.
|
||||
return 'django_date_trunc("%s", %s)' % (lookup_type.lower(), field_name)
|
||||
|
||||
def get_limit_offset_sql(limit, offset=None):
|
||||
sql = "LIMIT %s" % limit
|
||||
if offset and offset != 0:
|
||||
sql += " OFFSET %s" % offset
|
||||
return sql
|
||||
|
||||
def get_random_function_sql():
|
||||
return "RANDOM()"
|
||||
|
||||
def _sqlite_date_trunc(lookup_type, dt):
|
||||
try:
|
||||
dt = typecasts.typecast_timestamp(dt)
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
if lookup_type == 'year':
|
||||
return "%i-01-01 00:00:00" % dt.year
|
||||
elif lookup_type == 'month':
|
||||
return "%i-%02i-01 00:00:00" % (dt.year, dt.month)
|
||||
elif lookup_type == 'day':
|
||||
return "%i-%02i-%02i 00:00:00" % (dt.year, dt.month, dt.day)
|
||||
|
||||
def get_table_list(cursor):
|
||||
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")
|
||||
return [row[0] for row in cursor.fetchall()]
|
||||
|
||||
def get_table_description(cursor, table_name):
|
||||
cursor.execute("PRAGMA table_info(%s)" % DatabaseWrapper().quote_name(table_name))
|
||||
return [(row[1], row[2], None, None) for row in cursor.fetchall()]
|
||||
|
||||
def get_relations(cursor, table_name):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_indexes(cursor, table_name):
|
||||
raise NotImplementedError
|
||||
|
||||
# Operators and fields ########################################################
|
||||
|
||||
# SQLite requires LIKE statements to include an ESCAPE clause if the value
|
||||
# being escaped has a percent or underscore in it.
|
||||
# See http://www.sqlite.org/lang_expr.html for an explanation.
|
||||
OPERATOR_MAPPING = {
|
||||
'exact': '= %s',
|
||||
'iexact': "LIKE %s ESCAPE '\\'",
|
||||
'contains': "LIKE %s ESCAPE '\\'",
|
||||
'icontains': "LIKE %s ESCAPE '\\'",
|
||||
'ne': '!= %s',
|
||||
'gt': '> %s',
|
||||
'gte': '>= %s',
|
||||
'lt': '< %s',
|
||||
'lte': '<= %s',
|
||||
'startswith': "LIKE %s ESCAPE '\\'",
|
||||
'endswith': "LIKE %s ESCAPE '\\'",
|
||||
'istartswith': "LIKE %s ESCAPE '\\'",
|
||||
'iendswith': "LIKE %s ESCAPE '\\'",
|
||||
}
|
||||
|
||||
# SQLite doesn't actually support most of these types, but it "does the right
|
||||
# thing" given more verbose field definitions, so leave them as is so that
|
||||
# schema inspection is more useful.
|
||||
DATA_TYPES = {
|
||||
'AutoField': 'integer',
|
||||
'BooleanField': 'bool',
|
||||
'CharField': 'varchar(%(maxlength)s)',
|
||||
'CommaSeparatedIntegerField': 'varchar(%(maxlength)s)',
|
||||
'DateField': 'date',
|
||||
'DateTimeField': 'datetime',
|
||||
'FileField': 'varchar(100)',
|
||||
'FilePathField': 'varchar(100)',
|
||||
'FloatField': 'numeric(%(max_digits)s, %(decimal_places)s)',
|
||||
'ImageField': 'varchar(100)',
|
||||
'IntegerField': 'integer',
|
||||
'IPAddressField': 'char(15)',
|
||||
'ManyToManyField': None,
|
||||
'NullBooleanField': 'bool',
|
||||
'OneToOneField': 'integer',
|
||||
'PhoneNumberField': 'varchar(20)',
|
||||
'PositiveIntegerField': 'integer unsigned',
|
||||
'PositiveSmallIntegerField': 'smallint unsigned',
|
||||
'SlugField': 'varchar(%(maxlength)s)',
|
||||
'SmallIntegerField': 'smallint',
|
||||
'TextField': 'text',
|
||||
'TimeField': 'time',
|
||||
'URLField': 'varchar(200)',
|
||||
'USStateField': 'varchar(2)',
|
||||
}
|
||||
|
||||
# Maps SQL types to Django Field types. Some of the SQL types have multiple
|
||||
# entries here because SQLite allows for anything and doesn't normalize the
|
||||
# field type; it uses whatever was given.
|
||||
BASE_DATA_TYPES_REVERSE = {
|
||||
'bool': 'BooleanField',
|
||||
'boolean': 'BooleanField',
|
||||
'smallint': 'SmallIntegerField',
|
||||
'smallinteger': 'SmallIntegerField',
|
||||
'int': 'IntegerField',
|
||||
'integer': 'IntegerField',
|
||||
'text': 'TextField',
|
||||
'char': 'CharField',
|
||||
'date': 'DateField',
|
||||
'datetime': 'DateTimeField',
|
||||
'time': 'TimeField',
|
||||
}
|
||||
|
||||
# This light wrapper "fakes" a dictionary interface, because some SQLite data
|
||||
# types include variables in them -- e.g. "varchar(30)" -- and can't be matched
|
||||
# as a simple dictionary lookup.
|
||||
class FlexibleFieldLookupDict:
|
||||
def __getitem__(self, key):
|
||||
key = key.lower()
|
||||
try:
|
||||
return BASE_DATA_TYPES_REVERSE[key]
|
||||
except KeyError:
|
||||
import re
|
||||
m = re.search(r'^\s*(?:var)?char\s*\(\s*(\d+)\s*\)\s*$', key)
|
||||
if m:
|
||||
return ('CharField', {'maxlength': int(m.group(1))})
|
||||
raise KeyError
|
||||
|
||||
DATA_TYPES_REVERSE = FlexibleFieldLookupDict()
|
||||
@@ -1,32 +0,0 @@
|
||||
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)
|
||||
@@ -1,24 +0,0 @@
|
||||
"""
|
||||
Helper functions for dictfetch* for databases that don't natively support them.
|
||||
"""
|
||||
|
||||
def _dict_helper(desc, row):
|
||||
"Returns a dictionary for the given cursor.description and result row."
|
||||
return dict([(desc[col[0]][0], col[1]) for col in enumerate(row)])
|
||||
|
||||
def dictfetchone(cursor):
|
||||
"Returns a row from the cursor as a dict"
|
||||
row = cursor.fetchone()
|
||||
if not row:
|
||||
return None
|
||||
return _dict_helper(cursor.description, row)
|
||||
|
||||
def dictfetchmany(cursor, number):
|
||||
"Returns a certain number of rows from a cursor as a dict"
|
||||
desc = cursor.description
|
||||
return [_dict_helper(desc, row) for row in cursor.fetchmany(number)]
|
||||
|
||||
def dictfetchall(cursor):
|
||||
"Returns all rows from a cursor as a dict"
|
||||
desc = cursor.description
|
||||
return [_dict_helper(desc, row) for row in cursor.fetchall()]
|
||||
@@ -1,55 +0,0 @@
|
||||
import datetime
|
||||
|
||||
###############################################
|
||||
# Converters from database (string) to Python #
|
||||
###############################################
|
||||
|
||||
def typecast_date(s):
|
||||
return s and datetime.date(*map(int, s.split('-'))) or None # returns None if s is null
|
||||
|
||||
def typecast_time(s): # does NOT store time zone information
|
||||
if not s: return None
|
||||
hour, minutes, seconds = s.split(':')
|
||||
if '.' in seconds: # check whether seconds have a fractional part
|
||||
seconds, microseconds = seconds.split('.')
|
||||
else:
|
||||
microseconds = '0'
|
||||
return datetime.time(int(hour), int(minutes), int(seconds), int(float('.'+microseconds) * 1000000))
|
||||
|
||||
def typecast_timestamp(s): # does NOT store time zone information
|
||||
# "2005-07-29 15:48:00.590358-05"
|
||||
# "2005-07-29 09:56:00-05"
|
||||
if not s: return None
|
||||
if not ' ' in s: return typecast_date(s)
|
||||
d, t = s.split()
|
||||
# Extract timezone information, if it exists. Currently we just throw
|
||||
# it away, but in the future we may make use of it.
|
||||
if '-' in t:
|
||||
t, tz = t.split('-', 1)
|
||||
tz = '-' + tz
|
||||
elif '+' in t:
|
||||
t, tz = t.split('+', 1)
|
||||
tz = '+' + tz
|
||||
else:
|
||||
tz = ''
|
||||
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), int(float('.'+microseconds) * 1000000))
|
||||
|
||||
def typecast_boolean(s):
|
||||
if s is None: return None
|
||||
if not s: return False
|
||||
return str(s)[0].lower() == 't'
|
||||
|
||||
###############################################
|
||||
# Converters from Python to database (string) #
|
||||
###############################################
|
||||
|
||||
def rev_typecast_boolean(obj, d):
|
||||
return obj and '1' or '0'
|
||||
@@ -1,13 +1,8 @@
|
||||
"Global Django exceptions"
|
||||
|
||||
from django.core.template import SilentVariableFailure
|
||||
|
||||
class Http404(Exception):
|
||||
pass
|
||||
|
||||
class ObjectDoesNotExist(SilentVariableFailure):
|
||||
class ObjectDoesNotExist(Exception):
|
||||
"The requested object does not exist"
|
||||
pass
|
||||
silent_variable_failure = True
|
||||
|
||||
class SuspiciousOperation(Exception):
|
||||
"The user did something suspicious"
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
# This module collects helper functions and classes that "span" multiple levels
|
||||
# of MVC. In other words, these functions/classes introduce controlled coupling
|
||||
# for convenience's sake.
|
||||
|
||||
from django.core.exceptions import Http404, ImproperlyConfigured, ObjectDoesNotExist
|
||||
from django.core.template import Context, loader
|
||||
from django.conf.settings import TEMPLATE_CONTEXT_PROCESSORS
|
||||
from django.utils.httpwrappers import HttpResponse
|
||||
|
||||
_standard_context_processors = None
|
||||
|
||||
# This is a function rather than module-level procedural code because we only
|
||||
# want it to execute if somebody uses DjangoContext.
|
||||
def get_standard_processors():
|
||||
global _standard_context_processors
|
||||
if _standard_context_processors is None:
|
||||
processors = []
|
||||
for path in TEMPLATE_CONTEXT_PROCESSORS:
|
||||
i = path.rfind('.')
|
||||
module, attr = path[:i], path[i+1:]
|
||||
try:
|
||||
mod = __import__(module, '', '', [attr])
|
||||
except ImportError, e:
|
||||
raise ImproperlyConfigured, 'Error importing request processor module %s: "%s"' % (module, e)
|
||||
try:
|
||||
func = getattr(mod, attr)
|
||||
except AttributeError:
|
||||
raise ImproperlyConfigured, 'Module "%s" does not define a "%s" callable request processor' % (module, attr)
|
||||
processors.append(func)
|
||||
_standard_context_processors = tuple(processors)
|
||||
return _standard_context_processors
|
||||
|
||||
def render_to_response(*args, **kwargs):
|
||||
return HttpResponse(loader.render_to_string(*args, **kwargs))
|
||||
load_and_render = render_to_response # For backwards compatibility.
|
||||
|
||||
def get_object_or_404(mod, **kwargs):
|
||||
try:
|
||||
return mod.get_object(**kwargs)
|
||||
except ObjectDoesNotExist:
|
||||
raise Http404
|
||||
|
||||
def get_list_or_404(mod, **kwargs):
|
||||
obj_list = mod.get_list(**kwargs)
|
||||
if not obj_list:
|
||||
raise Http404
|
||||
return obj_list
|
||||
|
||||
class DjangoContext(Context):
|
||||
"""
|
||||
This subclass of template.Context automatically populates itself using
|
||||
the processors defined in TEMPLATE_CONTEXT_PROCESSORS.
|
||||
Additional processors can be specified as a list of callables
|
||||
using the "processors" keyword argument.
|
||||
"""
|
||||
def __init__(self, request, dict=None, processors=None):
|
||||
Context.__init__(self, dict)
|
||||
if processors is None:
|
||||
processors = ()
|
||||
else:
|
||||
processors = tuple(processors)
|
||||
for processor in get_standard_processors() + processors:
|
||||
self.update(processor(request))
|
||||
|
||||
# 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_permission_list())
|
||||
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)
|
||||
@@ -1,917 +0,0 @@
|
||||
from django.core import validators
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.utils.html import escape
|
||||
from django.conf.settings import DEFAULT_CHARSET
|
||||
from django.utils.translation import gettext_lazy, ngettext
|
||||
|
||||
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\n%s" % (field_name, repr(self.fields))
|
||||
|
||||
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(gettext_lazy('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:
|
||||
field.convert_post_data(new_data)
|
||||
|
||||
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, edit_inline=True):
|
||||
self.manipulator, self.data = manipulator, data
|
||||
self.error_dict = error_dict
|
||||
self._inline_collections = None
|
||||
self.edit_inline = edit_inline
|
||||
|
||||
def __repr__(self):
|
||||
return repr(self.__dict__)
|
||||
|
||||
def __getitem__(self, key):
|
||||
for field in self.manipulator.fields:
|
||||
if field.field_name == key:
|
||||
data = field.extract_data(self.data)
|
||||
return FormFieldWrapper(field, data, self.error_dict.get(field.field_name, []))
|
||||
if self.edit_inline:
|
||||
self.fill_inline_collections()
|
||||
for inline_collection in self._inline_collections:
|
||||
if inline_collection.name == key:
|
||||
return inline_collection
|
||||
raise KeyError, "Could not find Formfield or InlineObjectCollection named %r" % key
|
||||
|
||||
def fill_inline_collections(self):
|
||||
if not self._inline_collections:
|
||||
ic = []
|
||||
related_objects = self.manipulator.get_related_objects()
|
||||
for rel_obj in related_objects:
|
||||
data = rel_obj.extract_data(self.data)
|
||||
inline_collection = InlineObjectCollection(self.manipulator, rel_obj, data, self.error_dict)
|
||||
ic.append(inline_collection)
|
||||
self._inline_collections = ic
|
||||
|
||||
def has_errors(self):
|
||||
return self.error_dict != {}
|
||||
|
||||
def _get_fields(self):
|
||||
try:
|
||||
return self._fields
|
||||
except AttributeError:
|
||||
self._fields = [self.__getitem__(field.field_name) for field in self.manipulator.fields]
|
||||
return self._fields
|
||||
|
||||
fields = property(_get_fields)
|
||||
|
||||
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 ''
|
||||
|
||||
def get_id(self):
|
||||
return self.formfield.get_id()
|
||||
|
||||
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():
|
||||
if hasattr(field, 'errors'):
|
||||
errors.extend(field.errors())
|
||||
return errors
|
||||
|
||||
def has_errors(self):
|
||||
return bool(len(self.errors()))
|
||||
|
||||
def html_combined_error_list(self):
|
||||
return ''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')])
|
||||
|
||||
class InlineObjectCollection:
|
||||
"An object that acts like a list of form field collections."
|
||||
def __init__(self, parent_manipulator, rel_obj, data, errors):
|
||||
self.parent_manipulator = parent_manipulator
|
||||
self.rel_obj = rel_obj
|
||||
self.data = data
|
||||
self.errors = errors
|
||||
self._collections = None
|
||||
self.name = rel_obj.name
|
||||
|
||||
def __len__(self):
|
||||
self.fill()
|
||||
return self._collections.__len__()
|
||||
|
||||
def __getitem__(self, k):
|
||||
self.fill()
|
||||
return self._collections.__getitem__(k)
|
||||
|
||||
def __setitem__(self, k, v):
|
||||
self.fill()
|
||||
return self._collections.__setitem__(k,v)
|
||||
|
||||
def __delitem__(self, k):
|
||||
self.fill()
|
||||
return self._collections.__delitem__(k)
|
||||
|
||||
def __iter__(self):
|
||||
self.fill()
|
||||
return self._collections.__iter__()
|
||||
|
||||
def fill(self):
|
||||
if self._collections:
|
||||
return
|
||||
else:
|
||||
var_name = self.rel_obj.opts.object_name.lower()
|
||||
wrapper = []
|
||||
orig = hasattr(self.parent_manipulator, 'original_object') and self.parent_manipulator.original_object or None
|
||||
orig_list = self.rel_obj.get_list(orig)
|
||||
for i, instance in enumerate(orig_list):
|
||||
collection = {'original': instance}
|
||||
for f in self.rel_obj.editable_fields():
|
||||
for field_name in f.get_manipulator_field_names(''):
|
||||
full_field_name = '%s.%d.%s' % (var_name, i, field_name)
|
||||
field = self.parent_manipulator[full_field_name]
|
||||
data = field.extract_data(self.data)
|
||||
errors = self.errors.get(full_field_name, [])
|
||||
collection[field_name] = FormFieldWrapper(field, data, errors)
|
||||
wrapper.append(FormFieldCollection(collection))
|
||||
self._collections = wrapper
|
||||
|
||||
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
|
||||
|
||||
def get_member_name(self):
|
||||
if hasattr(self, 'member_name'):
|
||||
return self.member_name
|
||||
else:
|
||||
return self.field_name
|
||||
|
||||
def extract_data(self, data_dict):
|
||||
if hasattr(self, 'requires_data_list') and hasattr(data_dict, 'getlist'):
|
||||
data = data_dict.getlist(self.get_member_name())
|
||||
else:
|
||||
data = data_dict.get(self.get_member_name(), None)
|
||||
if data is None:
|
||||
data = ''
|
||||
return data
|
||||
|
||||
def convert_post_data(self, new_data):
|
||||
name = self.get_member_name()
|
||||
if new_data.has_key(self.field_name):
|
||||
d = new_data.getlist(self.field_name)
|
||||
try:
|
||||
converted_data = [self.__class__.html2python(data) for data in d]
|
||||
except ValueError:
|
||||
converted_data = d
|
||||
new_data.setlist(name, converted_data)
|
||||
else:
|
||||
try:
|
||||
# individual fields deal with None values themselves
|
||||
new_data.setlist(name, [self.__class__.html2python(None)])
|
||||
except EmptyValue:
|
||||
new_data.setlist(name, [])
|
||||
|
||||
def get_id(self):
|
||||
"Returns the HTML 'id' attribute for this form field."
|
||||
return FORM_FIELD_ID_PREFIX + self.field_name
|
||||
|
||||
####################
|
||||
# GENERIC WIDGETS #
|
||||
####################
|
||||
|
||||
class TextField(FormField):
|
||||
input_type = "text"
|
||||
def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=[], member_name=None):
|
||||
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
|
||||
if member_name != None:
|
||||
self.member_name = member_name
|
||||
|
||||
def isValidLength(self, data, form):
|
||||
if data and self.maxlength and len(data.decode(DEFAULT_CHARSET)) > self.maxlength:
|
||||
raise validators.ValidationError, ngettext("Ensure your text is less than %s character.",
|
||||
"Ensure your text is less than %s characters.", self.maxlength) % 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(DEFAULT_CHARSET)
|
||||
return '<input type="%s" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \
|
||||
(self.input_type, self.get_id(), self.__class__.__name__, self.is_required and ' required' or '',
|
||||
self.field_name, self.length, escape(data), maxlength)
|
||||
|
||||
def html2python(data):
|
||||
return data
|
||||
html2python = staticmethod(html2python)
|
||||
|
||||
class PasswordField(TextField):
|
||||
input_type = "password"
|
||||
|
||||
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(DEFAULT_CHARSET)
|
||||
return '<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \
|
||||
(self.get_id(), 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" />' % \
|
||||
(self.get_id(), 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 />' % \
|
||||
(self.get_id(), 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=[], member_name=None):
|
||||
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
|
||||
if member_name != None:
|
||||
self.member_name = member_name
|
||||
|
||||
def render(self, data):
|
||||
output = ['<select id="%s" class="v%s%s" name="%s" size="%s">' % \
|
||||
(self.get_id(), 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, escape(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; '%(data)s' is not in %(choices)s.") % {'data': str_data, 'choices': 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=[], member_name=None):
|
||||
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
|
||||
if member_name != None:
|
||||
self.member_name = member_name
|
||||
|
||||
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/>' % \
|
||||
(self.get_id() + '_' + str(i), self.field_name, value, selected_html),
|
||||
'label': '<label for="%s">%s</label>' % \
|
||||
(self.get_id() + '_' + 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; '%(data)s' is not in %(choices)s.") % {'data':str_data, 'choices':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">' % \
|
||||
(self.get_id(), 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; '%(data)s' is not in %(choices)s.") % {'data':val, 'choices':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, readable_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" class="v%s" name="%s"%s /> <label for="%s">%s</label></li>' % \
|
||||
(self.get_id() + value , self.__class__.__name__, field_name, checked_html,
|
||||
self.get_id() + value, 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" />' % \
|
||||
(self.get_id(), 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=[], member_name=None):
|
||||
validator_list = [self.isInteger] + validator_list
|
||||
if member_name is not None:
|
||||
self.member_name = member_name
|
||||
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 or HH:MM:SS.mmmmmm."""
|
||||
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:
|
||||
part_list = data.split('.')
|
||||
try:
|
||||
time_tuple = time.strptime(part_list[0], '%H:%M:%S')
|
||||
except ValueError: # seconds weren't provided
|
||||
time_tuple = time.strptime(part_list[0], '%H:%M')
|
||||
t = datetime.time(*time_tuple[3:6])
|
||||
if (len(part_list) == 2):
|
||||
t = t.replace(microsecond=int(part_list[1]))
|
||||
return t
|
||||
except (ValueError, TypeError, AttributeError):
|
||||
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, maxlength=75, is_required=False, validator_list=[]):
|
||||
validator_list = [self.isValidEmail] + validator_list
|
||||
TextField.__init__(self, field_name, length, maxlength=maxlength,
|
||||
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 __init__(self, field_name, length=15, maxlength=15, is_required=False, validator_list=[]):
|
||||
validator_list = [self.isValidIPAddress] + validator_list
|
||||
TextField.__init__(self, field_name, length=length, maxlength=maxlength,
|
||||
is_required=is_required, validator_list=validator_list)
|
||||
|
||||
def isValidIPAddress(self, field_data, all_data):
|
||||
try:
|
||||
validators.isValidIPAddress4(field_data, all_data)
|
||||
except validators.ValidationError, e:
|
||||
raise validators.CriticalValidationError, e.messages
|
||||
|
||||
def html2python(data):
|
||||
return data or None
|
||||
html2python = staticmethod(html2python)
|
||||
|
||||
####################
|
||||
# MISCELLANEOUS #
|
||||
####################
|
||||
|
||||
class FilePathField(SelectField):
|
||||
"A SelectField whose choices are the files in a given directory."
|
||||
def __init__(self, field_name, path, match=None, recursive=False, is_required=False, validator_list=[]):
|
||||
import os
|
||||
if match is not None:
|
||||
import re
|
||||
match_re = re.compile(match)
|
||||
choices = []
|
||||
if recursive:
|
||||
for root, dirs, files in os.walk(path):
|
||||
for f in files:
|
||||
if match is None or match_re.search(f):
|
||||
choices.append((os.path.join(path, f), f))
|
||||
else:
|
||||
try:
|
||||
for f in os.listdir(path):
|
||||
full_file = os.path.join(path, f)
|
||||
if os.path.isfile(full_file) and (match is None or match_re.search(f)):
|
||||
choices.append((full_file, f))
|
||||
except OSError:
|
||||
pass
|
||||
SelectField.__init__(self, field_name, choices, 1, is_required, validator_list)
|
||||
|
||||
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):
|
||||
if data:
|
||||
return data.upper() # Should always be stored in upper case
|
||||
else:
|
||||
return None
|
||||
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 RawIdAdminField(CommaSeparatedIntegerField):
|
||||
def html2python(data):
|
||||
return data.split(',')
|
||||
html2python = staticmethod(html2python)
|
||||
|
||||
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
|
||||
@@ -1,4 +1,6 @@
|
||||
from django.utils import httpwrappers
|
||||
from django.core import signals
|
||||
from django.dispatch import dispatcher
|
||||
from django import http
|
||||
import sys
|
||||
|
||||
class BaseHandler:
|
||||
@@ -48,12 +50,9 @@ class BaseHandler:
|
||||
|
||||
def get_response(self, path, request):
|
||||
"Returns an HttpResponse object for the given HttpRequest"
|
||||
from django.core import db, exceptions, urlresolvers
|
||||
from django.core import exceptions, urlresolvers
|
||||
from django.core.mail import mail_admins
|
||||
from django.conf.settings import DEBUG, INTERNAL_IPS, ROOT_URLCONF
|
||||
|
||||
# Reset query list per request.
|
||||
db.db.queries = []
|
||||
from django.conf import settings
|
||||
|
||||
# Apply request middleware
|
||||
for middleware_method in self._request_middleware:
|
||||
@@ -61,7 +60,7 @@ class BaseHandler:
|
||||
if response:
|
||||
return response
|
||||
|
||||
resolver = urlresolvers.RegexURLResolver(r'^/', ROOT_URLCONF)
|
||||
resolver = urlresolvers.RegexURLResolver(r'^/', settings.ROOT_URLCONF)
|
||||
try:
|
||||
callback, callback_args, callback_kwargs = resolver.resolve(path)
|
||||
|
||||
@@ -88,30 +87,23 @@ class BaseHandler:
|
||||
raise ValueError, "The view %s.%s didn't return an HttpResponse object." % (callback.__module__, callback.func_name)
|
||||
|
||||
return response
|
||||
except exceptions.Http404, e:
|
||||
if DEBUG:
|
||||
except http.Http404, e:
|
||||
if settings.DEBUG:
|
||||
return self.get_technical_error_response(request, is404=True, exception=e)
|
||||
else:
|
||||
callback, param_dict = resolver.resolve404()
|
||||
return callback(request, **param_dict)
|
||||
except db.DatabaseError:
|
||||
db.db.rollback()
|
||||
if DEBUG:
|
||||
return self.get_technical_error_response(request)
|
||||
else:
|
||||
subject = 'Database error (%s IP): %s' % ((request.META.get('REMOTE_ADDR') in INTERNAL_IPS and 'internal' or 'EXTERNAL'), getattr(request, 'path', ''))
|
||||
message = "%s\n\n%s" % (self._get_traceback(), request)
|
||||
mail_admins(subject, message, fail_silently=True)
|
||||
return self.get_friendly_error_response(request, resolver)
|
||||
except exceptions.PermissionDenied:
|
||||
return httpwrappers.HttpResponseForbidden('<h1>Permission denied</h1>')
|
||||
return http.HttpResponseForbidden('<h1>Permission denied</h1>')
|
||||
except: # Handle everything else, including SuspiciousOperation, etc.
|
||||
if DEBUG:
|
||||
if settings.DEBUG:
|
||||
return self.get_technical_error_response(request)
|
||||
else:
|
||||
# Get the exception info now, in case another exception is thrown later.
|
||||
exc_info = sys.exc_info()
|
||||
subject = 'Coding error (%s IP): %s' % ((request.META.get('REMOTE_ADDR') in INTERNAL_IPS and 'internal' or 'EXTERNAL'), getattr(request, 'path', ''))
|
||||
receivers = dispatcher.send(signal=signals.got_request_exception)
|
||||
# When DEBUG is False, send an error message to the admins.
|
||||
subject = 'Error (%s IP): %s' % ((request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS and 'internal' or 'EXTERNAL'), getattr(request, 'path', ''))
|
||||
try:
|
||||
request_repr = repr(request)
|
||||
except:
|
||||
@@ -123,7 +115,7 @@ class BaseHandler:
|
||||
def get_friendly_error_response(self, request, resolver):
|
||||
"""
|
||||
Returns an HttpResponse that displays a PUBLIC error message for a
|
||||
fundamental database or coding error.
|
||||
fundamental error.
|
||||
"""
|
||||
from django.core import urlresolvers
|
||||
callback, param_dict = resolver.resolve500()
|
||||
@@ -132,7 +124,7 @@ class BaseHandler:
|
||||
def get_technical_error_response(self, request, is404=False, exception=None):
|
||||
"""
|
||||
Returns an HttpResponse that displays a TECHNICAL error message for a
|
||||
fundamental database or coding error.
|
||||
fundamental error.
|
||||
"""
|
||||
from django.views import debug
|
||||
if is404:
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
from django.core.handlers.base import BaseHandler
|
||||
from django.utils import datastructures, httpwrappers
|
||||
from django.core import signals
|
||||
from django.dispatch import dispatcher
|
||||
from django.utils import datastructures
|
||||
from django import http
|
||||
from pprint import pformat
|
||||
import os
|
||||
|
||||
@@ -7,15 +10,15 @@ import os
|
||||
# settings) until after ModPythonHandler has been called; otherwise os.environ
|
||||
# won't be set up correctly (with respect to settings).
|
||||
|
||||
class ModPythonRequest(httpwrappers.HttpRequest):
|
||||
class ModPythonRequest(http.HttpRequest):
|
||||
def __init__(self, req):
|
||||
self._req = req
|
||||
self.path = req.uri
|
||||
|
||||
def __repr__(self):
|
||||
return '<ModPythonRequest\npath:%s,\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s,\nuser:%s>' % \
|
||||
return '<ModPythonRequest\npath:%s,\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' % \
|
||||
(self.path, pformat(self.GET), pformat(self.POST), pformat(self.COOKIES),
|
||||
pformat(self.META), pformat(self.user))
|
||||
pformat(self.META))
|
||||
|
||||
def get_full_path(self):
|
||||
return '%s%s' % (self.path, self._req.args and ('?' + self._req.args) or '')
|
||||
@@ -23,18 +26,18 @@ class ModPythonRequest(httpwrappers.HttpRequest):
|
||||
def _load_post_and_files(self):
|
||||
"Populates self._post and self._files"
|
||||
if self._req.headers_in.has_key('content-type') and self._req.headers_in['content-type'].startswith('multipart'):
|
||||
self._post, self._files = httpwrappers.parse_file_upload(self._req.headers_in, self.raw_post_data)
|
||||
self._post, self._files = http.parse_file_upload(self._req.headers_in, self.raw_post_data)
|
||||
else:
|
||||
self._post, self._files = httpwrappers.QueryDict(self.raw_post_data), datastructures.MultiValueDict()
|
||||
self._post, self._files = http.QueryDict(self.raw_post_data), datastructures.MultiValueDict()
|
||||
|
||||
def _get_request(self):
|
||||
if not hasattr(self, '_request'):
|
||||
self._request = datastructures.MergeDict(self.POST, self.GET)
|
||||
self._request = datastructures.MergeDict(self.POST, self.GET)
|
||||
return self._request
|
||||
|
||||
def _get_get(self):
|
||||
if not hasattr(self, '_get'):
|
||||
self._get = httpwrappers.QueryDict(self._req.args)
|
||||
self._get = http.QueryDict(self._req.args)
|
||||
return self._get
|
||||
|
||||
def _set_get(self, get):
|
||||
@@ -50,7 +53,7 @@ class ModPythonRequest(httpwrappers.HttpRequest):
|
||||
|
||||
def _get_cookies(self):
|
||||
if not hasattr(self, '_cookies'):
|
||||
self._cookies = httpwrappers.parse_cookie(self._req.headers_in.get('cookie', ''))
|
||||
self._cookies = http.parse_cookie(self._req.headers_in.get('cookie', ''))
|
||||
return self._cookies
|
||||
|
||||
def _set_cookies(self, cookies):
|
||||
@@ -95,22 +98,6 @@ class ModPythonRequest(httpwrappers.HttpRequest):
|
||||
self._raw_post_data = self._req.read()
|
||||
return self._raw_post_data
|
||||
|
||||
def _get_user(self):
|
||||
if not hasattr(self, '_user'):
|
||||
from django.models.auth import users
|
||||
try:
|
||||
user_id = self.session[users.SESSION_KEY]
|
||||
if not user_id:
|
||||
raise ValueError
|
||||
self._user = users.get_object(pk=user_id)
|
||||
except (AttributeError, KeyError, ValueError, users.UserDoesNotExist):
|
||||
from django.parts.auth import anonymoususers
|
||||
self._user = anonymoususers.AnonymousUser()
|
||||
return self._user
|
||||
|
||||
def _set_user(self, user):
|
||||
self._user = user
|
||||
|
||||
GET = property(_get_get, _set_get)
|
||||
POST = property(_get_post, _set_post)
|
||||
COOKIES = property(_get_cookies, _set_cookies)
|
||||
@@ -118,7 +105,6 @@ class ModPythonRequest(httpwrappers.HttpRequest):
|
||||
META = property(_get_meta)
|
||||
REQUEST = property(_get_request)
|
||||
raw_post_data = property(_get_raw_post_data)
|
||||
user = property(_get_user, _set_user)
|
||||
|
||||
class ModPythonHandler(BaseHandler):
|
||||
def __call__(self, req):
|
||||
@@ -128,7 +114,6 @@ class ModPythonHandler(BaseHandler):
|
||||
# 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 settings.ENABLE_PSYCO:
|
||||
import psyco
|
||||
@@ -138,15 +123,17 @@ class ModPythonHandler(BaseHandler):
|
||||
if self._request_middleware is None:
|
||||
self.load_middleware()
|
||||
|
||||
dispatcher.send(signal=signals.request_started)
|
||||
try:
|
||||
request = ModPythonRequest(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)
|
||||
# Apply response middleware
|
||||
for middleware_method in self._response_middleware:
|
||||
response = middleware_method(request, response)
|
||||
|
||||
finally:
|
||||
dispatcher.send(signal=signals.request_finished)
|
||||
|
||||
# Convert our custom HttpResponse object back into the mod_python req.
|
||||
populate_apache_request(response, req)
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
from django.core.handlers.base import BaseHandler
|
||||
from django.utils import datastructures, httpwrappers
|
||||
from django.core import signals
|
||||
from django.dispatch import dispatcher
|
||||
from django.utils import datastructures
|
||||
from django import http
|
||||
from pprint import pformat
|
||||
|
||||
# See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
|
||||
@@ -47,7 +50,7 @@ STATUS_CODE_TEXT = {
|
||||
505: 'HTTP VERSION NOT SUPPORTED',
|
||||
}
|
||||
|
||||
class WSGIRequest(httpwrappers.HttpRequest):
|
||||
class WSGIRequest(http.HttpRequest):
|
||||
def __init__(self, environ):
|
||||
self.environ = environ
|
||||
self.path = environ['PATH_INFO']
|
||||
@@ -60,7 +63,7 @@ class WSGIRequest(httpwrappers.HttpRequest):
|
||||
pformat(self.META))
|
||||
|
||||
def get_full_path(self):
|
||||
return '%s%s' % (self.path, self.environ['QUERY_STRING'] and ('?' + self.environ['QUERY_STRING']) or '')
|
||||
return '%s%s' % (self.path, self.environ.get('QUERY_STRING', '') and ('?' + self.environ.get('QUERY_STRING', '')) or '')
|
||||
|
||||
def _load_post_and_files(self):
|
||||
# Populates self._post and self._files
|
||||
@@ -68,21 +71,21 @@ class WSGIRequest(httpwrappers.HttpRequest):
|
||||
if self.environ.get('CONTENT_TYPE', '').startswith('multipart'):
|
||||
header_dict = dict([(k, v) for k, v in self.environ.items() if k.startswith('HTTP_')])
|
||||
header_dict['Content-Type'] = self.environ.get('CONTENT_TYPE', '')
|
||||
self._post, self._files = httpwrappers.parse_file_upload(header_dict, self.raw_post_data)
|
||||
self._post, self._files = http.parse_file_upload(header_dict, self.raw_post_data)
|
||||
else:
|
||||
self._post, self._files = httpwrappers.QueryDict(self.raw_post_data), datastructures.MultiValueDict()
|
||||
self._post, self._files = http.QueryDict(self.raw_post_data), datastructures.MultiValueDict()
|
||||
else:
|
||||
self._post, self._files = httpwrappers.QueryDict(''), datastructures.MultiValueDict()
|
||||
self._post, self._files = http.QueryDict(''), datastructures.MultiValueDict()
|
||||
|
||||
def _get_request(self):
|
||||
if not hasattr(self, '_request'):
|
||||
self._request = datastructures.MergeDict(self.POST, self.GET)
|
||||
self._request = datastructures.MergeDict(self.POST, self.GET)
|
||||
return self._request
|
||||
|
||||
def _get_get(self):
|
||||
if not hasattr(self, '_get'):
|
||||
# The WSGI spec says 'QUERY_STRING' may be absent.
|
||||
self._get = httpwrappers.QueryDict(self.environ.get('QUERY_STRING', ''))
|
||||
self._get = http.QueryDict(self.environ.get('QUERY_STRING', ''))
|
||||
return self._get
|
||||
|
||||
def _set_get(self, get):
|
||||
@@ -98,7 +101,7 @@ class WSGIRequest(httpwrappers.HttpRequest):
|
||||
|
||||
def _get_cookies(self):
|
||||
if not hasattr(self, '_cookies'):
|
||||
self._cookies = httpwrappers.parse_cookie(self.environ.get('HTTP_COOKIE', ''))
|
||||
self._cookies = http.parse_cookie(self.environ.get('HTTP_COOKIE', ''))
|
||||
return self._cookies
|
||||
|
||||
def _set_cookies(self, cookies):
|
||||
@@ -116,34 +119,16 @@ class WSGIRequest(httpwrappers.HttpRequest):
|
||||
self._raw_post_data = self.environ['wsgi.input'].read(int(self.environ["CONTENT_LENGTH"]))
|
||||
return self._raw_post_data
|
||||
|
||||
def _get_user(self):
|
||||
if not hasattr(self, '_user'):
|
||||
from django.models.auth import users
|
||||
try:
|
||||
user_id = self.session[users.SESSION_KEY]
|
||||
if not user_id:
|
||||
raise ValueError
|
||||
self._user = users.get_object(pk=user_id)
|
||||
except (AttributeError, KeyError, ValueError, users.UserDoesNotExist):
|
||||
from django.parts.auth import anonymoususers
|
||||
self._user = anonymoususers.AnonymousUser()
|
||||
return self._user
|
||||
|
||||
def _set_user(self, user):
|
||||
self._user = user
|
||||
|
||||
GET = property(_get_get, _set_get)
|
||||
POST = property(_get_post, _set_post)
|
||||
COOKIES = property(_get_cookies, _set_cookies)
|
||||
FILES = property(_get_files)
|
||||
REQUEST = property(_get_request)
|
||||
raw_post_data = property(_get_raw_post_data)
|
||||
user = property(_get_user, _set_user)
|
||||
|
||||
class WSGIHandler(BaseHandler):
|
||||
def __call__(self, environ, start_response):
|
||||
from django.conf import settings
|
||||
from django.core import db
|
||||
|
||||
if settings.ENABLE_PSYCO:
|
||||
import psyco
|
||||
@@ -154,15 +139,17 @@ class WSGIHandler(BaseHandler):
|
||||
if self._request_middleware is None:
|
||||
self.load_middleware()
|
||||
|
||||
dispatcher.send(signal=signals.request_started)
|
||||
try:
|
||||
request = WSGIRequest(environ)
|
||||
response = self.get_response(request.path, request)
|
||||
finally:
|
||||
db.db.close()
|
||||
|
||||
# Apply response middleware
|
||||
for middleware_method in self._response_middleware:
|
||||
response = middleware_method(request, response)
|
||||
# Apply response middleware
|
||||
for middleware_method in self._response_middleware:
|
||||
response = middleware_method(request, response)
|
||||
|
||||
finally:
|
||||
dispatcher.send(signal=signals.request_finished)
|
||||
|
||||
try:
|
||||
status_text = STATUS_CODE_TEXT[response.status_code]
|
||||
|
||||
@@ -46,9 +46,18 @@ def send_mass_mail(datatuple, fail_silently=False, auth_user=settings.EMAIL_HOST
|
||||
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()
|
||||
try:
|
||||
server.sendmail(from_email, recipient_list, msg.as_string())
|
||||
num_sent += 1
|
||||
except:
|
||||
if not fail_silently:
|
||||
raise
|
||||
try:
|
||||
server.quit()
|
||||
except:
|
||||
if fail_silently:
|
||||
return
|
||||
raise
|
||||
return num_sent
|
||||
|
||||
def mail_admins(subject, message, fail_silently=False):
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,956 +0,0 @@
|
||||
from django.conf import settings
|
||||
from django.core import formfields, validators
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.utils.functional import curry, lazy
|
||||
from django.utils.text import capfirst
|
||||
from django.utils.translation import gettext_lazy, ngettext
|
||||
import datetime, os
|
||||
|
||||
class NOT_PROVIDED:
|
||||
pass
|
||||
|
||||
# Values for filter_interface.
|
||||
HORIZONTAL, VERTICAL = 1, 2
|
||||
|
||||
# The values to use for "blank" in SelectFields. Will be appended to the start of most "choices" lists.
|
||||
BLANK_CHOICE_DASH = [("", "---------")]
|
||||
BLANK_CHOICE_NONE = [("", "None")]
|
||||
|
||||
# Values for Relation.edit_inline.
|
||||
TABULAR, STACKED = 1, 2
|
||||
|
||||
RECURSIVE_RELATIONSHIP_CONSTANT = 'self'
|
||||
|
||||
# prepares a value for use in a LIKE query
|
||||
prep_for_like_query = lambda x: str(x).replace("%", "\%").replace("_", "\_")
|
||||
|
||||
# returns the <ul> class for a given radio_admin value
|
||||
get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '')
|
||||
|
||||
def string_concat(*strings):
|
||||
""""
|
||||
lazy variant of string concatenation, needed for translations that are
|
||||
constructed from multiple parts. Handles lazy strings and non-strings by
|
||||
first turning all arguments to strings, before joining them.
|
||||
"""
|
||||
return ''.join([str(el) for el in strings])
|
||||
|
||||
string_concat = lazy(string_concat, str)
|
||||
|
||||
def manipulator_valid_rel_key(f, self, field_data, all_data):
|
||||
"Validates that the value is a valid foreign key"
|
||||
mod = f.rel.to.get_model_module()
|
||||
try:
|
||||
mod.get_object(pk=field_data)
|
||||
except ObjectDoesNotExist:
|
||||
raise validators.ValidationError, _("Please enter a valid %s.") % f.verbose_name
|
||||
|
||||
def manipulator_validator_unique(f, opts, self, field_data, all_data):
|
||||
"Validates that the value is unique for this field."
|
||||
if f.rel and isinstance(f.rel, ManyToOneRel):
|
||||
lookup_type = '%s__%s__exact' % (f.name, f.rel.get_related_field().name)
|
||||
else:
|
||||
lookup_type = '%s__exact' % f.name
|
||||
try:
|
||||
old_obj = opts.get_model_module().get_object(**{lookup_type: field_data})
|
||||
except ObjectDoesNotExist:
|
||||
return
|
||||
if hasattr(self, 'original_object') and getattr(self.original_object, opts.pk.attname) == getattr(old_obj, opts.pk.attname):
|
||||
return
|
||||
raise validators.ValidationError, _("%(optname)s with this %(fieldname)s already exists.") % {'optname': capfirst(opts.verbose_name), 'fieldname': f.verbose_name}
|
||||
|
||||
class BoundField(object):
|
||||
def __init__(self, field, field_mapping, original):
|
||||
self.field = field
|
||||
self.original = original
|
||||
self.form_fields = self.resolve_form_fields(field_mapping)
|
||||
|
||||
def resolve_form_fields(self, field_mapping):
|
||||
return [field_mapping[name] for name in self.field.get_manipulator_field_names('')]
|
||||
|
||||
def as_field_list(self):
|
||||
return [self.field]
|
||||
|
||||
def original_value(self):
|
||||
if self.original:
|
||||
return self.original.__dict__[self.field.column]
|
||||
|
||||
def __repr__(self):
|
||||
return "BoundField:(%s, %s)" % (self.field.name, self.form_fields)
|
||||
|
||||
# A guide to Field parameters:
|
||||
#
|
||||
# * name: The name of the field specified in the model.
|
||||
# * attname: The attribute to use on the model object. This is the same as
|
||||
# "name", except in the case of ForeignKeys, where "_id" is
|
||||
# appended.
|
||||
# * db_column: The db_column specified in the model (or None).
|
||||
# * column: The database column for this field. This is the same as
|
||||
# "attname", except if db_column is specified.
|
||||
#
|
||||
# Code that introspects values, or does other dynamic things, should use
|
||||
# attname. For example, this gets the primary key value of object "obj":
|
||||
#
|
||||
# getattr(obj, opts.pk.attname)
|
||||
|
||||
class Field(object):
|
||||
|
||||
# Designates whether empty strings fundamentally are allowed at the
|
||||
# database level.
|
||||
empty_strings_allowed = True
|
||||
|
||||
# Tracks each time a Field instance is created. Used to retain order.
|
||||
creation_counter = 0
|
||||
|
||||
def __init__(self, verbose_name=None, name=None, primary_key=False,
|
||||
maxlength=None, unique=False, blank=False, null=False, db_index=None,
|
||||
core=False, rel=None, default=NOT_PROVIDED, editable=True,
|
||||
prepopulate_from=None, unique_for_date=None, unique_for_month=None,
|
||||
unique_for_year=None, validator_list=None, choices=None, radio_admin=None,
|
||||
help_text='', db_column=None):
|
||||
self.name = name
|
||||
self.verbose_name = verbose_name or (name and name.replace('_', ' '))
|
||||
self.primary_key = primary_key
|
||||
self.maxlength, self.unique = maxlength, unique
|
||||
self.blank, self.null = blank, null
|
||||
self.core, self.rel, self.default = core, rel, default
|
||||
self.editable = editable
|
||||
self.validator_list = validator_list or []
|
||||
self.prepopulate_from = prepopulate_from
|
||||
self.unique_for_date, self.unique_for_month = unique_for_date, unique_for_month
|
||||
self.unique_for_year = unique_for_year
|
||||
self.choices = choices or []
|
||||
self.radio_admin = radio_admin
|
||||
self.help_text = help_text
|
||||
self.db_column = db_column
|
||||
if rel and isinstance(rel, ManyToManyRel):
|
||||
if rel.raw_id_admin:
|
||||
self.help_text = string_concat(self.help_text,
|
||||
gettext_lazy(' Separate multiple IDs with commas.'))
|
||||
else:
|
||||
self.help_text = string_concat(self.help_text,
|
||||
gettext_lazy(' Hold down "Control", or "Command" on a Mac, to select more than one.'))
|
||||
|
||||
# Set db_index to True if the field has a relationship and doesn't explicitly set db_index.
|
||||
if db_index is None:
|
||||
if isinstance(rel, OneToOneRel) or isinstance(rel, ManyToOneRel):
|
||||
self.db_index = True
|
||||
else:
|
||||
self.db_index = False
|
||||
else:
|
||||
self.db_index = db_index
|
||||
|
||||
# Increase the creation counter, and save our local copy.
|
||||
self.creation_counter = Field.creation_counter
|
||||
Field.creation_counter += 1
|
||||
|
||||
self.attname, self.column = self.get_attname_column()
|
||||
|
||||
def set_name(self, name):
|
||||
self.name = name
|
||||
self.verbose_name = self.verbose_name or name.replace('_', ' ')
|
||||
self.attname, self.column = self.get_attname_column()
|
||||
|
||||
def get_attname_column(self):
|
||||
if isinstance(self.rel, ManyToOneRel):
|
||||
attname = '%s_id' % self.name
|
||||
else:
|
||||
attname = self.name
|
||||
column = self.db_column or attname
|
||||
return attname, column
|
||||
|
||||
def get_cache_name(self):
|
||||
return '_%s_cache' % self.name
|
||||
|
||||
def get_internal_type(self):
|
||||
return self.__class__.__name__
|
||||
|
||||
def pre_save(self, value, add):
|
||||
"Returns field's value just before saving."
|
||||
return value
|
||||
|
||||
def get_db_prep_save(self, value):
|
||||
"Returns field's value prepared for saving into a database."
|
||||
return value
|
||||
|
||||
def get_db_prep_lookup(self, lookup_type, value):
|
||||
"Returns field's value prepared for database lookup."
|
||||
if lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte', 'ne', 'year', 'month', 'day'):
|
||||
return [value]
|
||||
elif lookup_type in ('range', 'in'):
|
||||
return value
|
||||
elif lookup_type in ('contains', 'icontains'):
|
||||
return ["%%%s%%" % prep_for_like_query(value)]
|
||||
elif lookup_type == 'iexact':
|
||||
return [prep_for_like_query(value)]
|
||||
elif lookup_type in ('startswith', 'istartswith'):
|
||||
return ["%s%%" % prep_for_like_query(value)]
|
||||
elif lookup_type in ('endswith', 'iendswith'):
|
||||
return ["%%%s" % prep_for_like_query(value)]
|
||||
elif lookup_type == 'isnull':
|
||||
return []
|
||||
raise TypeError, "Field has invalid lookup: %s" % lookup_type
|
||||
|
||||
def has_default(self):
|
||||
"Returns a boolean of whether this field has a default value."
|
||||
return self.default is not NOT_PROVIDED
|
||||
|
||||
def get_default(self):
|
||||
"Returns the default value for this field."
|
||||
if self.default is not NOT_PROVIDED:
|
||||
if hasattr(self.default, '__get_value__'):
|
||||
return self.default.__get_value__()
|
||||
return self.default
|
||||
if not self.empty_strings_allowed or self.null:
|
||||
return None
|
||||
return ""
|
||||
|
||||
def get_manipulator_field_names(self, name_prefix):
|
||||
"""
|
||||
Returns a list of field names that this object adds to the manipulator.
|
||||
"""
|
||||
return [name_prefix + self.name]
|
||||
|
||||
def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False):
|
||||
"""
|
||||
Returns a list of formfields.FormField instances for this field. It
|
||||
calculates the choices at runtime, not at compile time.
|
||||
|
||||
name_prefix is a prefix to prepend to the "field_name" argument.
|
||||
rel is a boolean specifying whether this field is in a related context.
|
||||
"""
|
||||
params = {'validator_list': self.validator_list[:]}
|
||||
if self.maxlength and not self.choices: # Don't give SelectFields a maxlength parameter.
|
||||
params['maxlength'] = self.maxlength
|
||||
if isinstance(self.rel, ManyToOneRel):
|
||||
params['member_name'] = name_prefix + self.attname
|
||||
if self.rel.raw_id_admin:
|
||||
field_objs = self.get_manipulator_field_objs()
|
||||
params['validator_list'].append(curry(manipulator_valid_rel_key, self, manipulator))
|
||||
else:
|
||||
if self.radio_admin:
|
||||
field_objs = [formfields.RadioSelectField]
|
||||
params['ul_class'] = get_ul_class(self.radio_admin)
|
||||
else:
|
||||
if self.null:
|
||||
field_objs = [formfields.NullSelectField]
|
||||
else:
|
||||
field_objs = [formfields.SelectField]
|
||||
params['choices'] = self.get_choices_default()
|
||||
elif self.choices:
|
||||
if self.radio_admin:
|
||||
field_objs = [formfields.RadioSelectField]
|
||||
params['ul_class'] = get_ul_class(self.radio_admin)
|
||||
else:
|
||||
field_objs = [formfields.SelectField]
|
||||
|
||||
params['choices'] = self.get_choices_default()
|
||||
else:
|
||||
field_objs = self.get_manipulator_field_objs()
|
||||
|
||||
# Add the "unique" validator(s).
|
||||
for field_name_list in opts.unique_together:
|
||||
if field_name_list[0] == self.name:
|
||||
params['validator_list'].append(getattr(manipulator, 'isUnique%s' % '_'.join(field_name_list)))
|
||||
|
||||
# Add the "unique for..." validator(s).
|
||||
if self.unique_for_date:
|
||||
params['validator_list'].append(getattr(manipulator, 'isUnique%sFor%s' % (self.name, self.unique_for_date)))
|
||||
if self.unique_for_month:
|
||||
params['validator_list'].append(getattr(manipulator, 'isUnique%sFor%s' % (self.name, self.unique_for_month)))
|
||||
if self.unique_for_year:
|
||||
params['validator_list'].append(getattr(manipulator, 'isUnique%sFor%s' % (self.name, self.unique_for_year)))
|
||||
if self.unique or (self.primary_key and not rel):
|
||||
params['validator_list'].append(curry(manipulator_validator_unique, self, opts, manipulator))
|
||||
|
||||
# Only add is_required=True if the field cannot be blank. Primary keys
|
||||
# are a special case, and fields in a related context should set this
|
||||
# as False, because they'll be caught by a separate validator --
|
||||
# RequiredIfOtherFieldGiven.
|
||||
params['is_required'] = not self.blank and not self.primary_key and not rel
|
||||
|
||||
# If this field is in a related context, check whether any other fields
|
||||
# in the related object have core=True. If so, add a validator --
|
||||
# RequiredIfOtherFieldsGiven -- to this FormField.
|
||||
if rel and not self.blank and not isinstance(self, AutoField) and not isinstance(self, FileField):
|
||||
# First, get the core fields, if any.
|
||||
core_field_names = []
|
||||
for f in opts.fields:
|
||||
if f.core and f != self:
|
||||
core_field_names.extend(f.get_manipulator_field_names(name_prefix))
|
||||
# Now, if there are any, add the validator to this FormField.
|
||||
if core_field_names:
|
||||
params['validator_list'].append(validators.RequiredIfOtherFieldsGiven(core_field_names, gettext_lazy("This field is required.")))
|
||||
|
||||
# BooleanFields (CheckboxFields) are a special case. They don't take
|
||||
# is_required or validator_list.
|
||||
if isinstance(self, BooleanField):
|
||||
del params['validator_list'], params['is_required']
|
||||
|
||||
# Finally, add the field_names.
|
||||
field_names = self.get_manipulator_field_names(name_prefix)
|
||||
return [man(field_name=field_names[i], **params) for i, man in enumerate(field_objs)]
|
||||
|
||||
def get_manipulator_new_data(self, new_data, rel=False):
|
||||
"""
|
||||
Given the full new_data dictionary (from the manipulator), returns this
|
||||
field's data.
|
||||
"""
|
||||
if rel:
|
||||
return new_data.get(self.name, [self.get_default()])[0]
|
||||
else:
|
||||
val = new_data.get(self.name, self.get_default())
|
||||
if not self.empty_strings_allowed and val == '' and self.null:
|
||||
val = None
|
||||
return val
|
||||
|
||||
def get_choices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH):
|
||||
"Returns a list of tuples used as SelectField choices for this field."
|
||||
first_choice = include_blank and blank_choice or []
|
||||
if self.choices:
|
||||
return first_choice + list(self.choices)
|
||||
rel_obj = self.rel.to
|
||||
return first_choice + [(getattr(x, rel_obj.pk.attname), str(x))
|
||||
for x in rel_obj.get_model_module().get_list(**self.rel.limit_choices_to)]
|
||||
|
||||
def get_choices_default(self):
|
||||
if(self.radio_admin):
|
||||
return self.get_choices(include_blank=self.blank, blank_choice=BLANK_CHOICE_NONE)
|
||||
else:
|
||||
return self.get_choices()
|
||||
|
||||
def _get_val_from_obj(self, obj):
|
||||
if obj:
|
||||
return getattr(obj, self.attname)
|
||||
else:
|
||||
return self.get_default()
|
||||
|
||||
def flatten_data(self, follow, obj=None):
|
||||
"""
|
||||
Returns a dictionary mapping the field's manipulator field names to its
|
||||
"flattened" string values for the admin view. obj is the instance to
|
||||
extract the values from.
|
||||
"""
|
||||
return {self.attname: self._get_val_from_obj(obj)}
|
||||
|
||||
def get_follow(self, override=None):
|
||||
if override != None:
|
||||
return override
|
||||
else:
|
||||
return self.editable
|
||||
|
||||
def bind(self, fieldmapping, original, bound_field_class=BoundField):
|
||||
return bound_field_class(self, fieldmapping, original)
|
||||
|
||||
class AutoField(Field):
|
||||
empty_strings_allowed = False
|
||||
def __init__(self, *args, **kwargs):
|
||||
assert kwargs.get('primary_key', False) is True, "%ss must have primary_key=True." % self.__class__.__name__
|
||||
Field.__init__(self, *args, **kwargs)
|
||||
|
||||
def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False):
|
||||
if not rel:
|
||||
return [] # Don't add a FormField unless it's in a related context.
|
||||
return Field.get_manipulator_fields(self, opts, manipulator, change, name_prefix, rel)
|
||||
|
||||
def get_manipulator_field_objs(self):
|
||||
return [formfields.HiddenField]
|
||||
|
||||
def get_manipulator_new_data(self, new_data, rel=False):
|
||||
if not rel:
|
||||
return None
|
||||
return Field.get_manipulator_new_data(self, new_data, rel)
|
||||
|
||||
class BooleanField(Field):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['blank'] = True
|
||||
Field.__init__(self, *args, **kwargs)
|
||||
|
||||
def get_manipulator_field_objs(self):
|
||||
return [formfields.CheckboxField]
|
||||
|
||||
class CharField(Field):
|
||||
def get_manipulator_field_objs(self):
|
||||
return [formfields.TextField]
|
||||
|
||||
class CommaSeparatedIntegerField(CharField):
|
||||
def get_manipulator_field_objs(self):
|
||||
return [formfields.CommaSeparatedIntegerField]
|
||||
|
||||
class DateField(Field):
|
||||
empty_strings_allowed = False
|
||||
def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs):
|
||||
self.auto_now, self.auto_now_add = auto_now, auto_now_add
|
||||
#HACKs : auto_now_add/auto_now should be done as a default or a pre_save...
|
||||
if auto_now or auto_now_add:
|
||||
kwargs['editable'] = False
|
||||
kwargs['blank'] = True
|
||||
Field.__init__(self, verbose_name, name, **kwargs)
|
||||
|
||||
def get_db_prep_lookup(self, lookup_type, value):
|
||||
if lookup_type == 'range':
|
||||
value = [str(v) for v in value]
|
||||
else:
|
||||
value = str(value)
|
||||
return Field.get_db_prep_lookup(self, lookup_type, value)
|
||||
|
||||
def pre_save(self, value, add):
|
||||
if self.auto_now or (self.auto_now_add and add):
|
||||
return datetime.datetime.now()
|
||||
return value
|
||||
|
||||
# Needed because of horrible auto_now[_add] behaviour wrt. editable
|
||||
def get_follow(self, override=None):
|
||||
if override != None:
|
||||
return override
|
||||
else:
|
||||
return self.editable or self.auto_now or self.auto_now_add
|
||||
|
||||
def get_db_prep_save(self, value):
|
||||
# Casts dates into string format for entry into database.
|
||||
if value is not None:
|
||||
value = value.strftime('%Y-%m-%d')
|
||||
return Field.get_db_prep_save(self, value)
|
||||
|
||||
def get_manipulator_field_objs(self):
|
||||
return [formfields.DateField]
|
||||
|
||||
def flatten_data(self, follow, obj = None):
|
||||
val = self._get_val_from_obj(obj)
|
||||
return {self.attname: (val is not None and val.strftime("%Y-%m-%d") or '')}
|
||||
|
||||
class DateTimeField(DateField):
|
||||
def get_db_prep_save(self, value):
|
||||
# Casts dates into string format for entry into database.
|
||||
if value is not None:
|
||||
# MySQL will throw a warning if microseconds are given, because it
|
||||
# doesn't support microseconds.
|
||||
if settings.DATABASE_ENGINE == 'mysql':
|
||||
value = value.replace(microsecond=0)
|
||||
value = str(value)
|
||||
return Field.get_db_prep_save(self, value)
|
||||
|
||||
def get_manipulator_field_objs(self):
|
||||
return [formfields.DateField, formfields.TimeField]
|
||||
|
||||
def get_manipulator_field_names(self, name_prefix):
|
||||
return [name_prefix + self.name + '_date', name_prefix + self.name + '_time']
|
||||
|
||||
def get_manipulator_new_data(self, new_data, rel=False):
|
||||
date_field, time_field = self.get_manipulator_field_names('')
|
||||
if rel:
|
||||
d = new_data.get(date_field, [None])[0]
|
||||
t = new_data.get(time_field, [None])[0]
|
||||
else:
|
||||
d = new_data.get(date_field, None)
|
||||
t = new_data.get(time_field, None)
|
||||
if d is not None and t is not None:
|
||||
return datetime.datetime.combine(d, t)
|
||||
return self.get_default()
|
||||
|
||||
def flatten_data(self,follow, obj = None):
|
||||
val = self._get_val_from_obj(obj)
|
||||
date_field, time_field = self.get_manipulator_field_names('')
|
||||
return {date_field: (val is not None and val.strftime("%Y-%m-%d") or ''),
|
||||
time_field: (val is not None and val.strftime("%H:%M:%S") or '')}
|
||||
|
||||
class EmailField(Field):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['maxlength'] = 75
|
||||
Field.__init__(self, *args, **kwargs)
|
||||
|
||||
def get_internal_type(self):
|
||||
return "CharField"
|
||||
|
||||
def get_manipulator_field_objs(self):
|
||||
return [formfields.EmailField]
|
||||
|
||||
class FileField(Field):
|
||||
def __init__(self, verbose_name=None, name=None, upload_to='', **kwargs):
|
||||
self.upload_to = upload_to
|
||||
Field.__init__(self, verbose_name, name, **kwargs)
|
||||
|
||||
def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False):
|
||||
field_list = Field.get_manipulator_fields(self, opts, manipulator, change, name_prefix, rel)
|
||||
|
||||
if not self.blank:
|
||||
if rel:
|
||||
# This validator makes sure FileFields work in a related context.
|
||||
class RequiredFileField:
|
||||
def __init__(self, other_field_names, other_file_field_name):
|
||||
self.other_field_names = other_field_names
|
||||
self.other_file_field_name = other_file_field_name
|
||||
self.always_test = True
|
||||
def __call__(self, field_data, all_data):
|
||||
if not all_data.get(self.other_file_field_name, False):
|
||||
c = validators.RequiredIfOtherFieldsGiven(self.other_field_names, gettext_lazy("This field is required."))
|
||||
c(field_data, all_data)
|
||||
# First, get the core fields, if any.
|
||||
core_field_names = []
|
||||
for f in opts.fields:
|
||||
if f.core and f != self:
|
||||
core_field_names.extend(f.get_manipulator_field_names(name_prefix))
|
||||
# Now, if there are any, add the validator to this FormField.
|
||||
if core_field_names:
|
||||
field_list[0].validator_list.append(RequiredFileField(core_field_names, field_list[1].field_name))
|
||||
else:
|
||||
v = validators.RequiredIfOtherFieldNotGiven(field_list[1].field_name, gettext_lazy("This field is required."))
|
||||
v.always_test = True
|
||||
field_list[0].validator_list.append(v)
|
||||
field_list[0].is_required = field_list[1].is_required = False
|
||||
|
||||
# If the raw path is passed in, validate it's under the MEDIA_ROOT.
|
||||
def isWithinMediaRoot(field_data, all_data):
|
||||
f = os.path.abspath(os.path.join(settings.MEDIA_ROOT, field_data))
|
||||
if not f.startswith(os.path.normpath(settings.MEDIA_ROOT)):
|
||||
raise validators.ValidationError, _("Enter a valid filename.")
|
||||
field_list[1].validator_list.append(isWithinMediaRoot)
|
||||
return field_list
|
||||
|
||||
def get_manipulator_field_objs(self):
|
||||
return [formfields.FileUploadField, formfields.HiddenField]
|
||||
|
||||
def get_manipulator_field_names(self, name_prefix):
|
||||
return [name_prefix + self.name + '_file', name_prefix + self.name]
|
||||
|
||||
def save_file(self, new_data, new_object, original_object, change, rel):
|
||||
upload_field_name = self.get_manipulator_field_names('')[0]
|
||||
if new_data.get(upload_field_name, False):
|
||||
func = getattr(new_object, 'save_%s_file' % self.name)
|
||||
if rel:
|
||||
func(new_data[upload_field_name][0]["filename"], new_data[upload_field_name][0]["content"])
|
||||
else:
|
||||
func(new_data[upload_field_name]["filename"], new_data[upload_field_name]["content"])
|
||||
|
||||
def get_directory_name(self):
|
||||
return os.path.normpath(datetime.datetime.now().strftime(self.upload_to))
|
||||
|
||||
def get_filename(self, filename):
|
||||
from django.utils.text import get_valid_filename
|
||||
f = os.path.join(self.get_directory_name(), get_valid_filename(os.path.basename(filename)))
|
||||
return os.path.normpath(f)
|
||||
|
||||
class FilePathField(Field):
|
||||
def __init__(self, verbose_name=None, name=None, path='', match=None, recursive=False, **kwargs):
|
||||
self.path, self.match, self.recursive = path, match, recursive
|
||||
Field.__init__(self, verbose_name, name, **kwargs)
|
||||
|
||||
def get_manipulator_field_objs(self):
|
||||
return [curry(formfields.FilePathField, path=self.path, match=self.match, recursive=self.recursive)]
|
||||
|
||||
class FloatField(Field):
|
||||
empty_strings_allowed = False
|
||||
def __init__(self, verbose_name=None, name=None, max_digits=None, decimal_places=None, **kwargs):
|
||||
self.max_digits, self.decimal_places = max_digits, decimal_places
|
||||
Field.__init__(self, verbose_name, name, **kwargs)
|
||||
|
||||
def get_manipulator_field_objs(self):
|
||||
return [curry(formfields.FloatField, max_digits=self.max_digits, decimal_places=self.decimal_places)]
|
||||
|
||||
class ImageField(FileField):
|
||||
def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, **kwargs):
|
||||
self.width_field, self.height_field = width_field, height_field
|
||||
FileField.__init__(self, verbose_name, name, **kwargs)
|
||||
|
||||
def get_manipulator_field_objs(self):
|
||||
return [formfields.ImageUploadField, formfields.HiddenField]
|
||||
|
||||
def save_file(self, new_data, new_object, original_object, change, rel):
|
||||
FileField.save_file(self, new_data, new_object, original_object, change, rel)
|
||||
# If the image has height and/or width field(s) and they haven't
|
||||
# changed, set the width and/or height field(s) back to their original
|
||||
# values.
|
||||
if change and (self.width_field or self.height_field):
|
||||
if self.width_field:
|
||||
setattr(new_object, self.width_field, getattr(original_object, self.width_field))
|
||||
if self.height_field:
|
||||
setattr(new_object, self.height_field, getattr(original_object, self.height_field))
|
||||
new_object.save()
|
||||
|
||||
class IntegerField(Field):
|
||||
empty_strings_allowed = False
|
||||
def get_manipulator_field_objs(self):
|
||||
return [formfields.IntegerField]
|
||||
|
||||
class IPAddressField(Field):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['maxlength'] = 15
|
||||
Field.__init__(self, *args, **kwargs)
|
||||
|
||||
def get_manipulator_field_objs(self):
|
||||
return [formfields.IPAddressField]
|
||||
|
||||
class NullBooleanField(Field):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['null'] = True
|
||||
Field.__init__(self, *args, **kwargs)
|
||||
|
||||
def get_manipulator_field_objs(self):
|
||||
return [formfields.NullBooleanField]
|
||||
|
||||
class PhoneNumberField(IntegerField):
|
||||
def get_manipulator_field_objs(self):
|
||||
return [formfields.PhoneNumberField]
|
||||
|
||||
class PositiveIntegerField(IntegerField):
|
||||
def get_manipulator_field_objs(self):
|
||||
return [formfields.PositiveIntegerField]
|
||||
|
||||
class PositiveSmallIntegerField(IntegerField):
|
||||
def get_manipulator_field_objs(self):
|
||||
return [formfields.PositiveSmallIntegerField]
|
||||
|
||||
class SlugField(Field):
|
||||
def __init__(self, *args, **kwargs):
|
||||
# Default to a maxlength of 50 but allow overrides.
|
||||
kwargs['maxlength'] = kwargs.get('maxlength', 50)
|
||||
kwargs.setdefault('validator_list', []).append(validators.isSlug)
|
||||
# Set db_index=True unless it's been set manually.
|
||||
if not kwargs.has_key('db_index'):
|
||||
kwargs['db_index'] = True
|
||||
Field.__init__(self, *args, **kwargs)
|
||||
|
||||
def get_manipulator_field_objs(self):
|
||||
return [formfields.TextField]
|
||||
|
||||
class SmallIntegerField(IntegerField):
|
||||
def get_manipulator_field_objs(self):
|
||||
return [formfields.SmallIntegerField]
|
||||
|
||||
class TextField(Field):
|
||||
def get_manipulator_field_objs(self):
|
||||
return [formfields.LargeTextField]
|
||||
|
||||
class TimeField(Field):
|
||||
empty_strings_allowed = False
|
||||
def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs):
|
||||
self.auto_now, self.auto_now_add = auto_now, auto_now_add
|
||||
if auto_now or auto_now_add:
|
||||
kwargs['editable'] = False
|
||||
Field.__init__(self, verbose_name, name, **kwargs)
|
||||
|
||||
def get_db_prep_lookup(self, lookup_type, value):
|
||||
if lookup_type == 'range':
|
||||
value = [str(v) for v in value]
|
||||
else:
|
||||
value = str(value)
|
||||
return Field.get_db_prep_lookup(self, lookup_type, value)
|
||||
|
||||
def pre_save(self, value, add):
|
||||
if self.auto_now or (self.auto_now_add and add):
|
||||
return datetime.datetime.now().time()
|
||||
return value
|
||||
|
||||
def get_db_prep_save(self, value):
|
||||
# Casts dates into string format for entry into database.
|
||||
if value is not None:
|
||||
# MySQL will throw a warning if microseconds are given, because it
|
||||
# doesn't support microseconds.
|
||||
if settings.DATABASE_ENGINE == 'mysql':
|
||||
value = value.replace(microsecond=0)
|
||||
value = str(value)
|
||||
return Field.get_db_prep_save(self, value)
|
||||
|
||||
def get_manipulator_field_objs(self):
|
||||
return [formfields.TimeField]
|
||||
|
||||
def flatten_data(self,follow, obj = None):
|
||||
val = self._get_val_from_obj(obj)
|
||||
return {self.attname: (val is not None and val.strftime("%H:%M:%S") or '')}
|
||||
|
||||
class URLField(Field):
|
||||
def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs):
|
||||
if verify_exists:
|
||||
kwargs.setdefault('validator_list', []).append(validators.isExistingURL)
|
||||
Field.__init__(self, verbose_name, name, **kwargs)
|
||||
|
||||
def get_manipulator_field_objs(self):
|
||||
return [formfields.URLField]
|
||||
|
||||
class USStateField(Field):
|
||||
def get_manipulator_field_objs(self):
|
||||
return [formfields.USStateField]
|
||||
|
||||
class XMLField(TextField):
|
||||
def __init__(self, verbose_name=None, name=None, schema_path=None, **kwargs):
|
||||
self.schema_path = schema_path
|
||||
Field.__init__(self, verbose_name, name, **kwargs)
|
||||
|
||||
def get_internal_type(self):
|
||||
return "TextField"
|
||||
|
||||
def get_manipulator_field_objs(self):
|
||||
return [curry(formfields.XMLLargeTextField, schema_path=self.schema_path)]
|
||||
|
||||
class ForeignKey(Field):
|
||||
empty_strings_allowed = False
|
||||
def __init__(self, to, to_field=None, **kwargs):
|
||||
try:
|
||||
to_name = to._meta.object_name.lower()
|
||||
except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
|
||||
assert to == 'self', "ForeignKey(%r) is invalid. First parameter to ForeignKey must be either a model or the string %r" % (to, RECURSIVE_RELATIONSHIP_CONSTANT)
|
||||
else:
|
||||
to_field = to_field or to._meta.pk.name
|
||||
kwargs['verbose_name'] = kwargs.get('verbose_name', '')
|
||||
|
||||
if kwargs.has_key('edit_inline_type'):
|
||||
import warnings
|
||||
warnings.warn("edit_inline_type is deprecated. Use edit_inline instead.")
|
||||
kwargs['edit_inline'] = kwargs.pop('edit_inline_type')
|
||||
|
||||
kwargs['rel'] = ManyToOneRel(to, to_field,
|
||||
num_in_admin=kwargs.pop('num_in_admin', 3),
|
||||
min_num_in_admin=kwargs.pop('min_num_in_admin', None),
|
||||
max_num_in_admin=kwargs.pop('max_num_in_admin', None),
|
||||
num_extra_on_change=kwargs.pop('num_extra_on_change', 1),
|
||||
edit_inline=kwargs.pop('edit_inline', False),
|
||||
related_name=kwargs.pop('related_name', None),
|
||||
limit_choices_to=kwargs.pop('limit_choices_to', None),
|
||||
lookup_overrides=kwargs.pop('lookup_overrides', None),
|
||||
raw_id_admin=kwargs.pop('raw_id_admin', False))
|
||||
Field.__init__(self, **kwargs)
|
||||
|
||||
def get_manipulator_field_objs(self):
|
||||
rel_field = self.rel.get_related_field()
|
||||
if self.rel.raw_id_admin and not isinstance(rel_field, AutoField):
|
||||
return rel_field.get_manipulator_field_objs()
|
||||
else:
|
||||
return [formfields.IntegerField]
|
||||
|
||||
def get_db_prep_save(self,value):
|
||||
if value == '' or value == None:
|
||||
return None
|
||||
else:
|
||||
return self.rel.get_related_field().get_db_prep_save(value)
|
||||
|
||||
def flatten_data(self, follow, obj=None):
|
||||
if not obj:
|
||||
# In required many-to-one fields with only one available choice,
|
||||
# select that one available choice. Note: For SelectFields
|
||||
# (radio_admin=False), we have to check that the length of choices
|
||||
# is *2*, not 1, because SelectFields always have an initial
|
||||
# "blank" value. Otherwise (radio_admin=True), we check that the
|
||||
# length is 1.
|
||||
if not self.blank and (not self.rel.raw_id_admin or self.choices):
|
||||
choice_list = self.get_choices_default()
|
||||
if self.radio_admin and len(choice_list) == 1:
|
||||
return {self.attname: choice_list[0][0]}
|
||||
if not self.radio_admin and len(choice_list) == 2:
|
||||
return {self.attname: choice_list[1][0]}
|
||||
return Field.flatten_data(self, follow, obj)
|
||||
|
||||
class ManyToManyField(Field):
|
||||
def __init__(self, to, **kwargs):
|
||||
kwargs['verbose_name'] = kwargs.get('verbose_name', to._meta.verbose_name_plural)
|
||||
kwargs['rel'] = ManyToManyRel(to, kwargs.pop('singular', None),
|
||||
num_in_admin=kwargs.pop('num_in_admin', 0),
|
||||
related_name=kwargs.pop('related_name', None),
|
||||
filter_interface=kwargs.pop('filter_interface', None),
|
||||
limit_choices_to=kwargs.pop('limit_choices_to', None),
|
||||
raw_id_admin=kwargs.pop('raw_id_admin', False))
|
||||
if kwargs["rel"].raw_id_admin:
|
||||
kwargs.setdefault("validator_list", []).append(self.isValidIDList)
|
||||
Field.__init__(self, **kwargs)
|
||||
|
||||
def get_manipulator_field_objs(self):
|
||||
if self.rel.raw_id_admin:
|
||||
return [formfields.RawIdAdminField]
|
||||
else:
|
||||
choices = self.get_choices_default()
|
||||
return [curry(formfields.SelectMultipleField, size=min(max(len(choices), 5), 15), choices=choices)]
|
||||
|
||||
def get_choices_default(self):
|
||||
return Field.get_choices(self, include_blank=False)
|
||||
|
||||
def get_m2m_db_table(self, original_opts):
|
||||
"Returns the name of the many-to-many 'join' table."
|
||||
return '%s_%s' % (original_opts.db_table, self.name)
|
||||
|
||||
def isValidIDList(self, field_data, all_data):
|
||||
"Validates that the value is a valid list of foreign keys"
|
||||
mod = self.rel.to.get_model_module()
|
||||
try:
|
||||
pks = map(int, field_data.split(','))
|
||||
except ValueError:
|
||||
# the CommaSeparatedIntegerField validator will catch this error
|
||||
return
|
||||
objects = mod.get_in_bulk(pks)
|
||||
if len(objects) != len(pks):
|
||||
badkeys = [k for k in pks if k not in objects]
|
||||
raise validators.ValidationError, ngettext("Please enter valid %(self)s IDs. The value %(value)r is invalid.",
|
||||
"Please enter valid %(self)s IDs. The values %(value)r are invalid.", len(badkeys)) % {
|
||||
'self': self.verbose_name,
|
||||
'value': len(badkeys) == 1 and badkeys[0] or tuple(badkeys),
|
||||
}
|
||||
|
||||
def flatten_data(self, follow, obj = None):
|
||||
new_data = {}
|
||||
if obj:
|
||||
get_list_func = getattr(obj, 'get_%s_list' % self.rel.singular)
|
||||
instance_ids = [getattr(instance, self.rel.to.pk.attname) for instance in get_list_func()]
|
||||
if self.rel.raw_id_admin:
|
||||
new_data[self.name] = ",".join([str(id) for id in instance_ids])
|
||||
else:
|
||||
new_data[self.name] = instance_ids
|
||||
else:
|
||||
# In required many-to-many fields with only one available choice,
|
||||
# select that one available choice.
|
||||
if not self.blank and not self.rel.edit_inline and not self.rel.raw_id_admin:
|
||||
choices_list = self.get_choices_default()
|
||||
if len(choices_list) == 1:
|
||||
new_data[self.name] = [choices_list[0][0]]
|
||||
return new_data
|
||||
|
||||
class OneToOneField(IntegerField):
|
||||
def __init__(self, to, to_field=None, **kwargs):
|
||||
kwargs['verbose_name'] = kwargs.get('verbose_name', 'ID')
|
||||
to_field = to_field or to._meta.pk.name
|
||||
|
||||
if kwargs.has_key('edit_inline_type'):
|
||||
import warnings
|
||||
warnings.warn("edit_inline_type is deprecated. Use edit_inline instead.")
|
||||
kwargs['edit_inline'] = kwargs.pop('edit_inline_type')
|
||||
|
||||
kwargs['rel'] = OneToOneRel(to, to_field,
|
||||
num_in_admin=kwargs.pop('num_in_admin', 0),
|
||||
edit_inline=kwargs.pop('edit_inline', False),
|
||||
related_name=kwargs.pop('related_name', None),
|
||||
limit_choices_to=kwargs.pop('limit_choices_to', None),
|
||||
lookup_overrides=kwargs.pop('lookup_overrides', None),
|
||||
raw_id_admin=kwargs.pop('raw_id_admin', False))
|
||||
kwargs['primary_key'] = True
|
||||
IntegerField.__init__(self, **kwargs)
|
||||
|
||||
class ManyToOneRel:
|
||||
def __init__(self, to, field_name, num_in_admin=3, min_num_in_admin=None,
|
||||
max_num_in_admin=None, num_extra_on_change=1, edit_inline=False,
|
||||
related_name=None, limit_choices_to=None, lookup_overrides=None, raw_id_admin=False):
|
||||
try:
|
||||
self.to = to._meta
|
||||
except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
|
||||
assert to == RECURSIVE_RELATIONSHIP_CONSTANT, "'to' must be either a model or the string '%s'" % RECURSIVE_RELATIONSHIP_CONSTANT
|
||||
self.to = to
|
||||
self.field_name = field_name
|
||||
self.num_in_admin, self.edit_inline = num_in_admin, edit_inline
|
||||
self.min_num_in_admin, self.max_num_in_admin = min_num_in_admin, max_num_in_admin
|
||||
self.num_extra_on_change, self.related_name = num_extra_on_change, related_name
|
||||
self.limit_choices_to = limit_choices_to or {}
|
||||
self.lookup_overrides = lookup_overrides or {}
|
||||
self.raw_id_admin = raw_id_admin
|
||||
|
||||
def get_related_field(self):
|
||||
"Returns the Field in the 'to' object to which this relationship is tied."
|
||||
return self.to.get_field(self.field_name)
|
||||
|
||||
class ManyToManyRel:
|
||||
def __init__(self, to, singular=None, num_in_admin=0, related_name=None,
|
||||
filter_interface=None, limit_choices_to=None, raw_id_admin=False):
|
||||
self.to = to._meta
|
||||
self.singular = singular or to._meta.object_name.lower()
|
||||
self.num_in_admin = num_in_admin
|
||||
self.related_name = related_name
|
||||
self.filter_interface = filter_interface
|
||||
self.limit_choices_to = limit_choices_to or {}
|
||||
self.edit_inline = False
|
||||
self.raw_id_admin = raw_id_admin
|
||||
assert not (self.raw_id_admin and self.filter_interface), "ManyToManyRels may not use both raw_id_admin and filter_interface"
|
||||
|
||||
class OneToOneRel(ManyToOneRel):
|
||||
def __init__(self, to, field_name, num_in_admin=0, edit_inline=False,
|
||||
related_name=None, limit_choices_to=None, lookup_overrides=None,
|
||||
raw_id_admin=False):
|
||||
self.to, self.field_name = to._meta, field_name
|
||||
self.num_in_admin, self.edit_inline = num_in_admin, edit_inline
|
||||
self.related_name = related_name
|
||||
self.limit_choices_to = limit_choices_to or {}
|
||||
self.lookup_overrides = lookup_overrides or {}
|
||||
self.raw_id_admin = raw_id_admin
|
||||
|
||||
class BoundFieldLine(object):
|
||||
def __init__(self, field_line, field_mapping, original, bound_field_class=BoundField):
|
||||
self.bound_fields = [field.bind(field_mapping, original, bound_field_class) for field in field_line]
|
||||
|
||||
def __iter__(self):
|
||||
for bound_field in self.bound_fields:
|
||||
yield bound_field
|
||||
|
||||
def __len__(self):
|
||||
return len(self.bound_fields)
|
||||
|
||||
class FieldLine(object):
|
||||
def __init__(self, field_locator_func, linespec):
|
||||
if isinstance(linespec, basestring):
|
||||
self.fields = [field_locator_func(linespec)]
|
||||
else:
|
||||
self.fields = [field_locator_func(field_name) for field_name in linespec]
|
||||
|
||||
def bind(self, field_mapping, original, bound_field_line_class=BoundFieldLine):
|
||||
return bound_field_line_class(self, field_mapping, original)
|
||||
|
||||
def __iter__(self):
|
||||
for field in self.fields:
|
||||
yield field
|
||||
|
||||
def __len__(self):
|
||||
return len(self.fields)
|
||||
|
||||
class BoundFieldSet(object):
|
||||
def __init__(self, field_set, field_mapping, original, bound_field_line_class=BoundFieldLine):
|
||||
self.name = field_set.name
|
||||
self.classes = field_set.classes
|
||||
self.bound_field_lines = [field_line.bind(field_mapping,original, bound_field_line_class) for field_line in field_set]
|
||||
|
||||
def __iter__(self):
|
||||
for bound_field_line in self.bound_field_lines:
|
||||
yield bound_field_line
|
||||
|
||||
def __len__(self):
|
||||
return len(self.bound_field_lines)
|
||||
|
||||
class FieldSet(object):
|
||||
def __init__(self, name, classes, field_locator_func, line_specs):
|
||||
self.name = name
|
||||
self.field_lines = [FieldLine(field_locator_func, line_spec) for line_spec in line_specs]
|
||||
self.classes = classes
|
||||
|
||||
def __repr__(self):
|
||||
return "FieldSet:(%s,%s)" % (self.name, self.field_lines)
|
||||
|
||||
def bind(self, field_mapping, original, bound_field_set_class=BoundFieldSet):
|
||||
return bound_field_set_class(self, field_mapping, original)
|
||||
|
||||
def __iter__(self):
|
||||
for field_line in self.field_lines:
|
||||
yield field_line
|
||||
|
||||
def __len__(self):
|
||||
return len(self.field_lines)
|
||||
|
||||
class Admin:
|
||||
def __init__(self, fields=None, js=None, list_display=None, list_filter=None, date_hierarchy=None,
|
||||
save_as=False, ordering=None, search_fields=None, save_on_top=False, list_select_related=False):
|
||||
self.fields = fields
|
||||
self.js = js or []
|
||||
self.list_display = list_display or ['__repr__']
|
||||
self.list_filter = list_filter or []
|
||||
self.date_hierarchy = date_hierarchy
|
||||
self.save_as, self.ordering = save_as, ordering
|
||||
self.search_fields = search_fields or []
|
||||
self.save_on_top = save_on_top
|
||||
self.list_select_related = list_select_related
|
||||
|
||||
def get_field_sets(self, opts):
|
||||
if self.fields is None:
|
||||
field_struct = ((None, {
|
||||
'fields': [f.name for f in opts.fields + opts.many_to_many if f.editable and not isinstance(f, AutoField)]
|
||||
}),)
|
||||
else:
|
||||
field_struct = self.fields
|
||||
new_fieldset_list = []
|
||||
for fieldset in field_struct:
|
||||
name = fieldset[0]
|
||||
fs_options = fieldset[1]
|
||||
classes = fs_options.get('classes', ())
|
||||
line_specs = fs_options['fields']
|
||||
new_fieldset_list.append(FieldSet(name, classes, opts.get_field, line_specs))
|
||||
return new_fieldset_list
|
||||
@@ -6,20 +6,17 @@ class InvalidPage(Exception):
|
||||
|
||||
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
|
||||
This class makes pagination easy. Feed it a QuerySet, 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
|
||||
def __init__(self, query_set, num_per_page):
|
||||
self.query_set = query_set
|
||||
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
|
||||
|
||||
@@ -30,14 +27,17 @@ class ObjectPaginator:
|
||||
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)
|
||||
limit = self.num_per_page + 1
|
||||
offset = page_number * self.num_per_page
|
||||
|
||||
object_list = list(self.query_set[offset:offset+limit])
|
||||
|
||||
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]
|
||||
|
||||
@@ -45,11 +45,8 @@ class ObjectPaginator:
|
||||
"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 != [])
|
||||
offset = (page_number + 1) * self.num_per_page
|
||||
self._has_next[page_number] = len(self.query_set[offset:offset+1]) > 0
|
||||
else:
|
||||
self._has_next[page_number] = page_number < (self.pages - 1)
|
||||
return self._has_next[page_number]
|
||||
@@ -59,12 +56,7 @@ class ObjectPaginator:
|
||||
|
||||
def _get_hits(self):
|
||||
if self._hits is None:
|
||||
order_args = copy(self.args)
|
||||
if order_args.has_key('order_by'):
|
||||
del order_args['order_by']
|
||||
if order_args.has_key('select_related'):
|
||||
del order_args['select_related']
|
||||
self._hits = getattr(self.module, self.count_method)(**order_args)
|
||||
self._hits = self.query_set.count()
|
||||
return self._hits
|
||||
|
||||
def _get_pages(self):
|
||||
|
||||
@@ -242,7 +242,7 @@ class ServerHandler:
|
||||
|
||||
# Error handling (also per-subclass or per-instance)
|
||||
traceback_limit = None # Print entire traceback to self.get_stderr()
|
||||
error_status = "500 Dude, this is whack!"
|
||||
error_status = "500 INTERNAL SERVER ERROR"
|
||||
error_headers = [('Content-Type','text/plain')]
|
||||
|
||||
# State variables (don't mess with these)
|
||||
@@ -383,7 +383,7 @@ class ServerHandler:
|
||||
assert type(data) is StringType,"write() argument must be string"
|
||||
|
||||
if not self.status:
|
||||
raise AssertionError("write() before start_response()")
|
||||
raise AssertionError("write() before start_response()")
|
||||
|
||||
elif not self.headers_sent:
|
||||
# Before the first output, send the stored headers
|
||||
@@ -532,8 +532,8 @@ class WSGIRequestHandler(BaseHTTPRequestHandler):
|
||||
server_version = "WSGIServer/" + __version__
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
from django.conf.settings import ADMIN_MEDIA_PREFIX
|
||||
self.admin_media_prefix = ADMIN_MEDIA_PREFIX
|
||||
from django.conf import settings
|
||||
self.admin_media_prefix = settings.ADMIN_MEDIA_PREFIX
|
||||
BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
|
||||
|
||||
def get_environ(self):
|
||||
|
||||
3
django/core/signals.py
Normal file
3
django/core/signals.py
Normal file
@@ -0,0 +1,3 @@
|
||||
request_started = object()
|
||||
request_finished = object()
|
||||
got_request_exception = object()
|
||||
@@ -1,920 +0,0 @@
|
||||
"""
|
||||
This is the Django template system.
|
||||
|
||||
How it works:
|
||||
|
||||
The Lexer.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
|
||||
from inspect import getargspec
|
||||
from django.utils.functional import curry
|
||||
from django.conf.settings import DEFAULT_CHARSET, TEMPLATE_DEBUG, TEMPLATE_STRING_IF_INVALID
|
||||
|
||||
__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_.'
|
||||
|
||||
# what to report as the origin for templates that come from non-loader sources
|
||||
# (e.g. strings)
|
||||
UNKNOWN_SOURCE="<unknown source>"
|
||||
|
||||
# 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 dictionary of libraries that have been loaded using get_library
|
||||
libraries = {}
|
||||
# global list of libraries to load by default for a new parser
|
||||
builtins = []
|
||||
|
||||
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 InvalidTemplateLibrary(Exception):
|
||||
pass
|
||||
|
||||
class Origin(object):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def reload(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class StringOrigin(Origin):
|
||||
def __init__(self, source):
|
||||
super(StringOrigin, self).__init__(UNKNOWN_SOURCE)
|
||||
self.source = source
|
||||
|
||||
def reload(self):
|
||||
return self.source
|
||||
|
||||
class Template:
|
||||
def __init__(self, template_string, origin=None):
|
||||
"Compilation stage"
|
||||
if TEMPLATE_DEBUG and origin == None:
|
||||
origin = StringOrigin(template_string)
|
||||
# Could do some crazy stack-frame stuff to record where this string
|
||||
# came from...
|
||||
self.nodelist = compile_string(template_string, origin)
|
||||
|
||||
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, origin):
|
||||
"Compiles template_string into NodeList ready for rendering"
|
||||
lexer = lexer_factory(template_string, origin)
|
||||
parser = parser_factory(lexer.tokenize())
|
||||
return parser.parse()
|
||||
|
||||
class Context:
|
||||
"A stack container for variable context"
|
||||
def __init__(self, dict=None):
|
||||
dict = dict or {}
|
||||
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 TEMPLATE_STRING_IF_INVALID
|
||||
|
||||
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 get(self, key, otherwise):
|
||||
for dict in self.dicts:
|
||||
if dict.has_key(key):
|
||||
return dict[key]
|
||||
return otherwise
|
||||
|
||||
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 __repr__(self):
|
||||
return '<%s token: "%s">' % (
|
||||
{TOKEN_TEXT: 'Text', TOKEN_VAR: 'Var', TOKEN_BLOCK: 'Block'}[self.token_type],
|
||||
self.contents[:].replace('\n', '')
|
||||
)
|
||||
|
||||
class Lexer(object):
|
||||
def __init__(self, template_string, origin):
|
||||
self.template_string = template_string
|
||||
self.origin = origin
|
||||
|
||||
def tokenize(self):
|
||||
"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(self.template_string))
|
||||
return map(self.create_token, bits)
|
||||
|
||||
def create_token(self,token_string):
|
||||
"Convert the given token string into a new Token object and return it"
|
||||
if token_string.startswith(VARIABLE_TAG_START):
|
||||
token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip())
|
||||
elif token_string.startswith(BLOCK_TAG_START):
|
||||
token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip())
|
||||
else:
|
||||
token = Token(TOKEN_TEXT, token_string)
|
||||
return token
|
||||
|
||||
class DebugLexer(Lexer):
|
||||
def __init__(self, template_string, origin):
|
||||
super(DebugLexer, self).__init__(template_string, origin)
|
||||
|
||||
def tokenize(self):
|
||||
"Return a list of tokens from a given template_string"
|
||||
token_tups, upto = [], 0
|
||||
for match in tag_re.finditer(self.template_string):
|
||||
start, end = match.span()
|
||||
if start > upto:
|
||||
token_tups.append( (self.template_string[upto:start], (upto, start)) )
|
||||
upto = start
|
||||
token_tups.append( (self.template_string[start:end], (start,end)) )
|
||||
upto = end
|
||||
last_bit = self.template_string[upto:]
|
||||
if last_bit:
|
||||
token_tups.append( (last_bit, (upto, upto + len(last_bit))) )
|
||||
return [self.create_token(tok, (self.origin, loc)) for tok, loc in token_tups]
|
||||
|
||||
def create_token(self, token_string, source):
|
||||
token = super(DebugLexer, self).create_token(token_string)
|
||||
token.source = source
|
||||
return token
|
||||
|
||||
class Parser(object):
|
||||
def __init__(self, tokens):
|
||||
self.tokens = tokens
|
||||
self.tags = {}
|
||||
self.filters = {}
|
||||
for lib in builtins:
|
||||
self.add_library(lib)
|
||||
|
||||
def parse(self, parse_until=[]):
|
||||
nodelist = self.create_nodelist()
|
||||
while self.tokens:
|
||||
token = self.next_token()
|
||||
if token.token_type == TOKEN_TEXT:
|
||||
self.extend_nodelist(nodelist, TextNode(token.contents), token)
|
||||
elif token.token_type == TOKEN_VAR:
|
||||
if not token.contents:
|
||||
self.empty_variable(token)
|
||||
filter_expression = self.compile_filter(token.contents)
|
||||
var_node = self.create_variable_node(filter_expression)
|
||||
self.extend_nodelist(nodelist, var_node,token)
|
||||
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:
|
||||
self.empty_block_tag(token)
|
||||
# execute callback function for this tag and append resulting node
|
||||
self.enter_command(command, token)
|
||||
try:
|
||||
compile_func = self.tags[command]
|
||||
except KeyError:
|
||||
self.invalid_block_tag(token, command)
|
||||
try:
|
||||
compiled_result = compile_func(self, token)
|
||||
except TemplateSyntaxError, e:
|
||||
if not self.compile_function_error(token, e):
|
||||
raise
|
||||
self.extend_nodelist(nodelist, compiled_result, token)
|
||||
self.exit_command()
|
||||
if parse_until:
|
||||
self.unclosed_block_tag(parse_until)
|
||||
return nodelist
|
||||
|
||||
def skip_past(self, endtag):
|
||||
while self.tokens:
|
||||
token = self.next_token()
|
||||
if token.token_type == TOKEN_BLOCK and token.contents == endtag:
|
||||
return
|
||||
self.unclosed_block_tag([endtag])
|
||||
|
||||
def create_variable_node(self, filter_expression):
|
||||
return VariableNode(filter_expression)
|
||||
|
||||
def create_nodelist(self):
|
||||
return NodeList()
|
||||
|
||||
def extend_nodelist(self, nodelist, node, token):
|
||||
nodelist.append(node)
|
||||
|
||||
def enter_command(self, command, token):
|
||||
pass
|
||||
|
||||
def exit_command(self):
|
||||
pass
|
||||
|
||||
def error(self, token, msg ):
|
||||
return TemplateSyntaxError(msg)
|
||||
|
||||
def empty_variable(self, token):
|
||||
raise self.error( token, "Empty variable tag")
|
||||
|
||||
def empty_block_tag(self, token):
|
||||
raise self.error( token, "Empty block tag")
|
||||
|
||||
def invalid_block_tag(self, token, command):
|
||||
raise self.error( token, "Invalid block tag: '%s'" % command)
|
||||
|
||||
def unclosed_block_tag(self, parse_until):
|
||||
raise self.error(None, "Unclosed tags: %s " % ', '.join(parse_until))
|
||||
|
||||
def compile_function_error(self, token, e):
|
||||
pass
|
||||
|
||||
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]
|
||||
|
||||
def add_library(self, lib):
|
||||
self.tags.update(lib.tags)
|
||||
self.filters.update(lib.filters)
|
||||
|
||||
def compile_filter(self,token):
|
||||
"Convenient wrapper for FilterExpression"
|
||||
return FilterExpression(token, self)
|
||||
|
||||
def find_filter(self, filter_name):
|
||||
if self.filters.has_key(filter_name):
|
||||
return self.filters[filter_name]
|
||||
else:
|
||||
raise TemplateSyntaxError, "Invalid filter: '%s'" % filter_name
|
||||
|
||||
class DebugParser(Parser):
|
||||
def __init__(self, lexer):
|
||||
super(DebugParser, self).__init__(lexer)
|
||||
self.command_stack = []
|
||||
|
||||
def enter_command(self, command, token):
|
||||
self.command_stack.append( (command, token.source) )
|
||||
|
||||
def exit_command(self):
|
||||
self.command_stack.pop()
|
||||
|
||||
def error(self, token, msg):
|
||||
return self.source_error(token.source, msg)
|
||||
|
||||
def source_error(self, source,msg):
|
||||
e = TemplateSyntaxError(msg)
|
||||
e.source = source
|
||||
return e
|
||||
|
||||
def create_nodelist(self):
|
||||
return DebugNodeList()
|
||||
|
||||
def create_variable_node(self, contents):
|
||||
return DebugVariableNode(contents)
|
||||
|
||||
def extend_nodelist(self, nodelist, node, token):
|
||||
node.source = token.source
|
||||
super(DebugParser, self).extend_nodelist(nodelist, node, token)
|
||||
|
||||
def unclosed_block_tag(self, parse_until):
|
||||
(command, source) = self.command_stack.pop()
|
||||
msg = "Unclosed tag '%s'. Looking for one of: %s " % (command, ', '.join(parse_until))
|
||||
raise self.source_error( source, msg)
|
||||
|
||||
def compile_function_error(self, token, e):
|
||||
if not hasattr(e, 'source'):
|
||||
e.source = token.source
|
||||
|
||||
if TEMPLATE_DEBUG:
|
||||
lexer_factory = DebugLexer
|
||||
parser_factory = DebugParser
|
||||
else:
|
||||
lexer_factory = Lexer
|
||||
parser_factory = Parser
|
||||
|
||||
class TokenParser:
|
||||
"""
|
||||
Subclass this and implement the top() method to parse a template line. When
|
||||
instantiating the parser, pass in the line from the Django template parser.
|
||||
|
||||
The parser's "tagname" instance-variable stores the name of the tag that
|
||||
the filter was called with.
|
||||
"""
|
||||
def __init__(self, subject):
|
||||
self.subject = subject
|
||||
self.pointer = 0
|
||||
self.backout = []
|
||||
self.tagname = self.tag()
|
||||
|
||||
def top(self):
|
||||
"Overload this method to do the actual parsing and return the result."
|
||||
raise NotImplemented
|
||||
|
||||
def more(self):
|
||||
"Returns True if there is more stuff in the tag."
|
||||
return self.pointer < len(self.subject)
|
||||
|
||||
def back(self):
|
||||
"Undoes the last microparser. Use this for lookahead and backtracking."
|
||||
if not len(self.backout):
|
||||
raise TemplateSyntaxError, "back called without some previous parsing"
|
||||
self.pointer = self.backout.pop()
|
||||
|
||||
def tag(self):
|
||||
"A microparser that just returns the next tag from the line."
|
||||
subject = self.subject
|
||||
i = self.pointer
|
||||
if i >= len(subject):
|
||||
raise TemplateSyntaxError, "expected another tag, found end of string: %s" % subject
|
||||
p = i
|
||||
while i < len(subject) and subject[i] not in (' ', '\t'):
|
||||
i += 1
|
||||
s = subject[p:i]
|
||||
while i < len(subject) and subject[i] in (' ', '\t'):
|
||||
i += 1
|
||||
self.backout.append(self.pointer)
|
||||
self.pointer = i
|
||||
return s
|
||||
|
||||
def value(self):
|
||||
"A microparser that parses for a value: some string constant or variable name."
|
||||
subject = self.subject
|
||||
i = self.pointer
|
||||
if i >= len(subject):
|
||||
raise TemplateSyntaxError, "Searching for value. Expected another value but found end of string: %s" % subject
|
||||
if subject[i] in ('"', "'"):
|
||||
p = i
|
||||
i += 1
|
||||
while i < len(subject) and subject[i] != subject[p]:
|
||||
i += 1
|
||||
if i >= len(subject):
|
||||
raise TemplateSyntaxError, "Searching for value. Unexpected end of string in column %d: %s" % subject
|
||||
i += 1
|
||||
res = subject[p:i]
|
||||
while i < len(subject) and subject[i] in (' ', '\t'):
|
||||
i += 1
|
||||
self.backout.append(self.pointer)
|
||||
self.pointer = i
|
||||
return res
|
||||
else:
|
||||
p = i
|
||||
while i < len(subject) and subject[i] not in (' ', '\t'):
|
||||
if subject[i] in ('"', "'"):
|
||||
c = subject[i]
|
||||
i += 1
|
||||
while i < len(subject) and subject[i] != c:
|
||||
i += 1
|
||||
if i >= len(subject):
|
||||
raise TemplateSyntaxError, "Searching for value. Unexpected end of string in column %d: %s" % subject
|
||||
i += 1
|
||||
s = subject[p:i]
|
||||
while i < len(subject) and subject[i] in (' ', '\t'):
|
||||
i += 1
|
||||
self.backout.append(self.pointer)
|
||||
self.pointer = i
|
||||
return s
|
||||
|
||||
|
||||
|
||||
|
||||
filter_raw_string = r"""
|
||||
^%(i18n_open)s"(?P<i18n_constant>%(str)s)"%(i18n_close)s|
|
||||
^"(?P<constant>%(str)s)"|
|
||||
^(?P<var>[%(var_chars)s]+)|
|
||||
(?:%(filter_sep)s
|
||||
(?P<filter_name>\w+)
|
||||
(?:%(arg_sep)s
|
||||
(?:
|
||||
%(i18n_open)s"(?P<i18n_arg>%(str)s)"%(i18n_close)s|
|
||||
"(?P<constant_arg>%(str)s)"|
|
||||
(?P<var_arg>[%(var_chars)s]+)
|
||||
)
|
||||
)?
|
||||
)""" % {
|
||||
'str': r"""[^"\\]*(?:\\.[^"\\]*)*""",
|
||||
'var_chars': "A-Za-z0-9\_\." ,
|
||||
'filter_sep': re.escape(FILTER_SEPARATOR),
|
||||
'arg_sep': re.escape(FILTER_ARGUMENT_SEPARATOR),
|
||||
'i18n_open' : re.escape("_("),
|
||||
'i18n_close' : re.escape(")"),
|
||||
}
|
||||
|
||||
filter_raw_string = filter_raw_string.replace("\n", "").replace(" ", "")
|
||||
filter_re = re.compile(filter_raw_string)
|
||||
|
||||
class FilterExpression(object):
|
||||
"""
|
||||
Parses 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, token, parser):
|
||||
self.token = token
|
||||
matches = filter_re.finditer(token)
|
||||
var = None
|
||||
filters = []
|
||||
upto = 0
|
||||
for match in matches:
|
||||
start = match.start()
|
||||
if upto != start:
|
||||
raise TemplateSyntaxError, "Could not parse some characters: %s|%s|%s" % \
|
||||
(token[:upto], token[upto:start], token[start:])
|
||||
if var == None:
|
||||
var, constant, i18n_constant = match.group("var", "constant", "i18n_constant")
|
||||
if i18n_constant:
|
||||
var = '"%s"' % _(i18n_constant)
|
||||
elif constant:
|
||||
var = '"%s"' % constant
|
||||
upto = match.end()
|
||||
if var == None:
|
||||
raise TemplateSyntaxError, "Could not find variable at start of %s" % token
|
||||
elif var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or var[0] == '_':
|
||||
raise TemplateSyntaxError, "Variables and attributes may not begin with underscores: '%s'" % var
|
||||
else:
|
||||
filter_name = match.group("filter_name")
|
||||
args = []
|
||||
constant_arg, i18n_arg, var_arg = match.group("constant_arg", "i18n_arg", "var_arg")
|
||||
if i18n_arg:
|
||||
args.append((False, _(i18n_arg.replace('\\', ''))))
|
||||
elif constant_arg:
|
||||
args.append((False, constant_arg.replace('\\', '')))
|
||||
elif var_arg:
|
||||
args.append((True, var_arg))
|
||||
filter_func = parser.find_filter(filter_name)
|
||||
self.args_check(filter_name,filter_func, args)
|
||||
filters.append( (filter_func,args))
|
||||
upto = match.end()
|
||||
if upto != len(token):
|
||||
raise TemplateSyntaxError, "Could not parse the remainder: %s" % token[upto:]
|
||||
self.var , self.filters = var, filters
|
||||
|
||||
def resolve(self, context):
|
||||
try:
|
||||
obj = resolve_variable(self.var, context)
|
||||
except VariableDoesNotExist:
|
||||
obj = TEMPLATE_STRING_IF_INVALID
|
||||
for func, args in self.filters:
|
||||
arg_vals = []
|
||||
for lookup, arg in args:
|
||||
if not lookup:
|
||||
arg_vals.append(arg)
|
||||
else:
|
||||
arg_vals.append(resolve_variable(arg, context))
|
||||
obj = func(obj, *arg_vals)
|
||||
return obj
|
||||
|
||||
def args_check(name, func, provided):
|
||||
provided = list(provided)
|
||||
plen = len(provided)
|
||||
(args, varargs, varkw, defaults) = getargspec(func)
|
||||
# First argument is filter input.
|
||||
args.pop(0)
|
||||
if defaults:
|
||||
nondefs = args[:-len(defaults)]
|
||||
else:
|
||||
nondefs = args
|
||||
# Args without defaults must be provided.
|
||||
try:
|
||||
for arg in nondefs:
|
||||
provided.pop(0)
|
||||
except IndexError:
|
||||
# Not enough
|
||||
raise TemplateSyntaxError, "%s requires %d arguments, %d provided" % (name, len(nondefs), plen)
|
||||
|
||||
# Defaults can be overridden.
|
||||
defaults = defaults and list(defaults) or []
|
||||
try:
|
||||
for parg in provided:
|
||||
defaults.pop(0)
|
||||
except IndexError:
|
||||
# Too many.
|
||||
raise TemplateSyntaxError, "%s requires %d arguments, %d provided" % (name, len(nondefs), plen)
|
||||
|
||||
return True
|
||||
args_check = staticmethod(args_check)
|
||||
|
||||
def __str__(self):
|
||||
return self.token
|
||||
|
||||
def resolve_variable(path, context):
|
||||
"""
|
||||
Returns the resolved variable, which may contain attribute syntax, within
|
||||
the given context. The variable may be a hard-coded string (if it begins
|
||||
and ends with single or double quote marks), or an integer or float literal.
|
||||
|
||||
>>> 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 '.')
|
||||
"""
|
||||
if path[0] in '0123456789':
|
||||
number_type = '.' in path and float or int
|
||||
try:
|
||||
current = number_type(path)
|
||||
except ValueError:
|
||||
current = TEMPLATE_STRING_IF_INVALID
|
||||
elif path[0] in ('"', "'") and path[0] == path[-1]:
|
||||
current = path[1:-1]
|
||||
else:
|
||||
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 = TEMPLATE_STRING_IF_INVALID
|
||||
else:
|
||||
try: # method call (assuming no args required)
|
||||
current = current()
|
||||
except SilentVariableFailure:
|
||||
current = TEMPLATE_STRING_IF_INVALID
|
||||
except TypeError: # arguments *were* required
|
||||
# GOTCHA: This will also catch any TypeError
|
||||
# raised in the function itself.
|
||||
current = TEMPLATE_STRING_IF_INVALID # 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
|
||||
|
||||
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(self.render_node(node, 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
|
||||
|
||||
def render_node(self, node, context):
|
||||
return(node.render(context))
|
||||
|
||||
class DebugNodeList(NodeList):
|
||||
def render_node(self, node, context):
|
||||
try:
|
||||
result = node.render(context)
|
||||
except TemplateSyntaxError, e:
|
||||
if not hasattr(e, 'source'):
|
||||
e.source = node.source
|
||||
raise
|
||||
except Exception:
|
||||
from sys import exc_info
|
||||
wrapped = TemplateSyntaxError('Caught an exception while rendering.')
|
||||
wrapped.source = node.source
|
||||
wrapped.exc_info = exc_info()
|
||||
raise wrapped
|
||||
return result
|
||||
|
||||
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, filter_expression):
|
||||
self.filter_expression = filter_expression
|
||||
|
||||
def __repr__(self):
|
||||
return "<Variable Node: %s>" % self.filter_expression
|
||||
|
||||
def encode_output(self, output):
|
||||
# Check type so that we don't run str() on a Unicode object
|
||||
if not isinstance(output, basestring):
|
||||
return str(output)
|
||||
elif isinstance(output, unicode):
|
||||
return output.encode(DEFAULT_CHARSET)
|
||||
else:
|
||||
return output
|
||||
|
||||
def render(self, context):
|
||||
output = self.filter_expression.resolve(context)
|
||||
return self.encode_output(output)
|
||||
|
||||
class DebugVariableNode(VariableNode):
|
||||
def render(self, context):
|
||||
try:
|
||||
output = self.filter_expression.resolve(context)
|
||||
except TemplateSyntaxError, e:
|
||||
if not hasattr(e, 'source'):
|
||||
e.source = self.source
|
||||
raise
|
||||
return self.encode_output(output)
|
||||
|
||||
def generic_tag_compiler(params, defaults, name, node_class, parser, token):
|
||||
"Returns a template.Node subclass."
|
||||
bits = token.contents.split()[1:]
|
||||
bmax = len(params)
|
||||
def_len = defaults and len(defaults) or 0
|
||||
bmin = bmax - def_len
|
||||
if(len(bits) < bmin or len(bits) > bmax):
|
||||
if bmin == bmax:
|
||||
message = "%s takes %s arguments" % (name, bmin)
|
||||
else:
|
||||
message = "%s takes between %s and %s arguments" % (name, bmin, bmax)
|
||||
raise TemplateSyntaxError, message
|
||||
return node_class(bits)
|
||||
|
||||
class Library(object):
|
||||
def __init__(self):
|
||||
self.filters = {}
|
||||
self.tags = {}
|
||||
|
||||
def tag(self, name=None, compile_function=None):
|
||||
if name == None and compile_function == None:
|
||||
# @register.tag()
|
||||
return self.tag_function
|
||||
elif name != None and compile_function == None:
|
||||
if(callable(name)):
|
||||
# @register.tag
|
||||
return self.tag_function(name)
|
||||
else:
|
||||
# @register.tag('somename') or @register.tag(name='somename')
|
||||
def dec(func):
|
||||
return self.tag(name, func)
|
||||
return dec
|
||||
elif name != None and compile_function != None:
|
||||
# register.tag('somename', somefunc)
|
||||
self.tags[name] = compile_function
|
||||
return compile_function
|
||||
else:
|
||||
raise InvalidTemplateLibrary, "Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function)
|
||||
|
||||
def tag_function(self,func):
|
||||
self.tags[func.__name__] = func
|
||||
return func
|
||||
|
||||
def filter(self, name=None, filter_func=None):
|
||||
if name == None and filter_func == None:
|
||||
# @register.filter()
|
||||
return self.filter_function
|
||||
elif filter_func == None:
|
||||
if(callable(name)):
|
||||
# @register.filter
|
||||
return self.filter_function(name)
|
||||
else:
|
||||
# @register.filter('somename') or @register.filter(name='somename')
|
||||
def dec(func):
|
||||
return self.filter(name, func)
|
||||
return dec
|
||||
elif name != None and filter_func != None:
|
||||
# register.filter('somename', somefunc)
|
||||
self.filters[name] = filter_func
|
||||
return filter_func
|
||||
else:
|
||||
raise InvalidTemplateLibrary, "Unsupported arguments to Library.filter: (%r, %r, %r)", (name, compile_function, has_arg)
|
||||
|
||||
def filter_function(self, func):
|
||||
self.filters[func.__name__] = func
|
||||
return func
|
||||
|
||||
def simple_tag(self,func):
|
||||
(params, xx, xxx, defaults) = getargspec(func)
|
||||
|
||||
class SimpleNode(Node):
|
||||
def __init__(self, vars_to_resolve):
|
||||
self.vars_to_resolve = vars_to_resolve
|
||||
|
||||
def render(self, context):
|
||||
resolved_vars = [resolve_variable(var, context) for var in self.vars_to_resolve]
|
||||
return func(*resolved_vars)
|
||||
|
||||
compile_func = curry(generic_tag_compiler, params, defaults, func.__name__, SimpleNode)
|
||||
compile_func.__doc__ = func.__doc__
|
||||
self.tag(func.__name__, compile_func)
|
||||
return func
|
||||
|
||||
def inclusion_tag(self, file_name, context_class=Context, takes_context=False):
|
||||
def dec(func):
|
||||
(params, xx, xxx, defaults) = getargspec(func)
|
||||
if takes_context:
|
||||
if params[0] == 'context':
|
||||
params = params[1:]
|
||||
else:
|
||||
raise TemplateSyntaxError, "Any tag function decorated with takes_context=True must have a first argument of 'context'"
|
||||
|
||||
class InclusionNode(Node):
|
||||
def __init__(self, vars_to_resolve):
|
||||
self.vars_to_resolve = vars_to_resolve
|
||||
|
||||
def render(self, context):
|
||||
resolved_vars = [resolve_variable(var, context) for var in self.vars_to_resolve]
|
||||
if takes_context:
|
||||
args = [context] + resolved_vars
|
||||
else:
|
||||
args = resolved_vars
|
||||
|
||||
dict = func(*args)
|
||||
|
||||
if not getattr(self, 'nodelist', False):
|
||||
from django.core.template_loader import get_template
|
||||
t = get_template(file_name)
|
||||
self.nodelist = t.nodelist
|
||||
return self.nodelist.render(context_class(dict))
|
||||
|
||||
compile_func = curry(generic_tag_compiler, params, defaults, func.__name__, InclusionNode)
|
||||
compile_func.__doc__ = func.__doc__
|
||||
self.tag(func.__name__, compile_func)
|
||||
return func
|
||||
return dec
|
||||
|
||||
def get_library(module_name):
|
||||
lib = libraries.get(module_name, None)
|
||||
if not lib:
|
||||
try:
|
||||
mod = __import__(module_name, '', '', [''])
|
||||
except ImportError, e:
|
||||
raise InvalidTemplateLibrary, "Could not load template library from %s, %s" % (module_name, e)
|
||||
try:
|
||||
lib = mod.register
|
||||
libraries[module_name] = lib
|
||||
except AttributeError:
|
||||
raise InvalidTemplateLibrary, "Template library %s does not have a variable named 'register'" % module_name
|
||||
return lib
|
||||
|
||||
def add_to_builtins(module_name):
|
||||
builtins.append(get_library(module_name))
|
||||
|
||||
add_to_builtins('django.core.template.defaulttags')
|
||||
add_to_builtins('django.core.template.defaultfilters')
|
||||
@@ -1,484 +0,0 @@
|
||||
"Default variable filters"
|
||||
|
||||
from django.core.template import resolve_variable, Library
|
||||
from django.conf.settings import DATE_FORMAT, TIME_FORMAT
|
||||
from django.utils.translation import gettext
|
||||
import re
|
||||
import random as random_module
|
||||
|
||||
register = Library()
|
||||
|
||||
###################
|
||||
# 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 place) -- but
|
||||
only if there's a point to be displayed
|
||||
"""
|
||||
try:
|
||||
f = float(text)
|
||||
except ValueError:
|
||||
return ''
|
||||
m = f - int(f)
|
||||
if m:
|
||||
return '%.1f' % f
|
||||
else:
|
||||
return '%d' % int(f)
|
||||
|
||||
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,
|
||||
and adding 'rel=nofollow' attribute to discourage spamming.
|
||||
|
||||
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 characters at which to wrap the text
|
||||
"""
|
||||
from django.utils.text import wrap
|
||||
return wrap(str(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(r'<%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 = [(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 = [(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_module.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:
|
||||
bits = []
|
||||
for x in arg.split(':'):
|
||||
if len(x) == 0:
|
||||
bits.append(None)
|
||||
else:
|
||||
bits.append(int(x))
|
||||
return value[slice(*bits)]
|
||||
|
||||
except (ValueError, TypeError):
|
||||
return value # Fail silently.
|
||||
|
||||
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([_helper(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=DATE_FORMAT):
|
||||
"Formats a date according to the given format"
|
||||
from django.utils.dateformat import format
|
||||
return format(value, arg)
|
||||
|
||||
def time(value, arg=TIME_FORMAT):
|
||||
"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 default_if_none(value, arg):
|
||||
"If value is None, use given default"
|
||||
if value is None:
|
||||
return arg
|
||||
return value
|
||||
|
||||
def divisibleby(value, arg):
|
||||
"Returns true if the value is devisible by the argument"
|
||||
return int(value) % int(arg) == 0
|
||||
|
||||
def yesno(value, arg=None):
|
||||
"""
|
||||
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.
|
||||
========== ====================== ==================================
|
||||
"""
|
||||
if arg is None:
|
||||
arg = gettext('yes,no,maybe')
|
||||
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[0], bits[1], 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: register.filter(name of filter, callback)
|
||||
register.filter(add)
|
||||
register.filter(addslashes)
|
||||
register.filter(capfirst)
|
||||
register.filter(center)
|
||||
register.filter(cut)
|
||||
register.filter(date)
|
||||
register.filter(default)
|
||||
register.filter(default_if_none)
|
||||
register.filter(dictsort)
|
||||
register.filter(dictsortreversed)
|
||||
register.filter(divisibleby)
|
||||
register.filter(escape)
|
||||
register.filter(filesizeformat)
|
||||
register.filter(first)
|
||||
register.filter(fix_ampersands)
|
||||
register.filter(floatformat)
|
||||
register.filter(get_digit)
|
||||
register.filter(join)
|
||||
register.filter(length)
|
||||
register.filter(length_is)
|
||||
register.filter(linebreaks)
|
||||
register.filter(linebreaksbr)
|
||||
register.filter(linenumbers)
|
||||
register.filter(ljust)
|
||||
register.filter(lower)
|
||||
register.filter(make_list)
|
||||
register.filter(phone2numeric)
|
||||
register.filter(pluralize)
|
||||
register.filter(pprint)
|
||||
register.filter(removetags)
|
||||
register.filter(random)
|
||||
register.filter(rjust)
|
||||
register.filter('slice', slice_)
|
||||
register.filter(slugify)
|
||||
register.filter(stringformat)
|
||||
register.filter(striptags)
|
||||
register.filter(time)
|
||||
register.filter(timesince)
|
||||
register.filter(title)
|
||||
register.filter(truncatewords)
|
||||
register.filter(unordered_list)
|
||||
register.filter(upper)
|
||||
register.filter(urlencode)
|
||||
register.filter(urlize)
|
||||
register.filter(urlizetrunc)
|
||||
register.filter(wordcount)
|
||||
register.filter(wordwrap)
|
||||
register.filter(yesno)
|
||||
@@ -1,816 +0,0 @@
|
||||
"Default tags used by the template system, available to all templates."
|
||||
|
||||
from django.core.template import Node, NodeList, Template, Context, resolve_variable
|
||||
from django.core.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END
|
||||
from django.core.template import get_library, Library, InvalidTemplateLibrary
|
||||
import sys
|
||||
|
||||
register = Library()
|
||||
|
||||
class CommentNode(Node):
|
||||
def render(self, context):
|
||||
return ''
|
||||
|
||||
class CycleNode(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(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(Node):
|
||||
def __init__(self, filter_expr, nodelist):
|
||||
self.filter_expr, self.nodelist = filter_expr, nodelist
|
||||
|
||||
def render(self, context):
|
||||
output = self.nodelist.render(context)
|
||||
# apply filters
|
||||
return self.filter_expr.resolve(Context({'var': output}))
|
||||
|
||||
class FirstOfNode(Node):
|
||||
def __init__(self, vars):
|
||||
self.vars = vars
|
||||
|
||||
def render(self, context):
|
||||
for var in self.vars:
|
||||
value = resolve_variable(var, context)
|
||||
if value:
|
||||
return str(value)
|
||||
return ''
|
||||
|
||||
class ForNode(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 = NodeList()
|
||||
if context.has_key('forloop'):
|
||||
parentloop = context['forloop']
|
||||
else:
|
||||
parentloop = {}
|
||||
context.push()
|
||||
try:
|
||||
values = self.sequence.resolve(context)
|
||||
except 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,
|
||||
# reverse counter iteration numbers
|
||||
'revcounter': len_values - i,
|
||||
'revcounter0': len_values - 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(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:
|
||||
self._last_seen = content
|
||||
return content
|
||||
else:
|
||||
return ''
|
||||
|
||||
class IfEqualNode(Node):
|
||||
def __init__(self, var1, var2, nodelist_true, nodelist_false, negate):
|
||||
self.var1, self.var2 = var1, var2
|
||||
self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
|
||||
self.negate = negate
|
||||
|
||||
def __repr__(self):
|
||||
return "<IfEqualNode>"
|
||||
|
||||
def render(self, context):
|
||||
val1 = resolve_variable(self.var1, context)
|
||||
val2 = resolve_variable(self.var2, context)
|
||||
if (self.negate and val1 != val2) or (not self.negate and val1 == val2):
|
||||
return self.nodelist_true.render(context)
|
||||
return self.nodelist_false.render(context)
|
||||
|
||||
class IfNode(Node):
|
||||
def __init__(self, bool_exprs, nodelist_true, nodelist_false):
|
||||
self.bool_exprs = bool_exprs
|
||||
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, bool_expr in self.bool_exprs:
|
||||
try:
|
||||
value = bool_expr.resolve(context)
|
||||
except 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(Node):
|
||||
def __init__(self, target, expression, var_name):
|
||||
self.target, self.expression = target, expression
|
||||
self.var_name = var_name
|
||||
|
||||
def render(self, context):
|
||||
obj_list = self.target.resolve(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 = self.expression.resolve(Context({'var': obj}))
|
||||
# TODO: Is this a sensible way to determine equality?
|
||||
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(Node):
|
||||
def __init__(self, filepath, parsed):
|
||||
self.filepath, self.parsed = filepath, parsed
|
||||
|
||||
def render(self, context):
|
||||
from django.conf.settings import DEBUG
|
||||
if not include_is_allowed(self.filepath):
|
||||
if DEBUG:
|
||||
return "[Didn't have permission to include file]"
|
||||
else:
|
||||
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(output)
|
||||
return t.render(context)
|
||||
except TemplateSyntaxError, e:
|
||||
if DEBUG:
|
||||
return "[Included template had syntax error: %s]" % e
|
||||
else:
|
||||
return '' # Fail silently for invalid included templates.
|
||||
return output
|
||||
|
||||
class LoadNode(Node):
|
||||
def render(self, context):
|
||||
return ''
|
||||
|
||||
class NowNode(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 SpacelessNode(Node):
|
||||
def __init__(self, nodelist):
|
||||
self.nodelist = nodelist
|
||||
|
||||
def render(self, context):
|
||||
from django.utils.html import strip_spaces_between_tags
|
||||
return strip_spaces_between_tags(self.nodelist.render(context).strip())
|
||||
|
||||
class TemplateTagNode(Node):
|
||||
mapping = {'openblock': BLOCK_TAG_START,
|
||||
'closeblock': BLOCK_TAG_END,
|
||||
'openvariable': VARIABLE_TAG_START,
|
||||
'closevariable': VARIABLE_TAG_END}
|
||||
|
||||
def __init__(self, tagtype):
|
||||
self.tagtype = tagtype
|
||||
|
||||
def render(self, context):
|
||||
return self.mapping.get(self.tagtype, '')
|
||||
|
||||
class WidthRatioNode(Node):
|
||||
def __init__(self, val_expr, max_expr, max_width):
|
||||
self.val_expr = val_expr
|
||||
self.max_expr = max_expr
|
||||
self.max_width = max_width
|
||||
|
||||
def render(self, context):
|
||||
try:
|
||||
value = self.val_expr.resolve(context)
|
||||
maxvalue = self.max_expr.resolve(context)
|
||||
except 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)))
|
||||
|
||||
#@register.tag
|
||||
def comment(parser, token):
|
||||
"""
|
||||
Ignore everything between ``{% comment %}`` and ``{% endcomment %}``
|
||||
"""
|
||||
parser.skip_past('endcomment')
|
||||
return CommentNode()
|
||||
comment = register.tag(comment)
|
||||
|
||||
#@register.tag
|
||||
def 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 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 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 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 TemplateSyntaxError("Invalid arguments to 'cycle': %s" % args)
|
||||
cycle = register.tag(cycle)
|
||||
|
||||
def debug(parser, token):
|
||||
return DebugNode()
|
||||
debug = register.tag(debug)
|
||||
|
||||
#@register.tag(name="filter")
|
||||
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)
|
||||
filter_expr = parser.compile_filter("var|%s" % (rest))
|
||||
nodelist = parser.parse(('endfilter',))
|
||||
parser.delete_first_token()
|
||||
return FilterNode(filter_expr, nodelist)
|
||||
filter = register.tag("filter", do_filter)
|
||||
|
||||
#@register.tag
|
||||
def 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 TemplateSyntaxError, "'firstof' statement requires at least one argument"
|
||||
return FirstOfNode(bits)
|
||||
firstof = register.tag(firstof)
|
||||
|
||||
#@register.tag(name="for")
|
||||
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.revcounter`` The number of iterations from the end of the
|
||||
loop (1-indexed)
|
||||
``forloop.revcounter0`` The number of iterations from the end 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 TemplateSyntaxError, "'for' statements with five words should end in 'reversed': %s" % token.contents
|
||||
if len(bits) not in (4, 5):
|
||||
raise TemplateSyntaxError, "'for' statements should have either four or five words: %s" % token.contents
|
||||
if bits[2] != 'in':
|
||||
raise TemplateSyntaxError, "'for' statement must contain 'in' as the second word: %s" % token.contents
|
||||
loopvar = bits[1]
|
||||
sequence = parser.compile_filter(bits[3])
|
||||
reversed = (len(bits) == 5)
|
||||
nodelist_loop = parser.parse(('endfor',))
|
||||
parser.delete_first_token()
|
||||
return ForNode(loopvar, sequence, reversed, nodelist_loop)
|
||||
do_for = register.tag("for", do_for)
|
||||
|
||||
def do_ifequal(parser, token, negate):
|
||||
"""
|
||||
Output the contents of the block if the two arguments equal/don't equal each other.
|
||||
|
||||
Examples::
|
||||
|
||||
{% ifequal user.id comment.user_id %}
|
||||
...
|
||||
{% endifequal %}
|
||||
|
||||
{% ifnotequal user.id comment.user_id %}
|
||||
...
|
||||
{% else %}
|
||||
...
|
||||
{% endifnotequal %}
|
||||
"""
|
||||
bits = token.contents.split()
|
||||
if len(bits) != 3:
|
||||
raise TemplateSyntaxError, "%r takes two arguments" % bits[0]
|
||||
end_tag = 'end' + bits[0]
|
||||
nodelist_true = parser.parse(('else', end_tag))
|
||||
token = parser.next_token()
|
||||
if token.contents == 'else':
|
||||
nodelist_false = parser.parse((end_tag,))
|
||||
parser.delete_first_token()
|
||||
else:
|
||||
nodelist_false = NodeList()
|
||||
return IfEqualNode(bits[1], bits[2], nodelist_true, nodelist_false, negate)
|
||||
|
||||
#@register.tag
|
||||
def ifequal(parser, token):
|
||||
return do_ifequal(parser, token, False)
|
||||
ifequal = register.tag(ifequal)
|
||||
|
||||
#@register.tag
|
||||
def ifnotequal(parser, token):
|
||||
return do_ifequal(parser, token, True)
|
||||
ifnotequal = register.tag(ifnotequal)
|
||||
|
||||
#@register.tag(name="if")
|
||||
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.
|
||||
{% endif %}
|
||||
|
||||
For simplicity, ``if`` tags do not allow ``and`` clauses. Use nested ``if``
|
||||
tags 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 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 TemplateSyntaxError, "Expected 'not' in if statement"
|
||||
boolvars.append((True, parser.compile_filter(boolvar)))
|
||||
else:
|
||||
boolvars.append((False, parser.compile_filter(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 = NodeList()
|
||||
return IfNode(boolvars, nodelist_true, nodelist_false)
|
||||
do_if = register.tag("if", do_if)
|
||||
|
||||
#@register.tag
|
||||
def 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 TemplateSyntaxError, "'ifchanged' tag takes no arguments"
|
||||
nodelist = parser.parse(('endifchanged',))
|
||||
parser.delete_first_token()
|
||||
return IfChangedNode(nodelist)
|
||||
ifchanged = register.tag(ifchanged)
|
||||
|
||||
#@register.tag
|
||||
def 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 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 TemplateSyntaxError, "Second (optional) argument to %s tag must be 'parsed'" % bits[0]
|
||||
return SsiNode(bits[1], parsed)
|
||||
ssi = register.tag(ssi)
|
||||
|
||||
#@register.tag
|
||||
def 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()
|
||||
for taglib in bits[1:]:
|
||||
# add the library to the parser
|
||||
try:
|
||||
lib = get_library("django.templatetags.%s" % taglib.split('.')[-1])
|
||||
parser.add_library(lib)
|
||||
except InvalidTemplateLibrary, e:
|
||||
raise TemplateSyntaxError, "'%s' is not a valid tag library: %s" % (taglib, e)
|
||||
return LoadNode()
|
||||
load = register.tag(load)
|
||||
|
||||
#@register.tag
|
||||
def 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 TemplateSyntaxError, "'now' statement takes one argument"
|
||||
format_string = bits[1]
|
||||
return NowNode(format_string)
|
||||
now = register.tag(now)
|
||||
|
||||
#@register.tag
|
||||
def 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:
|
||||
* Pat Smith
|
||||
|
||||
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 TemplateSyntaxError, "'regroup' tag takes five arguments"
|
||||
target = parser.compile_filter(firstbits[1])
|
||||
if firstbits[2] != 'by':
|
||||
raise TemplateSyntaxError, "second argument to 'regroup' tag must be 'by'"
|
||||
lastbits_reversed = firstbits[3][::-1].split(None, 2)
|
||||
if lastbits_reversed[1][::-1] != 'as':
|
||||
raise TemplateSyntaxError, "next-to-last argument to 'regroup' tag must be 'as'"
|
||||
|
||||
expression = parser.compile_filter('var.%s' % lastbits_reversed[2][::-1])
|
||||
|
||||
var_name = lastbits_reversed[0][::-1]
|
||||
return RegroupNode(target, expression, var_name)
|
||||
regroup = register.tag(regroup)
|
||||
|
||||
def spaceless(parser, token):
|
||||
"""
|
||||
Normalize whitespace between HTML tags to a single space. This includes tab
|
||||
characters and newlines.
|
||||
|
||||
Example usage::
|
||||
|
||||
{% spaceless %}
|
||||
<p>
|
||||
<a href="foo/">Foo</a>
|
||||
</p>
|
||||
{% endspaceless %}
|
||||
|
||||
This example would return this HTML::
|
||||
|
||||
<p> <a href="foo/">Foo</a> </p>
|
||||
|
||||
Only space between *tags* is normalized -- not space between tags and text. In
|
||||
this example, the space around ``Hello`` won't be stripped::
|
||||
|
||||
{% spaceless %}
|
||||
<strong>
|
||||
Hello
|
||||
</strong>
|
||||
{% endspaceless %}
|
||||
"""
|
||||
nodelist = parser.parse(('endspaceless',))
|
||||
parser.delete_first_token()
|
||||
return SpacelessNode(nodelist)
|
||||
spaceless = register.tag(spaceless)
|
||||
|
||||
#@register.tag
|
||||
def 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 TemplateSyntaxError, "'templatetag' statement takes one argument"
|
||||
tag = bits[1]
|
||||
if not TemplateTagNode.mapping.has_key(tag):
|
||||
raise TemplateSyntaxError, "Invalid templatetag argument: '%s'. Must be one of: %s" % \
|
||||
(tag, TemplateTagNode.mapping.keys())
|
||||
return TemplateTagNode(tag)
|
||||
templatetag = register.tag(templatetag)
|
||||
|
||||
#@register.tag
|
||||
def 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 TemplateSyntaxError("widthratio takes three arguments")
|
||||
tag, this_value_expr, max_value_expr, max_width = bits
|
||||
try:
|
||||
max_width = int(max_width)
|
||||
except ValueError:
|
||||
raise TemplateSyntaxError("widthratio final argument must be an integer")
|
||||
return WidthRatioNode(parser.compile_filter(this_value_expr),
|
||||
parser.compile_filter(max_value_expr), max_width)
|
||||
widthratio = register.tag(widthratio)
|
||||
@@ -1,116 +0,0 @@
|
||||
# Wrapper for loading templates from storage of some sort (e.g. filesystem, database).
|
||||
#
|
||||
# This uses the TEMPLATE_LOADERS setting, which is a list of loaders to use.
|
||||
# Each loader is expected to have this interface:
|
||||
#
|
||||
# callable(name, dirs=[])
|
||||
#
|
||||
# name is the template name.
|
||||
# dirs is an optional list of directories to search instead of TEMPLATE_DIRS.
|
||||
#
|
||||
# The loader should return a tuple of (template_source, path). The path returned
|
||||
# might be shown to the user for debugging purposes, so it should identify where
|
||||
# the template was loaded from.
|
||||
#
|
||||
# Each loader should have an "is_usable" attribute set. This is a boolean that
|
||||
# specifies whether the loader can be used in this Python installation. Each
|
||||
# loader is responsible for setting this when it's initialized.
|
||||
#
|
||||
# For example, the eggs loader (which is capable of loading templates from
|
||||
# Python eggs) sets is_usable to False if the "pkg_resources" module isn't
|
||||
# installed, because pkg_resources is necessary to read eggs.
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.template import Origin, StringOrigin, Template, Context, TemplateDoesNotExist, add_to_builtins
|
||||
from django.conf.settings import TEMPLATE_LOADERS, TEMPLATE_DEBUG
|
||||
|
||||
template_source_loaders = None
|
||||
|
||||
class LoaderOrigin(Origin):
|
||||
def __init__(self, display_name, loader, name, dirs):
|
||||
super(LoaderOrigin, self).__init__(display_name)
|
||||
self.loader, self.loadname, self.dirs = loader, name, dirs
|
||||
|
||||
def reload(self):
|
||||
return self.loader(self.loadname, self.dirs)[0]
|
||||
|
||||
def make_origin(display_name, loader, name, dirs):
|
||||
if TEMPLATE_DEBUG:
|
||||
return LoaderOrigin(display_name, loader, name, dirs)
|
||||
else:
|
||||
return None
|
||||
|
||||
def find_template_source(name, dirs=None):
|
||||
# Calculate template_source_loaders the first time the function is executed
|
||||
# because putting this logic in the module-level namespace may cause
|
||||
# circular import errors. See Django ticket #1292.
|
||||
global template_source_loaders
|
||||
if template_source_loaders is None:
|
||||
template_source_loaders = []
|
||||
for path in TEMPLATE_LOADERS:
|
||||
i = path.rfind('.')
|
||||
module, attr = path[:i], path[i+1:]
|
||||
try:
|
||||
mod = __import__(module, globals(), locals(), [attr])
|
||||
except ImportError, e:
|
||||
raise ImproperlyConfigured, 'Error importing template source loader %s: "%s"' % (module, e)
|
||||
try:
|
||||
func = getattr(mod, attr)
|
||||
except AttributeError:
|
||||
raise ImproperlyConfigured, 'Module "%s" does not define a "%s" callable template source loader' % (module, attr)
|
||||
if not func.is_usable:
|
||||
import warnings
|
||||
warnings.warn("Your TEMPLATE_LOADERS setting includes %r, but your Python installation doesn't support that type of template loading. Consider removing that line from TEMPLATE_LOADERS." % path)
|
||||
else:
|
||||
template_source_loaders.append(func)
|
||||
for loader in template_source_loaders:
|
||||
try:
|
||||
source, display_name = loader(name, dirs)
|
||||
return (source, make_origin(display_name, loader, name, dirs))
|
||||
except TemplateDoesNotExist:
|
||||
pass
|
||||
raise TemplateDoesNotExist, name
|
||||
|
||||
def get_template(template_name):
|
||||
"""
|
||||
Returns a compiled Template object for the given template name,
|
||||
handling template inheritance recursively.
|
||||
"""
|
||||
return get_template_from_string(*find_template_source(template_name))
|
||||
|
||||
def get_template_from_string(source, origin=None):
|
||||
"""
|
||||
Returns a compiled Template object for the given template code,
|
||||
handling template inheritance recursively.
|
||||
"""
|
||||
return Template(source, origin)
|
||||
|
||||
def render_to_string(template_name, dictionary=None, context_instance=None):
|
||||
"""
|
||||
Loads the given template_name and renders it with the given dictionary as
|
||||
context. The template_name may be a string to load a single template using
|
||||
get_template, or it may be a tuple to use select_template to find one of
|
||||
the templates in the list. Returns a string.
|
||||
"""
|
||||
dictionary = dictionary or {}
|
||||
if isinstance(template_name, (list, tuple)):
|
||||
t = select_template(template_name)
|
||||
else:
|
||||
t = get_template(template_name)
|
||||
if context_instance:
|
||||
context_instance.update(dictionary)
|
||||
else:
|
||||
context_instance = Context(dictionary)
|
||||
return t.render(context_instance)
|
||||
|
||||
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 TemplateDoesNotExist:
|
||||
continue
|
||||
# If we get here, none of the templates could be loaded
|
||||
raise TemplateDoesNotExist, ', '.join(template_name_list)
|
||||
|
||||
add_to_builtins('django.core.template.loader_tags')
|
||||
@@ -1,173 +0,0 @@
|
||||
from django.core.template import TemplateSyntaxError, TemplateDoesNotExist, resolve_variable
|
||||
from django.core.template import Library, Context, Node
|
||||
from django.core.template.loader import get_template, get_template_from_string, find_template_source
|
||||
from django.conf.settings import TEMPLATE_DEBUG
|
||||
register = Library()
|
||||
|
||||
class ExtendsError(Exception):
|
||||
pass
|
||||
|
||||
class BlockNode(Node):
|
||||
def __init__(self, name, nodelist, parent=None):
|
||||
self.name, self.nodelist, self.parent = name, nodelist, parent
|
||||
|
||||
def __repr__(self):
|
||||
return "<Block Node: %s. Contents: %r>" % (self.name, self.nodelist)
|
||||
|
||||
def render(self, context):
|
||||
context.push()
|
||||
# Save context in case of block.super().
|
||||
self.context = context
|
||||
context['block'] = self
|
||||
result = self.nodelist.render(context)
|
||||
context.pop()
|
||||
return result
|
||||
|
||||
def super(self):
|
||||
if self.parent:
|
||||
return self.parent.render(self.context)
|
||||
return ''
|
||||
|
||||
def add_parent(self, nodelist):
|
||||
if self.parent:
|
||||
self.parent.add_parent(nodelist)
|
||||
else:
|
||||
self.parent = BlockNode(self.name, nodelist)
|
||||
|
||||
class ExtendsNode(Node):
|
||||
def __init__(self, nodelist, parent_name, parent_name_expr, template_dirs=None):
|
||||
self.nodelist = nodelist
|
||||
self.parent_name, self.parent_name_expr = parent_name, parent_name_expr
|
||||
self.template_dirs = template_dirs
|
||||
|
||||
def get_parent(self, context):
|
||||
if self.parent_name_expr:
|
||||
self.parent_name = self.parent_name_expr.resolve(context)
|
||||
parent = self.parent_name
|
||||
if not parent:
|
||||
error_msg = "Invalid template name in 'extends' tag: %r." % parent
|
||||
if self.parent_name_expr:
|
||||
error_msg += " Got this from the %r variable." % self.parent_name_expr #TODO nice repr.
|
||||
raise TemplateSyntaxError, error_msg
|
||||
try:
|
||||
source, origin = find_template_source(parent, self.template_dirs)
|
||||
except TemplateDoesNotExist:
|
||||
raise TemplateSyntaxError, "Template %r cannot be extended, because it doesn't exist" % parent
|
||||
else:
|
||||
return get_template_from_string(source, origin)
|
||||
|
||||
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:
|
||||
# Keep any existing parents and add a new one. Used by BlockNode.
|
||||
parent_block.parent = block_node.parent
|
||||
parent_block.add_parent(parent_block.nodelist)
|
||||
parent_block.nodelist = block_node.nodelist
|
||||
return compiled_parent.render(context)
|
||||
|
||||
class ConstantIncludeNode(Node):
|
||||
def __init__(self, template_path):
|
||||
try:
|
||||
t = get_template(template_path)
|
||||
self.template = t
|
||||
except:
|
||||
if TEMPLATE_DEBUG:
|
||||
raise
|
||||
self.template = None
|
||||
|
||||
def render(self, context):
|
||||
if self.template:
|
||||
return self.template.render(context)
|
||||
else:
|
||||
return ''
|
||||
|
||||
class IncludeNode(Node):
|
||||
def __init__(self, template_name):
|
||||
self.template_name = template_name
|
||||
|
||||
def render(self, context):
|
||||
try:
|
||||
template_name = resolve_variable(self.template_name, context)
|
||||
t = get_template(template_name)
|
||||
return t.render(context)
|
||||
except TemplateSyntaxError, e:
|
||||
if TEMPLATE_DEBUG:
|
||||
raise
|
||||
return ''
|
||||
except:
|
||||
return '' # Fail silently for invalid included templates.
|
||||
|
||||
def do_block(parser, token):
|
||||
"""
|
||||
Define a block that can be overridden by child templates.
|
||||
"""
|
||||
bits = token.contents.split()
|
||||
if len(bits) != 2:
|
||||
raise 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 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 ``{% extends variable %}`` uses the value of ``variable`` as the name
|
||||
of the parent template to extend.
|
||||
"""
|
||||
bits = token.contents.split()
|
||||
if len(bits) != 2:
|
||||
raise TemplateSyntaxError, "'%s' takes one argument" % bits[0]
|
||||
parent_name, parent_name_expr = None, None
|
||||
if bits[1][0] in ('"', "'") and bits[1][-1] == bits[1][0]:
|
||||
parent_name = bits[1][1:-1]
|
||||
else:
|
||||
parent_name_expr = parser.compile_filter(bits[1])
|
||||
nodelist = parser.parse()
|
||||
if nodelist.get_nodes_by_type(ExtendsNode):
|
||||
raise TemplateSyntaxError, "'%s' cannot appear more than once in the same template" % bits[0]
|
||||
return ExtendsNode(nodelist, parent_name, parent_name_expr)
|
||||
|
||||
def do_include(parser, token):
|
||||
"""
|
||||
Loads a template and renders it with the current context.
|
||||
|
||||
Example::
|
||||
|
||||
{% include "foo/some_include" %}
|
||||
"""
|
||||
bits = token.contents.split()
|
||||
if len(bits) != 2:
|
||||
raise TemplateSyntaxError, "%r tag takes one argument: the name of the template to be included" % bits[0]
|
||||
path = bits[1]
|
||||
if path[0] in ('"', "'") and path[-1] == path[0]:
|
||||
return ConstantIncludeNode(path[1:-1])
|
||||
return IncludeNode(bits[1])
|
||||
|
||||
register.tag('block', do_block)
|
||||
register.tag('extends', do_extends)
|
||||
register.tag('include', do_include)
|
||||
@@ -1,41 +0,0 @@
|
||||
# Wrapper for loading templates from "template" directories in installed app packages.
|
||||
|
||||
from django.conf.settings import INSTALLED_APPS, TEMPLATE_FILE_EXTENSION
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.template import TemplateDoesNotExist
|
||||
import os
|
||||
|
||||
# At compile time, cache the directories to search.
|
||||
app_template_dirs = []
|
||||
for app in INSTALLED_APPS:
|
||||
i = app.rfind('.')
|
||||
if i == -1:
|
||||
m, a = app, None
|
||||
else:
|
||||
m, a = app[:i], app[i+1:]
|
||||
try:
|
||||
if a is None:
|
||||
mod = __import__(m, '', '', [])
|
||||
else:
|
||||
mod = getattr(__import__(m, '', '', [a]), a)
|
||||
except ImportError, e:
|
||||
raise ImproperlyConfigured, 'ImportError %s: %s' % (app, e.args[0])
|
||||
template_dir = os.path.join(os.path.dirname(mod.__file__), 'templates')
|
||||
if os.path.isdir(template_dir):
|
||||
app_template_dirs.append(template_dir)
|
||||
|
||||
# It won't change, so convert it to a tuple to save memory.
|
||||
app_template_dirs = tuple(app_template_dirs)
|
||||
|
||||
def get_template_sources(template_name, template_dirs=None):
|
||||
for template_dir in app_template_dirs:
|
||||
yield os.path.join(template_dir, template_name) + TEMPLATE_FILE_EXTENSION
|
||||
|
||||
def load_template_source(template_name, template_dirs=None):
|
||||
for filepath in get_template_sources(template_name, template_dirs):
|
||||
try:
|
||||
return (open(filepath).read(), filepath)
|
||||
except IOError:
|
||||
pass
|
||||
raise TemplateDoesNotExist, template_name
|
||||
load_template_source.is_usable = True
|
||||
@@ -1,25 +0,0 @@
|
||||
# Wrapper for loading templates from eggs via pkg_resources.resource_string.
|
||||
|
||||
try:
|
||||
from pkg_resources import resource_string
|
||||
except ImportError:
|
||||
resource_string = None
|
||||
|
||||
from django.core.template import TemplateDoesNotExist
|
||||
from django.conf.settings import INSTALLED_APPS, TEMPLATE_FILE_EXTENSION
|
||||
|
||||
def load_template_source(template_name, template_dirs=None):
|
||||
"""
|
||||
Loads templates from Python eggs via pkg_resource.resource_string.
|
||||
|
||||
For every installed app, it tries to get the resource (app, template_name).
|
||||
"""
|
||||
if resource_string is not None:
|
||||
pkg_name = 'templates/' + template_name + TEMPLATE_FILE_EXTENSION
|
||||
for app in INSTALLED_APPS:
|
||||
try:
|
||||
return (resource_string(app, pkg_name), 'egg:%s:%s ' % (app, pkg_name))
|
||||
except:
|
||||
pass
|
||||
raise TemplateDoesNotExist, template_name
|
||||
load_template_source.is_usable = resource_string is not None
|
||||
@@ -1,25 +0,0 @@
|
||||
# Wrapper for loading templates from the filesystem.
|
||||
|
||||
from django.conf.settings import TEMPLATE_DIRS, TEMPLATE_FILE_EXTENSION
|
||||
from django.core.template import TemplateDoesNotExist
|
||||
import os
|
||||
|
||||
def get_template_sources(template_name, template_dirs=None):
|
||||
if not template_dirs:
|
||||
template_dirs = TEMPLATE_DIRS
|
||||
for template_dir in template_dirs:
|
||||
yield os.path.join(template_dir, template_name) + TEMPLATE_FILE_EXTENSION
|
||||
|
||||
def load_template_source(template_name, template_dirs=None):
|
||||
tried = []
|
||||
for filepath in get_template_sources(template_name, template_dirs):
|
||||
try:
|
||||
return (open(filepath).read(), filepath)
|
||||
except IOError:
|
||||
tried.append(filepath)
|
||||
if template_dirs:
|
||||
error_msg = "Tried %s" % tried
|
||||
else:
|
||||
error_msg = "Your TEMPLATE_DIRS setting is empty. Change it to point to at least one template directory."
|
||||
raise TemplateDoesNotExist, error_msg
|
||||
load_template_source.is_usable = True
|
||||
@@ -1,7 +1,7 @@
|
||||
# This module is DEPRECATED!
|
||||
#
|
||||
# You should no longer be using django.core.template_loader.
|
||||
# You should no longer be using django.template_loader.
|
||||
#
|
||||
# Use django.core.template.loader instead.
|
||||
# Use django.template.loader instead.
|
||||
|
||||
from django.core.template.loader import *
|
||||
from django.template.loader import *
|
||||
|
||||
@@ -7,7 +7,8 @@ a string) and returns a tuple in this format:
|
||||
(view_function, function_args, function_kwargs)
|
||||
"""
|
||||
|
||||
from django.core.exceptions import Http404, ImproperlyConfigured, ViewDoesNotExist
|
||||
from django.http import Http404
|
||||
from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
|
||||
import re
|
||||
|
||||
class Resolver404(Http404):
|
||||
|
||||
@@ -8,6 +8,9 @@ validator will *always* be run, regardless of whether its associated
|
||||
form field is required.
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.translation import gettext, gettext_lazy, ngettext
|
||||
from django.utils.functional import Promise, lazy
|
||||
import re
|
||||
|
||||
_datere = r'(19|2\d)\d{2}-((?:0?[1-9])|(?:1[0-2]))-((?:0?[1-9])|(?:[12][0-9])|(?:3[0-1]))'
|
||||
@@ -24,10 +27,6 @@ phone_re = re.compile(r'^[A-PR-Y0-9]{3}-[A-PR-Y0-9]{3}-[A-PR-Y0-9]{4}$', re.IGNO
|
||||
slug_re = re.compile(r'^[-\w]+$')
|
||||
url_re = re.compile(r'^https?://\S+$')
|
||||
|
||||
from django.conf.settings import JING_PATH
|
||||
from django.utils.translation import gettext_lazy, ngettext
|
||||
from django.utils.functional import Promise, lazy
|
||||
|
||||
lazy_inter = lazy(lambda a,b: str(a) % b, str)
|
||||
|
||||
class ValidationError(Exception):
|
||||
@@ -58,11 +57,11 @@ class CriticalValidationError(Exception):
|
||||
|
||||
def isAlphaNumeric(field_data, all_data):
|
||||
if not alnum_re.search(field_data):
|
||||
raise ValidationError, _("This value must contain only letters, numbers and underscores.")
|
||||
raise ValidationError, gettext("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, dashes or slashes.")
|
||||
raise ValidationError, gettext("This value must contain only letters, numbers, underscores, dashes or slashes.")
|
||||
|
||||
def isSlug(field_data, all_data):
|
||||
if not slug_re.search(field_data):
|
||||
@@ -70,18 +69,18 @@ def isSlug(field_data, all_data):
|
||||
|
||||
def isLowerCase(field_data, all_data):
|
||||
if field_data.lower() != field_data:
|
||||
raise ValidationError, _("Uppercase letters are not allowed here.")
|
||||
raise ValidationError, gettext("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.")
|
||||
raise ValidationError, gettext("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.")
|
||||
raise ValidationError, gettext("Enter only digits separated by commas.")
|
||||
|
||||
def isCommaSeparatedEmailList(field_data, all_data):
|
||||
"""
|
||||
@@ -93,48 +92,48 @@ def isCommaSeparatedEmailList(field_data, all_data):
|
||||
try:
|
||||
isValidEmail(supposed_email.strip(), '')
|
||||
except ValidationError:
|
||||
raise ValidationError, _("Enter valid e-mail addresses separated by commas.")
|
||||
raise ValidationError, gettext("Enter valid e-mail addresses separated by commas.")
|
||||
|
||||
def isValidIPAddress4(field_data, all_data):
|
||||
if not ip4_re.search(field_data):
|
||||
raise ValidationError, _("Please enter a valid IP address.")
|
||||
raise ValidationError, gettext("Please enter a valid IP address.")
|
||||
|
||||
def isNotEmpty(field_data, all_data):
|
||||
if field_data.strip() == '':
|
||||
raise ValidationError, _("Empty values are not allowed here.")
|
||||
raise ValidationError, gettext("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.")
|
||||
raise ValidationError, gettext("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.")
|
||||
raise ValidationError, gettext("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.")
|
||||
raise ValidationError, gettext("Enter a whole number.")
|
||||
|
||||
def isOnlyLetters(field_data, all_data):
|
||||
if not field_data.isalpha():
|
||||
raise ValidationError, _("Only alphabetical characters are allowed here.")
|
||||
raise ValidationError, gettext("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.')
|
||||
raise ValidationError, gettext('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.')
|
||||
raise ValidationError, gettext('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.')
|
||||
raise ValidationError, gettext('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.')
|
||||
raise ValidationError, gettext('Enter a valid e-mail address.')
|
||||
|
||||
def isValidImage(field_data, all_data):
|
||||
"""
|
||||
@@ -146,18 +145,18 @@ def isValidImage(field_data, all_data):
|
||||
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.")
|
||||
raise ValidationError, gettext("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
|
||||
raise ValidationError, gettext("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
|
||||
raise ValidationError, gettext('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)"
|
||||
@@ -165,11 +164,11 @@ def isValidQuicktimeVideoURL(field_data, all_data):
|
||||
try:
|
||||
uc(field_data, all_data)
|
||||
except URLMimeTypeCheck.InvalidContentType:
|
||||
raise ValidationError, _("The URL %s does not point to a valid QuickTime video.") % field_data
|
||||
raise ValidationError, gettext("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.")
|
||||
raise ValidationError, gettext("A valid URL is required.")
|
||||
|
||||
def isValidHTML(field_data, all_data):
|
||||
import urllib, urllib2
|
||||
@@ -183,14 +182,14 @@ def isValidHTML(field_data, all_data):
|
||||
return
|
||||
from xml.dom.minidom import parseString
|
||||
error_messages = [e.firstChild.wholeText for e in parseString(u.read()).getElementsByTagName('messages')[0].getElementsByTagName('msg')]
|
||||
raise ValidationError, _("Valid HTML is required. Specific errors are:\n%s") % "\n".join(error_messages)
|
||||
raise ValidationError, gettext("Valid HTML is required. Specific errors are:\n%s") % "\n".join(error_messages)
|
||||
|
||||
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)
|
||||
raise ValidationError, gettext("Badly formed XML: %s") % str(e)
|
||||
|
||||
def isWellFormedXmlFragment(field_data, all_data):
|
||||
isWellFormedXml('<root>%s</root>' % field_data, all_data)
|
||||
@@ -200,19 +199,19 @@ def isExistingURL(field_data, all_data):
|
||||
try:
|
||||
u = urllib2.urlopen(field_data)
|
||||
except ValueError:
|
||||
raise ValidationError, _("Invalid URL: %s") % field_data
|
||||
raise ValidationError, gettext("Invalid URL: %s") % field_data
|
||||
except urllib2.HTTPError, e:
|
||||
# 401s are valid; they just mean authorization is required.
|
||||
if e.code not in ('401',):
|
||||
raise ValidationError, _("The URL %s is a broken link.") % field_data
|
||||
raise ValidationError, gettext("The URL %s is a broken link.") % field_data
|
||||
except: # urllib2.URLError, httplib.InvalidURL, etc.
|
||||
raise ValidationError, _("The URL %s is a broken link.") % field_data
|
||||
raise ValidationError, gettext("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.")
|
||||
raise ValidationError, gettext("Enter a valid U.S. state abbreviation.")
|
||||
|
||||
def hasNoProfanities(field_data, all_data):
|
||||
"""
|
||||
@@ -334,7 +333,7 @@ class IsAPowerOf:
|
||||
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
|
||||
raise ValidationError, gettext("This value must be a power of %s.") % self.power_of
|
||||
|
||||
class IsValidFloat:
|
||||
def __init__(self, max_digits, decimal_places):
|
||||
@@ -345,9 +344,9 @@ class IsValidFloat:
|
||||
try:
|
||||
float(data)
|
||||
except ValueError:
|
||||
raise ValidationError, _("Please enter a valid decimal number.")
|
||||
raise ValidationError, gettext("Please enter a valid decimal number.")
|
||||
if len(data) > (self.max_digits + 1):
|
||||
raise ValidationError, ngettext( "Please enter a valid decimal number with at most %s total digit.",
|
||||
raise ValidationError, ngettext("Please enter a valid decimal number with at most %s total digit.",
|
||||
"Please enter a valid decimal number with at most %s total digits.", self.max_digits) % self.max_digits
|
||||
if '.' in data and len(data.split('.')[1]) > self.decimal_places:
|
||||
raise ValidationError, ngettext("Please enter a valid decimal number with at most %s decimal place.",
|
||||
@@ -424,10 +423,10 @@ class URLMimeTypeCheck:
|
||||
try:
|
||||
info = urllib2.urlopen(field_data).info()
|
||||
except (urllib2.HTTPError, urllib2.URLError):
|
||||
raise URLMimeTypeCheck.CouldNotRetrieve, _("Could not retrieve anything from %s.") % field_data
|
||||
raise URLMimeTypeCheck.CouldNotRetrieve, gettext("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 %(url)s returned the invalid Content-Type header '%(contenttype)s'.") % {
|
||||
raise URLMimeTypeCheck.InvalidContentType, gettext("The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'.") % {
|
||||
'url': field_data, 'contenttype': content_type}
|
||||
|
||||
class RelaxNGCompact:
|
||||
@@ -447,9 +446,9 @@ class RelaxNGCompact:
|
||||
fp = open(filename, 'w')
|
||||
fp.write(field_data)
|
||||
fp.close()
|
||||
if not os.path.exists(JING_PATH):
|
||||
raise Exception, "%s not found!" % JING_PATH
|
||||
p = os.popen('%s -c %s %s' % (JING_PATH, self.schema_path, filename))
|
||||
if not os.path.exists(settings.JING_PATH):
|
||||
raise Exception, "%s not found!" % settings.JING_PATH
|
||||
p = os.popen('%s -c %s %s' % (settings.JING_PATH, self.schema_path, filename))
|
||||
errors = [line.strip() for line in p.readlines()]
|
||||
p.close()
|
||||
os.unlink(filename)
|
||||
|
||||
@@ -9,14 +9,13 @@ 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):
|
||||
def populate_xheaders(request, response, model, 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.
|
||||
HttpResponse according to the given model 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.get('REMOTE_ADDR') in INTERNAL_IPS:
|
||||
response['X-Object-Type'] = "%s.%s" % (package, python_module_name)
|
||||
from django.conf import settings
|
||||
if request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS:
|
||||
response['X-Object-Type'] = "%s.%s" % (model._meta.app_label, model._meta.object_name.lower())
|
||||
response['X-Object-Id'] = str(object_id)
|
||||
|
||||
Reference in New Issue
Block a user