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

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

0
django/__init__.py Normal file
View File

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

View File

@ -0,0 +1,15 @@
"Daily cleanup file"
from django.core.db import db
DOCUMENTATION_DIRECTORY = '/home/html/documentation/'
def clean_up():
# Clean up old database records
cursor = db.cursor()
cursor.execute("DELETE FROM auth_sessions WHERE start_time < NOW() - INTERVAL '2 weeks'")
cursor.execute("DELETE FROM registration_challenges WHERE request_date < NOW() - INTERVAL '1 week'")
db.commit()
if __name__ == "__main__":
clean_up()

412
django/bin/django-admin.py Normal file
View File

@ -0,0 +1,412 @@
#!/usr/bin/python2.3
from django.core import db, meta
import django
import os, re, sys
MODULE_TEMPLATE = ''' {%% if perms.%(app)s.%(addperm)s or perms.%(app)s.%(changeperm)s %%}
<tr>
<th>{%% if perms.%(app)s.%(changeperm)s %%}<a href="/%(app)s/%(mod)s/">{%% endif %%}%(name)s{%% if perms.%(app)s.%(changeperm)s %%}</a>{%% endif %%}</th>
<td class="x50">{%% if perms.%(app)s.%(addperm)s %%}<a href="/%(app)s/%(mod)s/add/" class="addlink">{%% endif %%}Add{%% if perms.%(app)s.%(addperm)s %%}</a>{%% endif %%}</td>
<td class="x75">{%% if perms.%(app)s.%(changeperm)s %%}<a href="/%(app)s/%(mod)s/" class="changelink">{%% endif %%}Change{%% if perms.%(app)s.%(changeperm)s %%}</a>{%% endif %%}</td>
</tr>
{%% endif %%}'''
APP_ARGS = '[app app ...]'
PROJECT_TEMPLATE_DIR = django.__path__[0] + '/conf/%s_template'
def _get_packages_insert(app_label):
return "INSERT INTO packages (label, name) VALUES ('%s', '%s');" % (app_label, app_label)
def _get_permission_codename(action, opts):
return '%s_%s' % (action, opts.object_name.lower())
def _get_all_permissions(opts):
"Returns (codename, name) for all permissions in the given opts."
perms = []
if opts.admin:
for action in ('add', 'change', 'delete'):
perms.append((_get_permission_codename(action, opts), 'Can %s %s' % (action, opts.verbose_name)))
return perms + list(opts.permissions)
def _get_permission_insert(name, codename, opts):
return "INSERT INTO auth_permissions (name, package, codename) VALUES ('%s', '%s', '%s');" % \
(name.replace("'", "''"), opts.app_label, codename)
def _get_contenttype_insert(opts):
return "INSERT INTO content_types (name, package, python_module_name) VALUES ('%s', '%s', '%s');" % \
(opts.verbose_name, opts.app_label, opts.module_name)
def _is_valid_dir_name(s):
return bool(re.search(r'^\w+$', s))
def get_sql_create(mod):
"Returns a list of the CREATE TABLE SQL statements for the given module."
final_output = []
for klass in mod._MODELS:
opts = klass._meta
table_output = []
for f in opts.fields:
if isinstance(f, meta.ForeignKey):
rel_field = f.rel.to.get_field(f.rel.field_name)
# If the foreign key points to an AutoField, the foreign key
# should be an IntegerField, not an AutoField. Otherwise, the
# foreign key should be the same type of field as the field
# to which it points.
if rel_field.__class__.__name__ == 'AutoField':
data_type = 'IntegerField'
else:
rel_field.__class__.__name__
else:
rel_field = f
data_type = f.__class__.__name__
col_type = db.DATA_TYPES[data_type]
if col_type is not None:
field_output = [f.name, col_type % rel_field.__dict__]
field_output.append('%sNULL' % (not f.null and 'NOT ' or ''))
if f.unique:
field_output.append('UNIQUE')
if f.primary_key:
field_output.append('PRIMARY KEY')
if f.rel:
field_output.append('REFERENCES %s (%s)' % \
(f.rel.to.db_table, f.rel.to.get_field(f.rel.field_name).name))
table_output.append(' '.join(field_output))
if opts.order_with_respect_to:
table_output.append('_order %s NULL' % db.DATA_TYPES['IntegerField'])
for field_constraints in opts.unique_together:
table_output.append('UNIQUE (%s)' % ", ".join(field_constraints))
full_statement = ['CREATE TABLE %s (' % opts.db_table]
for i, line in enumerate(table_output): # Combine and add commas.
full_statement.append(' %s%s' % (line, i < len(table_output)-1 and ',' or ''))
full_statement.append(');')
final_output.append('\n'.join(full_statement))
for klass in mod._MODELS:
opts = klass._meta
for f in opts.many_to_many:
table_output = ['CREATE TABLE %s_%s (' % (opts.db_table, f.name)]
table_output.append(' id %s NOT NULL PRIMARY KEY,' % db.DATA_TYPES['AutoField'])
table_output.append(' %s_id %s NOT NULL REFERENCES %s (%s),' % \
(opts.object_name.lower(), db.DATA_TYPES['IntegerField'], opts.db_table, opts.pk.name))
table_output.append(' %s_id %s NOT NULL REFERENCES %s (%s),' % \
(f.rel.to.object_name.lower(), db.DATA_TYPES['IntegerField'], f.rel.to.db_table, f.rel.to.pk.name))
table_output.append(' UNIQUE (%s_id, %s_id)' % (opts.object_name.lower(), f.rel.to.object_name.lower()))
table_output.append(');')
final_output.append('\n'.join(table_output))
return final_output
get_sql_create.help_doc = "Prints the CREATE TABLE SQL statements for the given app(s)."
get_sql_create.args = APP_ARGS
def get_sql_delete(mod):
"Returns a list of the DROP TABLE SQL statements for the given module."
try:
cursor = db.db.cursor()
except:
cursor = None
output = []
for klass in mod._MODELS:
try:
if cursor is not None:
# Check whether the table exists.
cursor.execute("SELECT 1 FROM %s LIMIT 1" % klass._meta.db_table)
except:
# The table doesn't exist, so it doesn't need to be dropped.
pass
else:
output.append("DROP TABLE %s;" % klass._meta.db_table)
for klass in mod._MODELS:
opts = klass._meta
for f in opts.many_to_many:
try:
if cursor is not None:
cursor.execute("SELECT 1 FROM %s_%s LIMIT 1" % (opts.db_table, f.name))
except:
pass
else:
output.append("DROP TABLE %s_%s;" % (opts.db_table, f.name))
output.append("DELETE FROM packages WHERE label = '%s';" % mod._MODELS[0]._meta.app_label)
return output
get_sql_delete.help_doc = "Prints the DROP TABLE SQL statements for the given app(s)."
get_sql_delete.args = APP_ARGS
def get_sql_reset(mod):
"Returns a list of the DROP TABLE SQL, then the CREATE TABLE SQL, for the given module."
return get_sql_delete(mod) + get_sql_all(mod)
get_sql_reset.help_doc = "Prints the DROP TABLE SQL, then the CREATE TABLE SQL, for the given app(s)."
get_sql_reset.args = APP_ARGS
def get_sql_initial_data(mod):
"Returns a list of the initial INSERT SQL statements for the given module."
output = []
app_label = mod._MODELS[0]._meta.app_label
output.append(_get_packages_insert(app_label))
app_dir = os.path.normpath(os.path.join(os.path.dirname(mod.__file__), '../sql'))
for klass in mod._MODELS:
opts = klass._meta
# Add custom SQL, if it's available.
sql_file_name = os.path.join(app_dir, opts.module_name + '.sql')
if os.path.exists(sql_file_name):
fp = open(sql_file_name, 'r')
output.append(fp.read())
fp.close()
# Content types.
output.append(_get_contenttype_insert(opts))
# Permissions.
for codename, name in _get_all_permissions(opts):
output.append(_get_permission_insert(name, codename, opts))
return output
get_sql_initial_data.help_doc = "Prints the initial INSERT SQL statements for the given app(s)."
get_sql_initial_data.args = APP_ARGS
def get_sql_sequence_reset(mod):
"Returns a list of the SQL statements to reset PostgreSQL sequences for the given module."
output = []
for klass in mod._MODELS:
for f in klass._meta.fields:
if isinstance(f, meta.AutoField):
output.append("SELECT setval('%s_%s_seq', (SELECT max(%s) FROM %s));" % (klass._meta.db_table, f.name, f.name, klass._meta.db_table))
return output
get_sql_sequence_reset.help_doc = "Prints the SQL statements for resetting PostgreSQL sequences for the given app(s)."
get_sql_sequence_reset.args = APP_ARGS
def get_sql_indexes(mod):
"Returns a list of the CREATE INDEX SQL statements for the given module."
output = []
for klass in mod._MODELS:
for f in klass._meta.fields:
if f.db_index:
unique = f.unique and "UNIQUE " or ""
output.append("CREATE %sINDEX %s_%s ON %s (%s);" % \
(unique, klass._meta.db_table, f.name, klass._meta.db_table, f.name))
return output
get_sql_indexes.help_doc = "Prints the CREATE INDEX SQL statements for the given app(s)."
get_sql_indexes.args = APP_ARGS
def get_sql_all(mod):
"Returns a list of CREATE TABLE SQL and initial-data insert for the given module."
return get_sql_create(mod) + get_sql_initial_data(mod)
get_sql_all.help_doc = "Prints the CREATE TABLE and initial-data SQL statements for the given app(s)."
get_sql_all.args = APP_ARGS
def database_check(mod):
"Checks that everything is properly installed in the database for the given module."
cursor = db.db.cursor()
app_label = mod._MODELS[0]._meta.app_label
# Check that the package exists in the database.
cursor.execute("SELECT 1 FROM packages WHERE label = %s", [app_label])
if cursor.rowcount < 1:
# sys.stderr.write("The '%s' package isn't installed.\n" % app_label)
print _get_packages_insert(app_label)
# Check that the permissions and content types are in the database.
perms_seen = {}
contenttypes_seen = {}
for klass in mod._MODELS:
opts = klass._meta
perms = _get_all_permissions(opts)
perms_seen.update(dict(perms))
contenttypes_seen[opts.module_name] = 1
for codename, name in perms:
cursor.execute("SELECT 1 FROM auth_permissions WHERE package = %s AND codename = %s", (app_label, codename))
if cursor.rowcount < 1:
# sys.stderr.write("The '%s.%s' permission doesn't exist.\n" % (app_label, codename))
print _get_permission_insert(name, codename, opts)
cursor.execute("SELECT 1 FROM content_types WHERE package = %s AND python_module_name = %s", (app_label, opts.module_name))
if cursor.rowcount < 1:
# sys.stderr.write("The '%s.%s' content type doesn't exist.\n" % (app_label, opts.module_name))
print _get_contenttype_insert(opts)
# Check that there aren't any *extra* permissions in the DB that the model
# doesn't know about.
cursor.execute("SELECT codename FROM auth_permissions WHERE package = %s", (app_label,))
for row in cursor.fetchall():
try:
perms_seen[row[0]]
except KeyError:
# sys.stderr.write("A permission called '%s.%s' was found in the database but not in the model.\n" % (app_label, row[0]))
print "DELETE FROM auth_permissions WHERE package='%s' AND codename = '%s';" % (app_label, row[0])
# Check that there aren't any *extra* content types in the DB that the
# model doesn't know about.
cursor.execute("SELECT python_module_name FROM content_types WHERE package = %s", (app_label,))
for row in cursor.fetchall():
try:
contenttypes_seen[row[0]]
except KeyError:
# sys.stderr.write("A content type called '%s.%s' was found in the database but not in the model.\n" % (app_label, row[0]))
print "DELETE FROM content_types WHERE package='%s' AND python_module_name = '%s';" % (app_label, row[0])
database_check.help_doc = "Checks that everything is installed in the database for the given app(s) and prints SQL statements if needed."
database_check.args = APP_ARGS
def get_admin_index(mod):
"Returns admin-index template snippet (in list form) for the given module."
output = []
app_label = mod._MODELS[0]._meta.app_label
output.append('{%% if perms.%s %%}' % app_label)
output.append('<div class="module"><h2>%s</h2><table>' % app_label.title())
for klass in mod._MODELS:
if klass._meta.admin:
output.append(MODULE_TEMPLATE % {
'app': app_label,
'mod': klass._meta.module_name,
'name': meta.capfirst(klass._meta.verbose_name_plural),
'addperm': klass._meta.get_add_permission(),
'changeperm': klass._meta.get_change_permission(),
})
output.append('</table></div>')
output.append('{% endif %}')
return output
get_admin_index.help_doc = "Prints the admin-index template snippet for the given app(s)."
get_admin_index.args = APP_ARGS
def init():
"Initializes the database with auth and core."
auth = meta.get_app('auth')
core = meta.get_app('core')
try:
cursor = db.db.cursor()
for sql in get_sql_create(core) + get_sql_create(auth) + get_sql_initial_data(core) + get_sql_initial_data(auth):
cursor.execute(sql)
except Exception, e:
sys.stderr.write("Error: The database couldn't be initialized. Here's the full exception:\n%s\n" % e)
db.db.rollback()
sys.exit(1)
db.db.commit()
init.args = ''
def install(mod):
"Executes the equivalent of 'get_sql_all' in the current database."
sql_list = get_sql_all(mod)
try:
cursor = db.db.cursor()
for sql in sql_list:
cursor.execute(sql)
except Exception, e:
mod_name = mod.__name__[mod.__name__.rindex('.')+1:]
sys.stderr.write("""Error: %s couldn't be installed. Possible reasons:
* The database isn't running or isn't configured correctly.
* At least one of the database tables already exists.
* The SQL was invalid.
Hint: Look at the output of '%s sqlall %s'. That's the SQL this command wasn't able to run.
The full error: %s\n""" % \
(mod_name, __file__, mod_name, e))
db.db.rollback()
sys.exit(1)
db.db.commit()
install.args = APP_ARGS
def _start_helper(app_or_project, name, directory, other_name=''):
other = {'project': 'app', 'app': 'project'}[app_or_project]
if not _is_valid_dir_name(name):
sys.stderr.write("Error: %r is not a valid %s name. Please use only numbers, letters and underscores.\n" % (name, app_or_project))
sys.exit(1)
top_dir = os.path.join(directory, name)
try:
os.mkdir(top_dir)
except OSError, e:
sys.stderr.write("Error: %s\n" % e)
sys.exit(1)
template_dir = PROJECT_TEMPLATE_DIR % app_or_project
for d, subdirs, files in os.walk(template_dir):
relative_dir = d[len(template_dir)+1:].replace('%s_name' % app_or_project, name)
if relative_dir:
os.mkdir(os.path.join(top_dir, relative_dir))
for f in files:
fp_old = open(os.path.join(d, f), 'r')
fp_new = open(os.path.join(top_dir, relative_dir, f.replace('%s_name' % app_or_project, name)), 'w')
fp_new.write(fp_old.read().replace('{{ %s_name }}' % app_or_project, name).replace('{{ %s_name }}' % other, other_name))
fp_old.close()
fp_new.close()
def startproject(project_name, directory):
"Creates a Django project for the given project_name in the given directory."
_start_helper('project', project_name, directory)
startproject.help_doc = "Creates a Django project directory structure for the given project name in the current directory."
startproject.args = "[projectname]"
def startapp(app_name, directory):
"Creates a Django app for the given project_name in the given directory."
# Determine the project_name a bit naively -- by looking at the name of
# the parent directory.
project_dir = os.path.normpath(os.path.join(directory, '../'))
project_name = os.path.basename(project_dir)
_start_helper('app', app_name, directory, project_name)
settings_file = os.path.join(project_dir, 'settings/main.py')
if os.path.exists(settings_file):
try:
settings_contents = open(settings_file, 'r').read()
fp = open(settings_file, 'w')
except IOError:
pass
else:
settings_contents = re.sub(r'(?s)\b(INSTALLED_APPS\s*=\s*\()(.*?)\)', "\\1\n '%s',\\2)" % app_name, settings_contents)
fp.write(settings_contents)
fp.close()
startapp.help_doc = "Creates a Django app directory structure for the given app name in the current directory."
startapp.args = "[appname]"
def usage():
sys.stderr.write("Usage: %s [action]\n" % sys.argv[0])
available_actions = ACTION_MAPPING.keys()
available_actions.sort()
sys.stderr.write("Available actions:\n")
for a in available_actions:
func = ACTION_MAPPING[a]
sys.stderr.write(" %s %s-- %s\n" % (a, func.args, getattr(func, 'help_doc', func.__doc__)))
sys.exit(1)
ACTION_MAPPING = {
'adminindex': get_admin_index,
# 'dbcheck': database_check,
'sql': get_sql_create,
'sqlall': get_sql_all,
'sqlclear': get_sql_delete,
'sqlindexes': get_sql_indexes,
'sqlinitialdata': get_sql_initial_data,
'sqlreset': get_sql_reset,
'sqlsequencereset': get_sql_sequence_reset,
'startapp': startapp,
'startproject': startproject,
'init': init,
'install': install,
}
if __name__ == "__main__":
try:
action = sys.argv[1]
except IndexError:
usage()
if not ACTION_MAPPING.has_key(action):
usage()
if action == 'init':
init()
sys.exit(0)
elif action in ('startapp', 'startproject'):
try:
name = sys.argv[2]
except IndexError:
usage()
ACTION_MAPPING[action](name, os.getcwd())
sys.exit(0)
elif action == 'dbcheck':
mod_list = meta.get_all_installed_modules()
else:
try:
mod_list = [meta.get_app(app_label) for app_label in sys.argv[2:]]
except ImportError, e:
sys.stderr.write("Error: %s. Are you sure your INSTALLED_APPS setting is correct?\n" % e)
sys.exit(1)
if not mod_list:
usage()
if action not in ('adminindex', 'dbcheck', 'install', 'sqlindexes'):
print "BEGIN;"
for mod in mod_list:
output = ACTION_MAPPING[action](mod)
if output:
print '\n'.join(output)
if action not in ('adminindex', 'dbcheck', 'install', 'sqlindexes'):
print "COMMIT;"

View File

View File

@ -0,0 +1,34 @@
"""
gather_profile_stats.py /path/to/dir/of/profiles
Note that the aggregated profiles must be read with pstats.Stats, not
hotshot.stats (the formats are incompatible)
"""
from hotshot import stats
import pstats
import sys, os
def gather_stats(p):
profiles = {}
for f in os.listdir(p):
if f.endswith('.agg.prof'):
path = f[:-9]
prof = pstats.Stats(os.path.join(p, f))
elif f.endswith('.prof'):
bits = f.split('.')
path = ".".join(bits[:-3])
prof = stats.load(os.path.join(p, f))
else:
continue
print "Processing %s" % f
if profiles.has_key(path):
profiles[path].add(prof)
else:
profiles[path] = prof
os.unlink(os.path.join(p, f))
for (path, prof) in profiles.items():
prof.dump_stats(os.path.join(p, "%s.agg.prof" % path))
if __name__ == '__main__':
gather_stats(sys.argv[1])

View File

@ -0,0 +1,22 @@
import hotshot, time, os
from django.core.handler import CoreHandler
PROFILE_DATA_DIR = "/var/log/cmsprofile/"
def handler(req):
'''
Handler that uses hotshot to store profile data.
Stores profile data in PROFILE_DATA_DIR. Since hotshot has no way (that I
know of) to append profile data to a single file, each request gets its own
profile. The file names are in the format <url>.<n>.prof where <url> is
the request path with "/" replaced by ".", and <n> is a timestamp with
microseconds to prevent overwriting files.
Use the gather_profile_stats.py script to gather these individual request
profiles into aggregated profiles by request path.
'''
profname = "%s.%.3f.prof" % (req.uri.strip("/").replace('/', '.'), time.time())
profname = os.path.join(PROFILE_DATA_DIR, profname)
prof = hotshot.Profile(profname)
return prof.runcall(CoreHandler(), req)

45
django/bin/setup.py Normal file
View File

@ -0,0 +1,45 @@
"""
Usage:
python setup.py bdist
python setup.py sdist
"""
from distutils.core import setup
import os
# Whether to include the .py files, rather than just .pyc's. Doesn't do anything yet.
INCLUDE_SOURCE = True
# Determines which apps are bundled with the distribution.
INSTALLED_APPS = ('auth', 'categories', 'comments', 'core', 'media', 'news', 'polls', 'registration', 'search', 'sms', 'staff')
# First, lump together all the generic, core packages that need to be included.
packages = [
'django',
'django.core',
'django.templatetags',
'django.utils',
'django.views',
]
for a in INSTALLED_APPS:
for dirname in ('parts', 'templatetags', 'views'):
if os.path.exists('django/%s/%s/' % (dirname, a)):
packages.append('django.%s.%s' % (dirname, a))
# Next, add individual modules.
py_modules = [
'django.cron.daily_cleanup',
'django.cron.search_indexer',
]
py_modules += ['django.models.%s' % a for a in INSTALLED_APPS]
setup(
name = 'django',
version = '1.0',
packages = packages,
py_modules = py_modules,
url = 'http://www.ljworld.com/',
author = 'World Online',
author_email = 'cms-support@ljworld.com',
)

36
django/bin/validate.py Normal file
View File

@ -0,0 +1,36 @@
from django.core import meta
def validate_app(app_label):
mod = meta.get_app(app_label)
for klass in mod._MODELS:
try:
validate_class(klass)
except AssertionError, e:
print e
def validate_class(klass):
opts = klass._meta
# Fields.
for f in opts.fields:
if isinstance(f, meta.ManyToManyField):
assert isinstance(f.rel, meta.ManyToMany), "ManyToManyField %s should have 'rel' set to a ManyToMany instance." % f.name
# Inline related objects.
for rel_opts, rel_field in opts.get_inline_related_objects():
assert len([f for f in rel_opts.fields if f.core]) > 0, "At least one field in %s should have core=True, because it's being edited inline by %s." % (rel_opts.object_name, opts.object_name)
# All related objects.
related_apps_seen = []
for rel_opts, rel_field in opts.get_all_related_objects():
if rel_opts in related_apps_seen:
assert rel_field.rel.related_name is not None, "Relationship in field %s.%s needs to set 'related_name' because more than one %s object is referenced in %s." % (rel_opts.object_name, rel_field.name, opts.object_name, rel_opts.object_name)
related_apps_seen.append(rel_opts)
# Etc.
if opts.admin is not None:
assert opts.admin.ordering or opts.ordering, "%s needs to set 'ordering' on either its 'admin' or its model, because it has 'admin' set." % opts.object_name
if __name__ == "__main__":
import sys
try:
validate_app(sys.argv[1])
except IndexError:
sys.stderr.write("Usage: %s [appname]\n" % __file__)
sys.exit(1)

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

View File

View File

@ -0,0 +1 @@
__all__ = ['{{ app_name }}']

View File

@ -0,0 +1,3 @@
from django.core import meta
# Create your models here.

View File

@ -0,0 +1,5 @@
from django.conf.urls.defaults import *
urlpatterns = patterns('{{ project_name }}.apps.{{ app_name }}.views',
# (r'', ''),
)

View File

@ -0,0 +1,199 @@
# Default Django settings. Override these with settings in the module
# pointed-to by the DJANGO_SETTINGS_MODULE environment variable.
import re
####################
# CORE #
####################
DEBUG = False
# Whether to use the "Etag" header. This saves bandwidth but slows down performance.
USE_ETAGS = False
# people who get code error notifications
ADMINS = (('Adrian Holovaty','aholovaty@ljworld.com'), ('Jacob Kaplan-Moss', 'jacob@lawrence.com'))
# These IP addresses:
# * See debug comments, when DEBUG is true
# * Receive x-headers
INTERNAL_IPS = (
'24.124.4.220', # World Online offices
'24.124.1.4', # https://admin.6newslawrence.com/
'24.148.30.138', # Adrian home
'127.0.0.1', # localhost
)
# Local time zone for this installation. All choices can be found here:
# http://www.postgresql.org/docs/current/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
TIME_ZONE = 'America/Chicago'
# Language code for this installation. All choices can be found here:
# http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes
# http://blogs.law.harvard.edu/tech/stories/storyReader$15
LANGUAGE_CODE = 'en-us'
# Not-necessarily-technical managers of the site. They get broken link
# notifications and other various e-mails.
MANAGERS = ADMINS
# which e-mail address error messages come from
SERVER_EMAIL = None
# Whether to send broken-link e-mails
SEND_BROKEN_LINK_EMAILS = True
# postgres database connection info
DATABASE_ENGINE = 'postgresql'
DATABASE_NAME = 'cms'
DATABASE_USER = 'apache'
DATABASE_PASSWORD = ''
DATABASE_HOST = '' # set to empty string for localhost
# host for sending e-mail
EMAIL_HOST = 'localhost'
# name of the session cookie
AUTH_SESSION_COOKIE = 'rizzo'
# name of the authorization profile module (below django.apps)
AUTH_PROFILE_MODULE = ''
# list of locations of the template source files, in search order
TEMPLATE_DIRS = []
# default e-mail address to use for various automated correspondence from the site managers
DEFAULT_FROM_EMAIL = 'webmaster@ljworld.com'
# whether to append trailing slashes to URLs
APPEND_SLASH = True
# whether to prepend the "www." subdomain to URLs
PREPEND_WWW = False
# list of regular expressions representing User-Agent strings that are not
# allowed to visit any page, CMS-wide. Use this for bad robots/crawlers.
DISALLOWED_USER_AGENTS = (
re.compile(r'^NaverBot.*'),
re.compile(r'^EmailSiphon.*'),
re.compile(r'^SiteSucker.*'),
re.compile(r'^sohu-search')
)
ABSOLUTE_URL_OVERRIDES = {}
# list of allowed prefixes for the {% ssi %} tag
ALLOWED_INCLUDE_ROOTS = ('/home/html',)
# if this is a admin settings module, this should be a list of
# settings modules for which this admin is an admin for
ADMIN_FOR = []
# 404s that may be ignored
IGNORABLE_404_STARTS = ('/cgi-bin/', '/_vti_bin', '/_vti_inf')
IGNORABLE_404_ENDS = ('mail.pl', 'mailform.pl', 'mail.cgi', 'mailform.cgi', 'favicon.ico', '.php')
##############
# Middleware #
##############
# List of middleware classes to use. Order is important; in the request phase,
# this middleware classes will be applied in the order given, and in the
# response phase the middleware will be applied in reverse order.
MIDDLEWARE_CLASSES = (
"django.middleware.common.CommonMiddleware",
"django.middleware.doc.XViewMiddleware",
)
#########
# CACHE #
#########
# The cache backend to use. See the docstring in django.core.cache for the
# values this can be set to.
CACHE_BACKEND = 'simple://'
####################
# REGISTRATION #
####################
# E-mail addresses at these domains cannot sign up for accounts
BANNED_EMAIL_DOMAINS = [
'mailinator.com', 'dodgeit.com', 'spamgourmet.com', 'mytrashmail.com'
]
REGISTRATION_COOKIE_DOMAIN = None # set to a string like ".lawrence.com", or None for standard domain cookie
# If this is set to True, users will be required to fill out their profile
# (defined by AUTH_PROFILE_MODULE) before they will be allowed to create
# an account.
REGISTRATION_REQUIRES_PROFILE = False
####################
# COMMENTS #
####################
COMMENTS_ALLOW_PROFANITIES = False
# The group ID that designates which users are banned.
# Set to None if you're not using it.
COMMENTS_BANNED_USERS_GROUP = 19
# The group ID that designates which users can moderate comments.
# Set to None if you're not using it.
COMMENTS_MODERATORS_GROUP = 20
# The group ID that designates the users whose comments should be e-mailed to MANAGERS.
# Set to None if you're not using it.
COMMENTS_SKETCHY_USERS_GROUP = 22
# The system will e-mail MANAGERS the first COMMENTS_FIRST_FEW comments by each
# user. Set this to 0 if you want to disable it.
COMMENTS_FIRST_FEW = 10
BANNED_IPS = (
# Dupont Stainmaster / GuessWho / a variety of other names (back when we had free comments)
'204.94.104.99', '66.142.59.23', '220.196.165.142',
# (Unknown)
'64.65.191.117',
# # Jimmy_Olsen / Clark_Kent / Bruce_Wayne
# # Unbanned on 2005-06-17, because other people want to register from this address.
# '12.106.111.10',
# hoof_hearted / hugh_Jass / Ferd_Burfel / fanny_farkel
'24.124.72.20', '170.135.241.46',
# Zac_McGraw
'198.74.20.74', '198.74.20.75',
)
####################
# BLOGS #
####################
# E-mail addresses to notify when a new blog entry is posted live
BLOGS_EMAILS_TO_NOTIFY = []
####################
# PLACES #
####################
# A list of IDs -- *as integers, not strings* -- that are considered the "main"
# cities served by this installation. Probably just one.
MAIN_CITY_IDS = (1,) # Lawrence
# A list of IDs -- *as integers, not strings* -- that are considered "local" by
# this installation.
LOCAL_CITY_IDS = (1, 3) # Lawrence and Kansas City, MO
####################
# THUMBNAILS #
####################
THUMB_ALLOWED_WIDTHS = (90, 120, 180, 240, 450)
####################
# VARIOUS ROOTS #
####################
# This is the new media root and URL! Use it, and only it!
MEDIA_ROOT = '/home/media/media.lawrence.com/'
MEDIA_URL = 'http://media.lawrence.com'

View File

View File

@ -0,0 +1,31 @@
# Django settings for {{ app_name }} project.
DEBUG = False
ADMINS = (
# ('Your Name', 'your_email@domain.com'),
)
MANAGERS = ADMINS
LANGUAGE_CODE = 'en-us'
DATABASE_ENGINE = 'postgresql' # Either 'postgresql' or 'mysql'.
DATABASE_NAME = ''
DATABASE_USER = ''
DATABASE_HOST = '' # Set to empty string for localhost.
# Absolute path to the directory that holds media.
# Example: "/home/media/media.lawrence.com/"
MEDIA_ROOT = ''
# URL that handles the media served from MEDIA_ROOT.
# Example: "http://media.lawrence.com"
MEDIA_URL = ''
TEMPLATE_DIRS = (
# Put strings here, like "/home/html/django_templates".
)
INSTALLED_APPS = (
)

42
django/conf/settings.py Normal file
View File

@ -0,0 +1,42 @@
"""
Settings and configuration for Django.
Values will be read from the module specified by the DJANGO_SETTINGS_MODULE environment
variable, and then from django.conf.global_settings; see the global settings file for
a list of all possible variables.
"""
import os
import sys
from django.conf import global_settings
# get a reference to this module (why isn't there a __module__ magic var?)
me = sys.modules[__name__]
# update this dict from global settings (but only for ALL_CAPS settings)
for setting in dir(global_settings):
if setting == setting.upper():
setattr(me, setting, getattr(global_settings, setting))
# try to load DJANGO_SETTINGS_MODULE
try:
mod = __import__(os.environ['DJANGO_SETTINGS_MODULE'], '', '', [''])
except (KeyError, ImportError, ValueError):
pass
else:
for setting in dir(mod):
if setting == setting.upper():
setattr(me, setting, getattr(mod, setting))
# save DJANGO_SETTINGS_MODULE in case anyone in the future cares
me.SETTINGS_MODULE = os.environ.get('DJANGO_SETTINGS_MODULE', '')
# move the time zone info into os.environ
os.environ['TZ'] = me.TIME_ZONE
# finally, clean up my namespace
for k in dir(me):
if not k.startswith('_') and k != 'me' and k != k.upper():
delattr(me, k)
del me, k

View File

56
django/conf/urls/admin.py Normal file
View File

@ -0,0 +1,56 @@
from django.conf.urls.defaults import *
from django.conf.settings import INSTALLED_APPS
urlpatterns = (
('^/?$', 'django.views.admin.main.index'),
('^logout/$', 'django.views.admin.main.logout'),
('^password_change/$', 'django.views.registration.passwords.password_change'),
('^password_change/done/$', 'django.views.registration.passwords.password_change_done'),
('^template_validator/$', 'django.views.admin.template.template_validator'),
# Documentation
('^doc/$', 'django.views.admin.doc.doc_index'),
('^doc/bookmarklets/$', 'django.views.admin.doc.bookmarklets'),
('^doc/tags/$', 'django.views.admin.doc.template_tag_index'),
('^doc/filters/$', 'django.views.admin.doc.template_filter_index'),
('^doc/views/$', 'django.views.admin.doc.view_index'),
('^doc/views/jump/$', 'django.views.admin.doc.jump_to_view'),
('^doc/views/(?P<view>[^/]+)/$', 'django.views.admin.doc.view_detail'),
('^doc/models/$', 'django.views.admin.doc.model_index'),
('^doc/models/(?P<model>[^/]+)/$', 'django.views.admin.doc.model_detail'),
)
if 'ellington.events' in INSTALLED_APPS:
urlpatterns += (
("^events/usersubmittedevents/(?P<object_id>\d+)/$", 'ellington.events.views.admin.user_submitted_event_change_stage'),
("^events/usersubmittedevents/(?P<object_id>\d+)/delete/$", 'ellington.events.views.admin.user_submitted_event_delete_stage'),
)
if 'ellington.news' in INSTALLED_APPS:
urlpatterns += (
("^stories/preview/$", 'ellington.news.views.admin.story_preview'),
("^stories/js/inlinecontrols/$", 'ellington.news.views.admin.inlinecontrols_js'),
("^stories/js/inlinecontrols/(?P<label>[-\w]+)/$", 'ellington.news.views.admin.inlinecontrols_js_specific'),
)
if 'ellington.alerts' in INSTALLED_APPS:
urlpatterns += (
("^alerts/send/$", 'ellington.alerts.views.admin.send_alert_form'),
("^alerts/send/do/$", 'ellington.alerts.views.admin.send_alert_action'),
)
if 'ellington.media' in INSTALLED_APPS:
urlpatterns += (
('^media/photos/caption/(?P<photo_id>\d+)/$', 'ellington.media.views.admin.get_exif_caption'),
)
urlpatterns += (
# Metasystem admin pages
('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/$', 'django.views.admin.main.change_list'),
('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/add/$', 'django.views.admin.main.add_stage'),
('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/(?P<object_id>\d+)/$', 'django.views.admin.main.change_stage'),
('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/(?P<object_id>\d+)/delete/$', 'django.views.admin.main.delete_stage'),
('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/(?P<object_id>\d+)/history/$', 'django.views.admin.main.history'),
('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/jsvalidation/$', 'django.views.admin.jsvalidation.jsvalidation'),
)
urlpatterns = patterns('', *urlpatterns)

View File

@ -0,0 +1,6 @@
from django.conf.urls.defaults import *
urlpatterns = patterns('django.views',
(r'^/?$', 'registration.passwords.password_reset', {'is_admin_site' : True}),
(r'^done/$', 'registration.passwords.password_reset_done'),
)

View File

@ -0,0 +1,12 @@
from django.conf.urls.defaults import *
urlpatterns = patterns('django.views',
(r'^post/$', 'comments.comments.post_comment'),
(r'^postfree/$', 'comments.comments.post_free_comment'),
(r'^posted/$', 'comments.comments.comment_was_posted'),
(r'^karma/vote/(?P<comment_id>\d+)/(?P<vote>up|down)/$', 'comments.karma.vote'),
(r'^flag/(?P<comment_id>\d+)/$', 'comments.userflags.flag'),
(r'^flag/(?P<comment_id>\d+)/done/$', 'comments.userflags.flag_done'),
(r'^delete/(?P<comment_id>\d+)/$', 'comments.userflags.delete'),
(r'^delete/(?P<comment_id>\d+)/done/$', 'comments.userflags.delete_done'),
)

View File

@ -0,0 +1,17 @@
from django.core.urlresolvers import RegexURLMultiplePattern, RegexURLPattern
__all__ = ['handler404', 'handler500', 'include', 'patterns']
handler404 = 'django.views.defaults.page_not_found'
handler500 = 'django.views.defaults.server_error'
include = lambda urlconf_module: [urlconf_module]
def patterns(prefix, *tuples):
pattern_list = []
for t in tuples:
if type(t[1]) == list:
pattern_list.append(RegexURLMultiplePattern(t[0], t[1][0]))
else:
pattern_list.append(RegexURLPattern(t[0], prefix and (prefix + '.' + t[1]) or t[1], *t[2:]))
return pattern_list

View File

@ -0,0 +1,5 @@
from django.conf.urls.defaults import *
urlpatterns = patterns('django.views',
(r'^(?P<url>.*)$', 'core.flatfiles.flat_file'),
)

View File

@ -0,0 +1,19 @@
from django.conf.urls.defaults import *
urlpatterns = patterns('',
(r'^login/$', 'django.views.auth.login.login'),
(r'^logout/$', 'django.views.auth.login.logout'),
(r'^login_another/$', 'django.views.auth.login.logout_then_login'),
(r'^register/$', 'ellington.registration.views.registration.signup'),
(r'^register/(?P<challenge_string>\w{32})/$', 'ellington.registration.views.registration.register_form'),
(r'^profile/$', 'ellington.registration.views.profile.profile'),
(r'^profile/welcome/$', 'ellington.registration.views.profile.profile_welcome'),
(r'^profile/edit/$', 'ellington.registration.views.profile.edit_profile'),
(r'^password_reset/$', 'django.views.registration.passwords.password_reset'),
(r'^password_reset/done/$', 'django.views.registration.passwords.password_reset_done'),
(r'^password_change/$', 'django.views.registration.passwords.password_change'),
(r'^password_change/done/$', 'django.views.registration.passwords.password_change_done'),
)

6
django/conf/urls/rss.py Normal file
View File

@ -0,0 +1,6 @@
from django.conf.urls.defaults import *
urlpatterns = patterns('django.views',
(r'^(?P<slug>\w+)/$', 'rss.rss.feed'),
(r'^(?P<slug>\w+)/(?P<param>[\w/]+)/$', 'rss.rss.feed'),
)

View File

@ -0,0 +1,5 @@
from django.conf.urls.defaults import *
urlpatterns = patterns('django.views',
(r'^(?P<content_type_id>\d+)/(?P<object_id>\d+)/$', 'defaults.shortcut'),
)

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

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

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

View File

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

View File

View File

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

View File

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

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

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

View File

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

View File

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

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

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

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

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

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

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

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

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

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

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

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

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

2142
django/core/meta.py Normal file

File diff suppressed because it is too large Load Diff

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

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

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

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

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

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

View File

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

View File

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

View File

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

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

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

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

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

View File

120
django/middleware/admin.py Normal file
View File

@ -0,0 +1,120 @@
from django.utils import httpwrappers
from django.core import template_loader
from django.core.extensions import CMSContext as Context
from django.models.auth import sessions, users
from django.views.registration import passwords
import base64, md5
import cPickle as pickle
# secret used in pickled data to guard against tampering
TAMPER_SECRET = '09VJWE9_RIZZO_j0jwfe09j'
ERROR_MESSAGE = "Please enter a correct username and password. Note that both fields are case-sensitive."
class AdminUserRequired:
"""
Admin middleware. If this is enabled, access to the site will be granted only
to valid users with the "is_staff" flag set.
"""
def process_view(self, request, view_func, param_dict):
"""
Make sure the user is logged in and is a valid admin user before
allowing any access.
Done at the view point because we need to know if we're running the
password reset function.
"""
# If this is the password reset view, we don't want to require login
# Otherwise the password reset would need its own entry in the httpd
# conf, which is a little uglier than this.
if view_func == passwords.password_reset or view_func == passwords.password_reset_done:
return
# Check for a logged in, valid user
if self.user_is_valid(request.user):
return
# If this isn't alreay the login page, display it
if not request.POST.has_key('this_is_the_login_form'):
if request.POST:
message = "Please log in again, because your session has expired. "\
"Don't worry: Your submission has been saved."
else:
message = ""
return self.display_login_form(request, message)
# Check the password
username = request.POST.get('username', '')
try:
user = users.get_object(username__exact=username)
except users.UserDoesNotExist:
message = ERROR_MESSAGE
if '@' in username:
# Mistakenly entered e-mail address instead of username? Look it up.
try:
user = users.get_object(email__exact=username)
except users.UserDoesNotExist:
message = "Usernames cannot contain the '@' character."
else:
message = "Your e-mail address is not your username. Try '%s' instead." % user.username
return self.display_login_form(request, message)
# The user data is correct; log in the user in and continue
else:
if self.authenticate_user(user, request.POST.get('password', '')):
if request.POST.has_key('post_data'):
post_data = decode_post_data(request.POST['post_data'])
if post_data and not post_data.has_key('this_is_the_login_form'):
# overwrite request.POST with the saved post_data, and continue
request.POST = post_data
request.user = user
request.session = sessions.create_session(user.id)
return
else:
response = httpwrappers.HttpResponseRedirect(request.path)
sessions.start_web_session(user.id, request, response)
return response
else:
return self.display_login_form(request, ERROR_MESSAGE)
def display_login_form(self, request, error_message=''):
if request.POST and request.POST.has_key('post_data'):
# User has failed login BUT has previously saved 'post_data'
post_data = request.POST['post_data']
elif request.POST:
# User's session must have expired; save their post data
post_data = encode_post_data(request.POST)
else:
post_data = encode_post_data({})
t = template_loader.get_template(self.get_login_template_name())
c = Context(request, {
'title': 'Log in',
'app_path': request.path,
'post_data': post_data,
'error_message': error_message
})
return httpwrappers.HttpResponse(t.render(c))
def authenticate_user(self, user, password):
return user.check_password(password) and user.is_staff
def user_is_valid(self, user):
return not user.is_anonymous() and user.is_staff
def get_login_template_name(self):
return "login"
def encode_post_data(post_data):
pickled = pickle.dumps(post_data)
pickled_md5 = md5.new(pickled + TAMPER_SECRET).hexdigest()
return base64.encodestring(pickled + pickled_md5)
def decode_post_data(encoded_data):
encoded_data = base64.decodestring(encoded_data)
pickled, tamper_check = encoded_data[:-32], encoded_data[-32:]
if md5.new(pickled + TAMPER_SECRET).hexdigest() != tamper_check:
from django.core.exceptions import SuspiciousOperation
raise SuspiciousOperation, "User may have tampered with session cookie."
return pickle.loads(pickled)

104
django/middleware/common.py Normal file
View File

@ -0,0 +1,104 @@
from django.conf import settings
from django.core import exceptions
from django.utils import httpwrappers
from django.core.mail import mail_managers
from django.views.core.flatfiles import flat_file
import md5, os
from urllib import urlencode
class CommonMiddleware:
"""
"Common" middleware for taking care of some basic operations:
- Forbids access to User-Agents in settings.DISALLOWED_USER_AGENTS
- URL rewriting: based on the APPEND_SLASH and PREPEND_WWW settings,
this middleware will -- shocking, isn't it -- append missing slashes
and/or prepend missing "www."s.
- ETags: if the USE_ETAGS setting is set, ETags will be calculated from
the entire page content and Not Modified responses will be returned
appropriately.
- Flat files: for 404 responses, a flat file matching the given path
will be looked up and used if found.
You probably want the CommonMiddleware object to the first entry in your
MIDDLEWARE_CLASSES setting;
"""
def process_request(self, request):
"""
Check for denied User-Agents and rewrite the URL based on
settings.APPEND_SLASH and settings.PREPEND_WWW
"""
# Check for denied User-Agents
if request.META.has_key('HTTP_USER_AGENT'):
for user_agent_regex in settings.DISALLOWED_USER_AGENTS:
if user_agent_regex.search(request.META['HTTP_USER_AGENT']):
return httpwrappers.HttpResponseForbidden('<h1>Forbidden</h1>')
# Check for a redirect based on settings.APPEND_SLASH and settings.PREPEND_WWW
old_url = [request.META['HTTP_HOST'], request.path]
new_url = old_url[:]
if settings.PREPEND_WWW and not old_url[0].startswith('www.'):
new_url[0] = 'www.' + old_url[0]
# Append a slash if append_slash is set and the URL doesn't have a
# trailing slash or a file extension.
if settings.APPEND_SLASH and (old_url[1][-1] != '/') and ('.' not in old_url[1].split('/')[-1]):
new_url[1] = new_url[1] + '/'
if new_url != old_url:
# Redirect
newurl = "%s://%s%s" % (os.environ.get('HTTPS') == 'on' and 'https' or 'http', new_url[0], new_url[1])
if request.GET:
newurl += '?' + urlencode(request.GET)
return httpwrappers.HttpResponseRedirect(newurl)
return None
def process_response(self, request, response):
"""
Check for a flatfile (for 404s) and calculate the Etag, if needed.
"""
# If this was a 404, check for a flat file
if response.status_code == 404:
try:
response = flat_file(request, request.path)
except exceptions.Http404:
# If the referrer was from an internal link or a non-search-engine site,
# send a note to the managers.
if settings.SEND_BROKEN_LINK_EMAILS:
domain = request.META['HTTP_HOST']
referer = request.META.get('HTTP_REFERER', None)
is_internal = referer and (domain in referer)
path = request.get_full_path()
if referer and not _is_ignorable_404(path) and (is_internal or '?' not in referer):
mail_managers("Broken %slink on %s" % ((is_internal and 'INTERNAL ' or ''), domain),
"Referrer: %s\nRequested URL: %s\n" % (referer, request.get_full_path()))
# If there's no flatfile we want to return the original 404 response
return response
# Use ETags, if requested
if settings.USE_ETAGS:
etag = md5.new(response.get_content_as_string('utf-8')).hexdigest()
if request.META.get('HTTP_IF_NONE_MATCH') == etag:
response = httpwrappers.HttpResponseNotModified()
else:
response['ETag'] = etag
return response
def _is_ignorable_404(uri):
"Returns True if a 404 at the given URL *shouldn't* notify the site managers"
for start in settings.IGNORABLE_404_STARTS:
if uri.startswith(start):
return True
for end in settings.IGNORABLE_404_ENDS:
if uri.endswith(end):
return True
if '_files' in uri:
# URI is probably from a locally-saved copy of the page.
return True
return False

18
django/middleware/doc.py Normal file
View File

@ -0,0 +1,18 @@
from django.conf import settings
from django.utils import httpwrappers
class XViewMiddleware:
"""
Adds an X-View header to internal HEAD requests -- used by the documentation system.
"""
def process_view(self, request, view_func, param_dict):
"""
If the request method is HEAD and the IP is internal, quickly return
with an x-header indicating the view function. This is used by the
documentation module to lookup the view function for an arbitrary page.
"""
if request.META['REQUEST_METHOD'] == 'HEAD' and request.META['REMOTE_ADDR'] in settings.INTERNAL_IPS:
response = httpwrappers.HttpResponse()
response['X-View'] = "%s.%s" % (view_func.__module__, view_func.__name__)
return response

91
django/models/__init__.py Normal file
View File

@ -0,0 +1,91 @@
from django.core import meta
__all__ = ['auth', 'comments', 'core']
# Alter this package's __path__ variable so that calling code can import models
# from "django.models" even though the model code doesn't physically live
# within django.models.
for mod in meta.get_installed_models():
__path__.extend(mod.__path__)
# First, import all models so the metaclasses run.
modules = meta.get_installed_model_modules(__all__)
# Now, create the extra methods that we couldn't create earlier because
# relationships hadn't been known until now.
for mod in modules:
for klass in mod._MODELS:
# Add "get_thingie", "get_thingie_count" and "get_thingie_list" methods
# for all related objects.
for rel_obj, rel_field in klass._meta.get_all_related_objects():
# Determine whether this related object is in another app.
# If it's in another app, the method names will have the app
# label prepended, and the add_BLAH() method will not be
# generated.
rel_mod = rel_obj.get_model_module()
rel_obj_name = klass._meta.get_rel_object_method_name(rel_obj, rel_field)
if isinstance(rel_field.rel, meta.OneToOne):
# Add "get_thingie" methods for one-to-one related objects.
# EXAMPLE: Place.get_restaurants_restaurant()
func = meta.curry(meta.method_get_related, 'get_object', rel_mod, rel_field)
func.__doc__ = "Returns the associated `%s.%s` object." % (rel_obj.app_label, rel_obj.module_name)
setattr(klass, 'get_%s' % rel_obj_name, func)
elif isinstance(rel_field.rel, meta.ManyToOne):
# Add "get_thingie" methods for many-to-one related objects.
# EXAMPLE: Poll.get_choice()
func = meta.curry(meta.method_get_related, 'get_object', rel_mod, rel_field)
func.__doc__ = "Returns the associated `%s.%s` object matching the given criteria." % (rel_obj.app_label, rel_obj.module_name)
setattr(klass, 'get_%s' % rel_obj_name, func)
# Add "get_thingie_count" methods for many-to-one related objects.
# EXAMPLE: Poll.get_choice_count()
func = meta.curry(meta.method_get_related, 'get_count', rel_mod, rel_field)
func.__doc__ = "Returns the number of associated `%s.%s` objects." % (rel_obj.app_label, rel_obj.module_name)
setattr(klass, 'get_%s_count' % rel_obj_name, func)
# Add "get_thingie_list" methods for many-to-one related objects.
# EXAMPLE: Poll.get_choice_list()
func = meta.curry(meta.method_get_related, 'get_list', rel_mod, rel_field)
func.__doc__ = "Returns a list of associated `%s.%s` objects." % (rel_obj.app_label, rel_obj.module_name)
setattr(klass, 'get_%s_list' % rel_obj_name, func)
# Add "add_thingie" methods for many-to-one related objects,
# but only for related objects that are in the same app.
# EXAMPLE: Poll.add_choice()
if rel_obj.app_label == klass._meta.app_label:
func = meta.curry(meta.method_add_related, rel_obj, rel_mod, rel_field)
func.alters_data = True
setattr(klass, 'add_%s' % rel_obj_name, func)
del func
del rel_obj_name, rel_mod, rel_obj, rel_field # clean up
# Do the same for all related many-to-many objects.
for rel_opts, rel_field in klass._meta.get_all_related_many_to_many_objects():
rel_mod = rel_opts.get_model_module()
rel_obj_name = klass._meta.get_rel_object_method_name(rel_opts, rel_field)
setattr(klass, 'get_%s' % rel_obj_name, meta.curry(meta.method_get_related_many_to_many, 'get_object', rel_mod, rel_field))
setattr(klass, 'get_%s_count' % rel_obj_name, meta.curry(meta.method_get_related_many_to_many, 'get_count', rel_mod, rel_field))
setattr(klass, 'get_%s_list' % rel_obj_name, meta.curry(meta.method_get_related_many_to_many, 'get_list', rel_mod, rel_field))
if rel_opts.app_label == klass._meta.app_label:
func = meta.curry(meta.method_set_related_many_to_many, rel_opts, rel_field)
func.alters_data = True
setattr(klass, 'set_%s' % rel_opts.module_name, func)
del func
del rel_obj_name, rel_mod, rel_opts, rel_field # clean up
# Add "set_thingie_order" and "get_thingie_order" methods for objects
# that are ordered with respect to this.
for obj in klass._meta.get_ordered_objects():
func = meta.curry(meta.method_set_order, obj)
func.__doc__ = "Sets the order of associated `%s.%s` objects to the given ID list." % (obj.app_label, obj.module_name)
func.alters_data = True
setattr(klass, 'set_%s_order' % obj.object_name.lower(), func)
func = meta.curry(meta.method_get_order, obj)
func.__doc__ = "Returns the order of associated `%s.%s` objects as a list of IDs." % (obj.app_label, obj.module_name)
setattr(klass, 'get_%s_order' % obj.object_name.lower(), func)
del func, obj # clean up
del klass # clean up
del mod
del modules
# Expose get_app and get_module.
from django.core.meta import get_app, get_module

290
django/models/auth.py Normal file
View File

@ -0,0 +1,290 @@
from django.core import meta, validators
from django.models import core
class Permission(meta.Model):
fields = (
meta.CharField('name', 'name', maxlength=50),
meta.ForeignKey(core.Package, name='package'),
meta.CharField('codename', 'code name', maxlength=100),
)
unique_together = (('package', 'codename'),)
ordering = (('package', 'ASC'), ('codename', 'ASC'))
def __repr__(self):
return "%s | %s" % (self.package, self.name)
class Group(meta.Model):
fields = (
meta.CharField('name', 'name', maxlength=80, unique=True),
meta.ManyToManyField(Permission, blank=True, filter_interface=meta.HORIZONTAL),
)
ordering = (('name', 'ASC'),)
admin = meta.Admin(
fields = (
(None, {'fields': ('name', 'permissions')}),
),
search_fields = ('name',),
)
def __repr__(self):
return self.name
class User(meta.Model):
fields = (
meta.CharField('username', 'username', maxlength=30, unique=True,
validator_list=[validators.isAlphaNumeric]),
meta.CharField('first_name', 'first name', maxlength=30, blank=True),
meta.CharField('last_name', 'last name', maxlength=30, blank=True),
meta.EmailField('email', 'e-mail address', blank=True),
meta.CharField('password_md5', 'password', maxlength=32),
meta.BooleanField('is_staff', 'staff status',
help_text="Designates whether the user can log into this admin site."),
meta.BooleanField('is_active', 'active', default=True),
meta.BooleanField('is_superuser', 'superuser status'),
meta.DateTimeField('last_login', 'last login', default=meta.LazyDate()),
meta.DateTimeField('date_joined', 'date joined', default=meta.LazyDate()),
meta.ManyToManyField(Group, blank=True,
help_text="In addition to the permissions manually assigned, this user will also get all permissions granted to each group he/she is in."),
meta.ManyToManyField(Permission, name='user_permissions', blank=True, filter_interface=meta.HORIZONTAL),
)
ordering = (('username', 'ASC'),)
exceptions = ('SiteProfileNotAvailable',)
admin = meta.Admin(
fields = (
(None, {'fields': ('username', 'password_md5')}),
('Personal info', {'fields': ('first_name', 'last_name', 'email')}),
('Permissions', {'fields': ('is_staff', 'is_active', 'is_superuser', 'user_permissions')}),
('Important dates', {'fields': ('last_login', 'date_joined')}),
('Groups', {'fields': ('groups',)}),
),
list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff'),
list_filter = ('is_staff', 'is_superuser'),
search_fields = ('username', 'first_name', 'last_name', 'email'),
)
def __repr__(self):
return self.username
def get_absolute_url(self):
return "/users/%s/" % self.username
def is_anonymous(self):
return False
def get_full_name(self):
full_name = '%s %s' % (self.first_name, self.last_name)
return full_name.strip()
def set_password(self, raw_password):
import md5
self.password_md5 = md5.new(raw_password).hexdigest()
def check_password(self, raw_password):
"Returns a boolean of whether the raw_password was correct."
import md5
return self.password_md5 == md5.new(raw_password).hexdigest()
def get_group_permissions(self):
"Returns a list of permission strings that this user has through his/her groups."
if not hasattr(self, '_group_perm_cache'):
import sets
cursor = db.cursor()
cursor.execute("""
SELECT p.package, p.codename
FROM auth_permissions p, auth_groups_permissions gp, auth_users_groups ug
WHERE p.id = gp.permission_id
AND gp.group_id = ug.group_id
AND ug.user_id = %s""", [self.id])
self._group_perm_cache = sets.Set(["%s.%s" % (row[0], row[1]) for row in cursor.fetchall()])
return self._group_perm_cache
def get_all_permissions(self):
if not hasattr(self, '_perm_cache'):
import sets
self._perm_cache = sets.Set(["%s.%s" % (p.package, p.codename) for p in self.get_user_permissions()])
self._perm_cache.update(self.get_group_permissions())
return self._perm_cache
def has_perm(self, perm):
"Returns True if the user has the specified permission."
if not self.is_active:
return False
if self.is_superuser:
return True
return perm in self.get_all_permissions()
def has_perms(self, perm_list):
"Returns True if the user has each of the specified permissions."
for perm in perm_list:
if not self.has_perm(perm):
return False
return True
def has_module_perms(self, package_name):
"Returns True if the user has any permissions in the given package."
if self.is_superuser:
return True
return bool(len([p for p in self.get_all_permissions() if p[:p.index('.')] == package_name]))
def get_and_delete_messages(self):
messages = []
for m in self.get_message_list():
messages.append(m.message)
m.delete()
return messages
def email_user(self, subject, message, from_email=None):
"Sends an e-mail to this User."
from django.core.mail import send_mail
send_mail(subject, message, from_email, [self.email])
def get_profile(self):
"""
Returns site-specific profile for this user. Raises
SiteProfileNotAvailable if this site does not allow profiles.
"""
if not hasattr(self, '_profile_cache'):
from django.conf.settings import AUTH_PROFILE_MODULE
if not AUTH_PROFILE_MODULE:
raise SiteProfileNotAvailable
try:
app, mod = AUTH_PROFILE_MODULE.split('.')
module = __import__('ellington.%s.apps.%s' % (app, mod), [], [], [''])
self._profile_cache = module.get_object(user_id=self.id)
except ImportError:
try:
module = __import__('django.models.%s' % AUTH_PROFILE_MODULE, [], [], [''])
self._profile_cache = module.get_object(user_id__exact=self.id)
except ImportError:
raise SiteProfileNotAvailable
return self._profile_cache
def _module_create_user(username, email, password):
"Creates and saves a User with the given username, e-mail and password."
import md5
password_md5 = md5.new(password).hexdigest()
now = datetime.datetime.now()
user = User(None, username, '', '', email.strip().lower(), password_md5, False, True, False, now, now)
user.save()
return user
def _module_make_random_password(length=10, allowed_chars='abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'):
"Generates a random password with the given length and given allowed_chars"
# Note that default value of allowed_chars does not have "I" or letters
# that look like it -- just to avoid confusion.
from whrandom import choice
return ''.join([choice(allowed_chars) for i in range(length)])
class Session(meta.Model):
fields = (
meta.ForeignKey(User),
meta.CharField('session_md5', 'session MD5 hash', maxlength=32),
meta.DateTimeField('start_time', 'start time', auto_now=True),
)
module_constants = {
# Used for providing pseudo-entropy in creating random session strings.
'SESSION_SALT': 'ijw2f3_MUPPET_avo#*5)(*',
# Secret used in cookie to guard against cookie tampering.
'TAMPER_SECRET': 'lj908_PIGGY_j0vajeawej-092j3f',
'TEST_COOKIE_NAME': 'testcookie',
'TEST_COOKIE_VALUE': 'worked',
}
def __repr__(self):
return "session started at %s" % self.start_time
def get_cookie(self):
"Returns a tuple of the cookie name and value for this session."
import md5
from django.conf.settings import AUTH_SESSION_COOKIE
return AUTH_SESSION_COOKIE, self.session_md5 + md5.new(self.session_md5 + TAMPER_SECRET).hexdigest()
def _module_create_session(user_id):
"Registers a session and returns the session_md5."
import md5, random, sys
# The random module is seeded when this Apache child is created.
# Use person_id and SESSION_SALT as added salt.
session_md5 = md5.new(str(random.randint(user_id, sys.maxint - 1)) + SESSION_SALT).hexdigest()
s = Session(None, user_id, session_md5, None)
s.save()
return s
def _module_get_session_from_cookie(session_cookie_string):
import md5
if not session_cookie_string:
raise SessionDoesNotExist
session_md5, tamper_check = session_cookie_string[:32], session_cookie_string[32:]
if md5.new(session_md5 + TAMPER_SECRET).hexdigest() != tamper_check:
raise SuspiciousOperation, "User may have tampered with session cookie."
return get_object(session_md5__exact=session_md5, select_related=True)
def _module_destroy_all_sessions(user_id):
"Destroys all sessions for a user, logging out all computers."
for session in get_list(user_id__exact=user_id):
session.delete()
def _module_start_web_session(user_id, request, response):
"Sets the necessary cookie in the given HttpResponse object, also updates last login time for user."
from django.models.auth import users
from django.conf.settings import REGISTRATION_COOKIE_DOMAIN
user = users.get_object(id__exact=user_id)
user.last_login = datetime.datetime.now()
user.save()
session = create_session(user_id)
key, value = session.get_cookie()
cookie_domain = REGISTRATION_COOKIE_DOMAIN or request.META['SERVER_NAME']
response.set_cookie(key, value, domain=cookie_domain)
class Message(meta.Model):
fields = (
meta.AutoField('id', 'ID', primary_key=True),
meta.ForeignKey(User),
meta.TextField('message', 'message'),
)
def __repr__(self):
return self.message
class LogEntry(meta.Model):
module_name = 'log'
verbose_name_plural = 'log entries'
db_table = 'auth_admin_log'
fields = (
meta.DateTimeField('action_time', 'action time', auto_now=True),
meta.ForeignKey(User),
meta.ForeignKey(core.ContentType, name='content_type_id', rel_name='content_type', blank=True, null=True),
meta.IntegerField('object_id', 'object ID', blank=True, null=True),
meta.CharField('object_repr', 'object representation', maxlength=200),
meta.PositiveSmallIntegerField('action_flag', 'action flag'),
meta.TextField('change_message', 'change message', blank=True),
)
ordering = (('action_time', 'DESC'),)
module_constants = {
'ADDITION': 1,
'CHANGE': 2,
'DELETION': 3,
}
def __repr__(self):
return str(self.action_time)
def is_addition(self):
return self.action_flag == ADDITION
def is_change(self):
return self.action_flag == CHANGE
def is_deletion(self):
return self.action_flag == DELETION
def get_edited_object(self):
"Returns the edited object represented by this log entry"
return self.get_content_type().get_object_for_this_type(id__exact=self.object_id)
def get_admin_url(self):
"Returns the admin URL to edit the object represented by this log entry"
return "/%s/%s/%s/" % (self.get_content_type().package, self.get_content_type().python_module_name, self.object_id)
def _module_log_action(user_id, content_type_id, object_id, object_repr, action_flag, change_message=''):
e = LogEntry(None, None, user_id, content_type_id, object_id, object_repr[:200], action_flag, change_message)
e.save()

281
django/models/comments.py Normal file
View File

@ -0,0 +1,281 @@
from django.core import meta
from django.models import auth, core
class Comment(meta.Model):
db_table = 'comments'
fields = (
meta.ForeignKey(auth.User, raw_id_admin=True),
meta.ForeignKey(core.ContentType, name='content_type_id', rel_name='content_type'),
meta.IntegerField('object_id', 'object ID'),
meta.CharField('headline', 'headline', maxlength=255, blank=True),
meta.TextField('comment', 'comment', maxlength=3000),
meta.PositiveSmallIntegerField('rating1', 'rating #1', blank=True, null=True),
meta.PositiveSmallIntegerField('rating2', 'rating #2', blank=True, null=True),
meta.PositiveSmallIntegerField('rating3', 'rating #3', blank=True, null=True),
meta.PositiveSmallIntegerField('rating4', 'rating #4', blank=True, null=True),
meta.PositiveSmallIntegerField('rating5', 'rating #5', blank=True, null=True),
meta.PositiveSmallIntegerField('rating6', 'rating #6', blank=True, null=True),
meta.PositiveSmallIntegerField('rating7', 'rating #7', blank=True, null=True),
meta.PositiveSmallIntegerField('rating8', 'rating #8', blank=True, null=True),
# This field designates whether to use this row's ratings in
# aggregate functions (summaries). We need this because people are
# allowed to post multiple review on the same thing, but the system
# will only use the latest one (with valid_rating=True) in tallying
# the reviews.
meta.BooleanField('valid_rating', 'is valid rating'),
meta.DateTimeField('submit_date', 'date/time submitted', auto_now_add=True),
meta.BooleanField('is_public', 'is public'),
meta.IPAddressField('ip_address', 'IP address', blank=True, null=True),
meta.BooleanField('is_removed', 'is removed',
help_text='Check this box if the comment is inappropriate. A "This comment has been removed" message will be displayed instead.'),
meta.ForeignKey(core.Site),
)
module_constants = {
# used as shared secret between comment form and comment-posting script
'COMMENT_SALT': 'ijw2f3_MRS_PIGGY_LOVES_KERMIT_avo#*5vv0(23j)(*',
# min. and max. allowed dimensions for photo resizing (in pixels)
'MIN_PHOTO_DIMENSION': 5,
'MAX_PHOTO_DIMENSION': 1000,
# option codes for comment-form hidden fields
'PHOTOS_REQUIRED': 'pr',
'PHOTOS_OPTIONAL': 'pa',
'RATINGS_REQUIRED': 'rr',
'RATINGS_OPTIONAL': 'ra',
'IS_PUBLIC': 'ip',
}
ordering = (('submit_date', 'DESC'),)
admin = meta.Admin(
fields = (
(None, {'fields': ('content_type_id', 'object_id', 'site_id')}),
('Content', {'fields': ('user_id', 'headline', 'comment')}),
('Ratings', {'fields': ('rating1', 'rating2', 'rating3', 'rating4', 'rating5', 'rating6', 'rating7', 'rating8', 'valid_rating')}),
('Meta', {'fields': ('is_public', 'is_removed', 'ip_address')}),
),
list_display = ('user_id', 'submit_date', 'content_type_id', 'get_content_object'),
list_filter = ('submit_date',),
date_hierarchy = 'submit_date',
search_fields = ('comment', 'user__username'),
)
def __repr__(self):
return "%s: %s..." % (self.get_user().username, self.comment[:100])
def get_absolute_url(self):
return self.get_content_object().get_absolute_url() + "#c" + str(self.id)
def get_crossdomain_url(self):
return "/r/%d/%d/" % (self.content_type_id, self.object_id)
def get_flag_url(self):
return "/comments/flag/%s/" % self.id
def get_deletion_url(self):
return "/comments/delete/%s/" % self.id
def get_content_object(self):
"""
Returns the object that this comment is a comment on. Returns None if
the object no longer exists.
"""
from django.core.exceptions import ObjectDoesNotExist
try:
return self.get_content_type().get_object_for_this_type(id__exact=self.object_id)
except ObjectDoesNotExist:
return None
get_content_object.short_description = 'Content object'
def _fill_karma_cache(self):
"Helper function that populates good/bad karma caches"
good, bad = 0, 0
for k in self.get_karmascore_list():
if k.score == -1:
bad +=1
elif k.score == 1:
good +=1
self._karma_total_good, self._karma_total_bad = good, bad
def get_good_karma_total(self):
if not hasattr(self, "_karma_total_good"):
self._fill_karma_cache()
return self._karma_total_good
def get_bad_karma_total(self):
if not hasattr(self, "_karma_total_bad"):
self._fill_karma_cache()
return self._karma_total_bad
def get_karma_total(self):
if not hasattr(self, "_karma_total_good") or not hasattr(self, "_karma_total_bad"):
self._fill_karma_cache()
return self._karma_total_good + self._karma_total_bad
def get_as_text(self):
return 'Posted by %s at %s\n\n%s\n\nhttp://%s%s' % \
(self.get_user().username, self.submit_date,
self.comment, self.get_site().domain, self.get_absolute_url())
def _module_get_security_hash(options, photo_options, rating_options, target):
"""
Returns the MD5 hash of the given options (a comma-separated string such as
'pa,ra') and target (something like 'lcom.eventtimes:5157'). Used to
validate that submitted form options have not been tampered-with.
"""
import md5
return md5.new(options + photo_options + rating_options + target + COMMENT_SALT).hexdigest()
def _module_get_rating_options(rating_string):
"""
Given a rating_string, this returns a tuple of (rating_range, options).
>>> s = "scale:1-10|First_category|Second_category"
>>> get_rating_options(s)
([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ['First category', 'Second category'])
"""
rating_range, options = rating_string.split('|', 1)
rating_range = range(int(rating_range[6:].split('-')[0]), int(rating_range[6:].split('-')[1])+1)
choices = [c.replace('_', ' ') for c in options.split('|')]
return rating_range, choices
def _module_get_list_with_karma(**kwargs):
"""
Returns a list of Comment objects matching the given lookup terms, with
_karma_total_good and _karma_total_bad filled.
"""
kwargs.setdefault('select', {})
kwargs['select']['_karma_total_good'] = 'SELECT COUNT(*) FROM comments_karma WHERE comments_karma.comment_id=comments.id AND score=1'
kwargs['select']['_karma_total_bad'] = 'SELECT COUNT(*) FROM comments_karma WHERE comments_karma.comment_id=comments.id AND score=-1'
return get_list(**kwargs)
def _module_user_is_moderator(user):
from django.conf.settings import COMMENTS_MODERATORS_GROUP
if user.is_superuser:
return True
for g in user.get_groups():
if g.id == COMMENTS_MODERATORS_GROUP:
return True
return False
class FreeComment(meta.Model):
"A FreeComment is a comment by a non-registered user"
db_table = 'comments_free'
fields = (
meta.ForeignKey(core.ContentType, name='content_type_id', rel_name='content_type'),
meta.IntegerField('object_id', 'object ID'),
meta.TextField('comment', 'comment', maxlength=3000),
meta.CharField('person_name', "person's name", maxlength=50),
meta.DateTimeField('submit_date', 'date/time submitted', auto_now_add=True),
meta.BooleanField('is_public', 'is public'),
meta.IPAddressField('ip_address', 'IP address'),
# TODO: Change this to is_removed, like Comment
meta.BooleanField('approved', 'approved by staff'),
meta.ForeignKey(core.Site),
)
ordering = (('submit_date', 'DESC'),)
admin = meta.Admin(
fields = (
(None, {'fields': ('content_type_id', 'object_id', 'site_id')}),
('Content', {'fields': ('person_name', 'comment')}),
('Meta', {'fields': ('submit_date', 'is_public', 'ip_address', 'approved')}),
),
list_display = ('person_name', 'submit_date', 'content_type_id', 'get_content_object'),
list_filter = ('submit_date',),
date_hierarchy = 'submit_date',
search_fields = ('comment', 'person_name'),
)
def __repr__(self):
return "%s: %s..." % (self.person_name, self.comment[:100])
def get_content_object(self):
"""
Returns the object that this comment is a comment on. Returns None if
the object no longer exists.
"""
from django.core.exceptions import ObjectDoesNotExist
try:
return self.get_content_type().get_object_for_this_type(id__exact=self.object_id)
except ObjectDoesNotExist:
return None
get_content_object.short_description = 'Content object'
class KarmaScore(meta.Model):
module_name = 'karma'
fields = (
meta.ForeignKey(auth.User),
meta.ForeignKey(Comment),
meta.SmallIntegerField('score', 'score', db_index=True),
meta.DateTimeField('scored_date', 'date scored', auto_now=True),
)
unique_together = (('user_id', 'comment_id'),)
module_constants = {
# what users get if they don't have any karma
'DEFAULT_KARMA': 5,
'KARMA_NEEDED_BEFORE_DISPLAYED': 3,
}
def __repr__(self):
return "%d rating by %s" % (self.score, self.get_user())
def _module_vote(user_id, comment_id, score):
try:
karma = get_object(comment_id__exact=comment_id, user_id__exact=user_id)
except KarmaScoreDoesNotExist:
karma = KarmaScore(None, user_id, comment_id, score, datetime.datetime.now())
karma.save()
else:
karma.score = score
karma.scored_date = datetime.datetime.now()
karma.save()
def _module_get_pretty_score(score):
"""
Given a score between -1 and 1 (inclusive), returns the same score on a
scale between 1 and 10 (inclusive), as an integer.
"""
if score is None:
return DEFAULT_KARMA
return int(round((4.5 * score) + 5.5))
class UserFlag(meta.Model):
db_table = 'comments_user_flags'
fields = (
meta.ForeignKey(auth.User),
meta.ForeignKey(Comment),
meta.DateTimeField('flag_date', 'date flagged', auto_now_add=True),
)
unique_together = (('user_id', 'comment_id'),)
def __repr__(self):
return "Flag by %r" % self.get_user()
def _module_flag(comment, user):
"""
Flags the given comment by the given user. If the comment has already
been flagged by the user, or it was a comment posted by the user,
nothing happens.
"""
if int(comment.user_id) == int(user.id):
return # A user can't flag his own comment. Fail silently.
try:
f = get_object(user_id__exact=user.id, comment_id__exact=comment.id)
except UserFlagDoesNotExist:
from django.core.mail import mail_managers
f = UserFlag(None, user.id, comment.id, None)
message = 'This comment was flagged by %s:\n\n%s' % (user.username, comment.get_as_text())
mail_managers('Comment flagged', message, fail_silently=True)
f.save()
class ModeratorDeletion(meta.Model):
db_table = 'comments_moderator_deletions'
fields = (
meta.ForeignKey(auth.User, verbose_name='moderator'),
meta.ForeignKey(Comment),
meta.DateTimeField('deletion_date', 'date deleted', auto_now_add=True),
)
unique_together = (('user_id', 'comment_id'),)
def __repr__(self):
return "Moderator deletion by %r" % self.get_user()

107
django/models/core.py Normal file
View File

@ -0,0 +1,107 @@
from django.core import meta, validators
class Site(meta.Model):
db_table = 'sites'
fields = (
meta.CharField('domain', 'domain name', maxlength=100),
meta.CharField('name', 'display name', maxlength=50),
)
ordering = (('domain', 'ASC'),)
def __repr__(self):
return self.domain
def _module_get_current():
"Returns the current site, according to the SITE_ID constant."
from django.conf.settings import SITE_ID
return get_object(id__exact=SITE_ID)
class Package(meta.Model):
db_table = 'packages'
fields = (
meta.CharField('label', 'label', maxlength=20, primary_key=True),
meta.CharField('name', 'name', maxlength=30, unique=True),
)
ordering = (('name', 'ASC'),)
def __repr__(self):
return self.name
class ContentType(meta.Model):
db_table = 'content_types'
fields = (
meta.CharField('name', 'name', maxlength=100),
meta.ForeignKey(Package, name='package'),
meta.CharField('python_module_name', 'Python module name', maxlength=50),
)
ordering = (('package', 'ASC'), ('name', 'ASC'),)
unique_together = (('package', 'python_module_name'),)
def __repr__(self):
return "%s | %s" % (self.package, self.name)
def get_model_module(self):
"Returns the Python model module for accessing this type of content."
return __import__('django.models.%s.%s' % (self.package, self.python_module_name), '', '', [''])
def get_object_for_this_type(self, **kwargs):
"""
Returns an object of this type for the keyword arguments given.
Basically, this is a proxy around this object_type's get_object() model
method. The ObjectNotExist exception, if thrown, will not be caught,
so code that calls this method should catch it.
"""
return self.get_model_module().get_object(**kwargs)
class Redirect(meta.Model):
db_table = 'redirects'
fields = (
meta.ForeignKey(Site, radio_admin=meta.VERTICAL),
meta.CharField('old_path', 'redirect from', maxlength=200, db_index=True,
help_text="This should be an absolute path, excluding the domain name. Example: '/events/search/'."),
meta.CharField('new_path', 'redirect to', maxlength=200, blank=True,
help_text="This can be either an absolute path (as above) or a full URL starting with 'http://'."),
)
unique_together=(('site_id', 'old_path'),)
ordering = (('old_path', 'ASC'),)
admin = meta.Admin(
fields = (
(None, {'fields': ('site_id', 'old_path', 'new_path')}),
),
list_display = ('__repr__',),
list_filter = ('site_id',),
search_fields = ('old_path', 'new_path'),
)
def __repr__(self):
return "%s ---> %s" % (self.old_path, self.new_path)
class FlatFile(meta.Model):
db_table = 'flatfiles'
fields = (
meta.CharField('url', 'URL', maxlength=100, validator_list=[validators.isAlphaNumericURL],
help_text="Example: '/about/contact/'. Make sure to have leading and trailing slashes."),
meta.CharField('title', 'title', maxlength=200),
meta.TextField('content', 'content', help_text="Full HTML is allowed."),
meta.BooleanField('enable_comments', 'enable comments'),
meta.CharField('template_name', 'template name', maxlength=70, blank=True,
help_text="Example: 'flatfiles/contact_page'. If this isn't provided, the system will use 'flatfiles/default'."),
meta.BooleanField('registration_required', 'registration required',
help_text="If this is checked, only logged-in users will be able to view the page."),
meta.ManyToManyField(Site),
)
ordering = (('url', 'ASC'),)
admin = meta.Admin(
fields = (
(None, {'fields': ('url', 'title', 'content', 'sites')}),
('Advanced options', {'classes': 'collapse', 'fields': ('enable_comments', 'registration_required', 'template_name')}),
),
list_filter = ('sites',),
search_fields = ('url', 'title'),
)
def __repr__(self):
return "%s -- %s" % (self.url, self.title)
def get_absolute_url(self):
return self.url

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

View File

93
django/parts/admin/doc.py Normal file
View File

@ -0,0 +1,93 @@
"""
Misc. utility functions/classes for documentation generator
"""
import re
from email.Parser import HeaderParser
from email.Errors import HeaderParseError
import docutils.core
import docutils.nodes
import docutils.parsers.rst.roles
#
# reST roles
#
ROLES = {
# role name, base role url (in the admin)
'model' : '/doc/models/%s/',
'view' : '/doc/views/%s/',
'template' : '/doc/templates/%s/',
'filter' : '/doc/filters/#%s',
'tag' : '/doc/tags/#%s',
}
def trim_docstring(docstring):
"""
Uniformly trims leading/trailing whitespace from docstrings.
Based on http://www.python.org/peps/pep-0257.html#handling-docstring-indentation
"""
if not docstring or not docstring.strip():
return ''
# Convert tabs to spaces and split into lines
lines = docstring.expandtabs().splitlines()
indent = min([len(line) - len(line.lstrip()) for line in lines if line.lstrip()])
trimmed = [lines[0].lstrip()] + [line[indent:].rstrip() for line in lines[1:]]
return "\n".join(trimmed).strip()
def parse_docstring(docstring):
"""
Parse out the parts of a docstring. Returns (title, body, metadata).
"""
docstring = trim_docstring(docstring)
parts = re.split(r'\n{2,}', docstring)
title = parts[0]
if len(parts) == 1:
body = ''
metadata = {}
else:
parser = HeaderParser()
try:
metadata = parser.parsestr(parts[-1])
except HeaderParseError:
metadata = {}
body = "\n\n".join(parts[1:])
else:
metadata = dict(metadata.items())
if metadata:
body = "\n\n".join(parts[1:-1])
else:
body = "\n\n".join(parts[1:])
return title, body, metadata
def parse_rst(text, default_reference_context, thing_being_parsed=None):
"""
Convert the string from reST to an XHTML fragment.
"""
overrides = {
'input_encoding' : 'unicode',
'doctitle_xform' : True,
'inital_header_level' : 3,
}
if thing_being_parsed:
thing_being_parsed = "<%s>" % thing_being_parsed
parts = docutils.core.publish_parts(text, source_path=thing_being_parsed,
destination_path=None, writer_name='html',
settings_overrides={'default_reference_context' : default_reference_context})
return parts['fragment']
def create_reference_role(rolename, urlbase):
def _role(name, rawtext, text, lineno, inliner, options={}, content=[]):
node = docutils.nodes.reference(rawtext, text, refuri=(urlbase % text), **options)
return [node], []
docutils.parsers.rst.roles.register_canonical_role(rolename, _role)
def default_reference_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
context = inliner.document.settings.default_reference_context
node = docutils.nodes.reference(rawtext, text, refuri=(ROLES[context] % text), **options)
return [node], []
docutils.parsers.rst.roles.register_canonical_role('cmsreference', default_reference_role)
docutils.parsers.rst.roles.DEFAULT_INTERPRETED_ROLE = 'cmsreference'
for (name, urlbase) in ROLES.items():
create_reference_role(name, urlbase)

View File

View File

@ -0,0 +1,48 @@
"""
Anonymous users
"""
class AnonymousUser:
def __init__(self):
pass
def __repr__(self):
return 'AnonymousUser'
def save(self):
raise NotImplementedError
def delete(self):
raise NotImplementedError
def set_password(self, raw_password):
raise NotImplementedError
def check_password(self, raw_password):
raise NotImplementedError
def get_groups(self):
return []
def set_groups(self, group_id_list):
raise NotImplementedError
def get_permissions(self):
return []
def set_permissions(self, permission_id_list):
raise NotImplementedError
def has_perm(self, perm):
return False
def get_and_delete_messages(self):
return []
def add_session(self, session_md5, start_time):
"Creates Session for this User, saves it, and returns the new object"
raise NotImplementedError
def is_anonymous(self):
return True

View File

@ -0,0 +1,46 @@
from django.models.auth import sessions, users
from django.core import formfields, validators
class AuthenticationForm(formfields.Manipulator):
"""
Base class for authenticating users. Extend this to get a form that accepts
username/password logins.
"""
def __init__(self, request=None):
"""
If request is passed in, the manipulator will validate that cookies are
enabled. Note that the request (a HttpRequest object) must have set a
cookie with the key TEST_COOKIE_NAME and value TEST_COOKIE_VALUE before
running this validator.
"""
self.request = request
self.fields = [
formfields.TextField(field_name="username", length=15, maxlength=30, is_required=True,
validator_list=[self.isValidUser, self.hasCookiesEnabled]),
formfields.PasswordField(field_name="password", length=15, maxlength=30, is_required=True,
validator_list=[self.isValidPasswordForUser]),
]
self.user_cache = None
def hasCookiesEnabled(self, field_data, all_data):
if self.request and (not self.request.COOKIES.has_key(sessions.TEST_COOKIE_NAME) or self.request.COOKIES[sessions.TEST_COOKIE_NAME] != sessions.TEST_COOKIE_VALUE):
raise validators.ValidationError, "Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in."
def isValidUser(self, field_data, all_data):
try:
self.user_cache = users.get_object(username__exact=field_data)
except users.UserDoesNotExist:
raise validators.ValidationError, "Please enter a correct username and password. Note that both fields are case-sensitive."
def isValidPasswordForUser(self, field_data, all_data):
if self.user_cache is not None and not self.user_cache.check_password(field_data):
self.user_cache = None
raise validators.ValidationError, "Please enter a correct username and password. Note that both fields are case-sensitive."
def get_user_id(self):
if self.user_cache:
return self.user_cache.id
return None
def get_user(self):
return self.user_cache

View File

View File

@ -0,0 +1,6 @@
import re
def get_thumbnail_url(photo_url, width):
bits = photo_url.split('/')
bits[-1] = re.sub(r'(?i)\.(gif|jpg)$', '_t%s.\\1' % width, bits[-1])
return '/'.join(bits)

View File

@ -0,0 +1,7 @@
from django.conf.settings import INSTALLED_APPS
for a in INSTALLED_APPS:
try:
__path__.extend(__import__(a + '.templatetags', '', '', ['']).__path__)
except ImportError:
pass

View File

@ -0,0 +1,331 @@
"Custom template tags for user comments"
from django.core import template
from django.core.exceptions import ObjectDoesNotExist
from django.models.comments import comments, freecomments
from django.models.core import contenttypes
import re
COMMENT_FORM = '''
{% if display_form %}
<form {% if photos_optional or photos_required %}enctype="multipart/form-data" {% endif %}action="/comments/post/" method="post">
{% if user.is_anonymous %}
<p>Username: <input type="text" name="username" id="id_username" /><br />Password: <input type="password" name="password" id="id_password" /> (<a href="/accounts/password_reset/">Forgotten your password?</a>)</p>
{% else %}
<p>Username: <strong>{{ user.username }}</strong> (<a href="/accounts/logout/">Log out</a>)</p>
{% endif %}
{% if ratings_optional or ratings_required %}
<p>Ratings ({% if ratings_required %}Required{% else %}Optional{% endif %}):</p>
<table>
<tr><th>&nbsp;</th>{% for value in rating_range %}<th>{{ value }}</th>{% endfor %}</tr>
{% for rating in rating_choices %}
<tr><th>{{ rating }}</th>{% for value in rating_range %}<th><input type="radio" name="rating{{ forloop.parentloop.counter }}" value="{{ value }}" /></th>{% endfor %}</tr>
{% endfor %}
</table>
<input type="hidden" name="rating_options" value="{{ rating_options }}" />
{% endif %}
{% if photos_optional or photos_required %}
<p>Post a photo ({% if photos_required %}Required{% else %}Optional{% endif %}): <input type="file" name="photo" /></p>
<input type="hidden" name="photo_options" value="{{ photo_options }}" />
{% endif %}
<p>Comment:<br /><textarea name="comment" id="id_comment" rows="10" cols="60"></textarea></p>
<input type="hidden" name="options" value="{{ options }}" />
<input type="hidden" name="target" value="{{ target }}" />
<input type="hidden" name="gonzo" value="{{ hash }}" />
<p><input type="submit" name="preview" value="Preview comment" /></p>
</form>
{% endif %}
'''
FREE_COMMENT_FORM = '''
{% if display_form %}
<form enctype="multipart/form-data" action="/comments/postfree/" method="post">
<p>Your name: <input type="text" id="id_person_name" name="person_name" /></p>
<p>Comment:<br /><textarea name="comment" id="id_comment" rows="10" cols="60"></textarea></p>
<input type="hidden" name="options" value="{{ options }}" />
<input type="hidden" name="target" value="{{ target }}" />
<input type="hidden" name="gonzo" value="{{ hash }}" />
<p><input type="submit" name="preview" value="Preview comment" /></p>
</form>
{% endif %}
'''
class CommentFormNode(template.Node):
def __init__(self, content_type, obj_id_lookup_var, obj_id, free,
photos_optional=False, photos_required=False, photo_options='',
ratings_optional=False, ratings_required=False, rating_options='',
is_public=True):
self.content_type = content_type
self.obj_id_lookup_var, self.obj_id, self.free = obj_id_lookup_var, obj_id, free
self.photos_optional, self.photos_required = photos_optional, photos_required
self.ratings_optional, self.ratings_required = ratings_optional, ratings_required
self.photo_options, self.rating_options = photo_options, rating_options
self.is_public = is_public
def render(self, context):
from django.utils.text import normalize_newlines
import base64
context.push()
if self.obj_id_lookup_var is not None:
try:
self.obj_id = template.resolve_variable(self.obj_id_lookup_var, context)
except template.VariableDoesNotExist:
return ''
# Validate that this object ID is valid for this content-type.
# We only have to do this validation if obj_id_lookup_var is provided,
# because do_comment_form() validates hard-coded object IDs.
try:
self.content_type.get_object_for_this_type(id__exact=self.obj_id)
except ObjectDoesNotExist:
context['display_form'] = False
else:
context['display_form'] = True
context['target'] = '%s:%s' % (self.content_type.id, self.obj_id)
options = []
for var, abbr in (('photos_required', comments.PHOTOS_REQUIRED),
('photos_optional', comments.PHOTOS_OPTIONAL),
('ratings_required', comments.RATINGS_REQUIRED),
('ratings_optional', comments.RATINGS_OPTIONAL),
('is_public', comments.IS_PUBLIC)):
context[var] = getattr(self, var)
if getattr(self, var):
options.append(abbr)
context['options'] = ','.join(options)
if self.free:
context['hash'] = comments.get_security_hash(context['options'], '', '', context['target'])
default_form = FREE_COMMENT_FORM
else:
context['photo_options'] = self.photo_options
context['rating_options'] = normalize_newlines(base64.encodestring(self.rating_options).strip())
if self.rating_options:
context['rating_range'], context['rating_choices'] = comments.get_rating_options(self.rating_options)
context['hash'] = comments.get_security_hash(context['options'], context['photo_options'], context['rating_options'], context['target'])
default_form = COMMENT_FORM
output = template.Template(default_form).render(context)
context.pop()
return output
class CommentCountNode(template.Node):
def __init__(self, package, module, context_var_name, obj_id, var_name, free):
self.package, self.module = package, module
self.context_var_name, self.obj_id = context_var_name, obj_id
self.var_name, self.free = var_name, free
def render(self, context):
from django.conf.settings import SITE_ID
get_count_function = self.free and freecomments.get_count or comments.get_count
if self.context_var_name is not None:
self.obj_id = template.resolve_variable(self.context_var_name, context)
comment_count = get_count_function(object_id__exact=self.obj_id,
content_type__package__label__exact=self.package,
content_type__python_module_name__exact=self.module, site_id__exact=SITE_ID)
context[self.var_name] = comment_count
return ''
class CommentListNode(template.Node):
def __init__(self, package, module, context_var_name, obj_id, var_name, free):
self.package, self.module = package, module
self.context_var_name, self.obj_id = context_var_name, obj_id
self.var_name, self.free = var_name, free
def render(self, context):
from django.conf.settings import COMMENTS_BANNED_USERS_GROUP, SITE_ID
get_list_function = self.free and freecomments.get_list or comments.get_list_with_karma
if self.context_var_name is not None:
try:
self.obj_id = template.resolve_variable(self.context_var_name, context)
except template.VariableDoesNotExist:
return ''
kwargs = {
'object_id__exact': self.obj_id,
'content_type__package__label__exact': self.package,
'content_type__python_module_name__exact': self.module,
'site_id__exact': SITE_ID,
'select_related': True,
'order_by': (('submit_date', 'ASC'),),
}
if not self.free and COMMENTS_BANNED_USERS_GROUP:
kwargs['select'] = {'is_hidden': 'user_id IN (SELECT user_id FROM auth_users_groups WHERE group_id = %s)' % COMMENTS_BANNED_USERS_GROUP}
comment_list = get_list_function(**kwargs)
if not self.free:
if context.has_key('user') and not context['user'].is_anonymous():
user_id = context['user'].id
context['user_can_moderate_comments'] = comments.user_is_moderator(context['user'])
else:
user_id = None
context['user_can_moderate_comments'] = False
# Only display comments by banned users to those users themselves.
if COMMENTS_BANNED_USERS_GROUP:
comment_list = [c for c in comment_list if not c.is_hidden or (user_id == c.user_id)]
context[self.var_name] = comment_list
return ''
class DoCommentForm:
"""
Displays a comment form for the given params. Syntax:
{% comment_form for [pkg].[py_module_name] [context_var_containing_obj_id] with [list of options] %}
Example usage:
{% comment_form for lcom.eventtimes event.id with is_public yes photos_optional thumbs,200,400 ratings_optional scale:1-5|first_option|second_option %}
[context_var_containing_obj_id] can be a hard-coded integer or a variable containing the ID.
"""
def __init__(self, free, tag_name):
self.free, self.tag_name = free, tag_name
def __call__(self, parser, token):
tokens = token.contents.split()
if len(tokens) < 4:
raise template.TemplateSyntaxError, "'%s' tag requires at least 3 arguments" % self.tag_name
if tokens[1] != 'for':
raise template.TemplateSyntaxError, "Second argument in '%s' tag must be 'for'" % self.tag_name
try:
package, module = tokens[2].split('.')
except ValueError: # unpack list of wrong size
raise template.TemplateSyntaxError, "Third argument in '%s' tag must be in the format 'package.module'" % self.tag_name
try:
content_type = contenttypes.get_object(package__label__exact=package, python_module_name__exact=module)
except contenttypes.ContentTypeDoesNotExist:
raise template.TemplateSyntaxError, "'%s' tag has invalid content-type '%s.%s'" % (self.tag_name, package, module)
obj_id_lookup_var, obj_id = None, None
if tokens[3].isdigit():
obj_id = tokens[3]
try: # ensure the object ID is valid
content_type.get_object_for_this_type(id__exact=obj_id)
except ObjectDoesNotExist:
raise template.TemplateSyntaxError, "'%s' tag refers to %s object with ID %s, which doesn't exist" % (self.tag_name, content_type.name, obj_id)
else:
obj_id_lookup_var = tokens[3]
kwargs = {}
if len(tokens) > 4:
if tokens[4] != 'with':
raise template.TemplateSyntaxError, "Fourth argument in '%s' tag must be 'with'" % self.tag_name
for option, args in zip(tokens[5::2], tokens[6::2]):
if option in ('photos_optional', 'photos_required') and not self.free:
# VALIDATION ##############################################
option_list = args.split(',')
if len(option_list) % 3 != 0:
raise template.TemplateSyntaxError, "Incorrect number of comma-separated arguments to '%s' tag" % self.tag_name
for opt in option_list[::3]:
if not opt.isalnum():
raise template.TemplateSyntaxError, "Invalid photo directory name in '%s' tag: '%s'" % (self.tag_name, opt)
for opt in option_list[1::3] + option_list[2::3]:
if not opt.isdigit() or not (comments.MIN_PHOTO_DIMENSION <= int(opt) <= comments.MAX_PHOTO_DIMENSION):
raise template.TemplateSyntaxError, "Invalid photo dimension in '%s' tag: '%s'. Only values between %s and %s are allowed." % (self.tag_name, opt, comments.MIN_PHOTO_DIMENSION, comments.MAX_PHOTO_DIMENSION)
# VALIDATION ENDS #########################################
kwargs[option] = True
kwargs['photo_options'] = args
elif option in ('ratings_optional', 'ratings_required') and not self.free:
# VALIDATION ##############################################
if 2 < len(args.split('|')) > 9:
raise template.TemplateSyntaxError, "Incorrect number of '%s' options in '%s' tag. Use between 2 and 8." % (option, self.tag_name)
if re.match('^scale:\d+\-\d+\:$', args.split('|')[0]):
raise template.TemplateSyntaxError, "Invalid 'scale' in '%s' tag's '%s' options" % (self.tag_name, option)
# VALIDATION ENDS #########################################
kwargs[option] = True
kwargs['rating_options'] = args
elif option in ('is_public'):
kwargs[option] = (args == 'true')
else:
raise template.TemplateSyntaxError, "'%s' tag got invalid parameter '%s'" % (self.tag_name, option)
return CommentFormNode(content_type, obj_id_lookup_var, obj_id, self.free, **kwargs)
class DoCommentCount:
"""
Gets comment count for the given params and populates the template context
with a variable containing that value, whose name is defined by the 'as'
clause. Syntax:
{% get_comment_count for [pkg].[py_module_name] [context_var_containing_obj_id] as [varname] %}
Example usage:
{% get_comment_count for lcom.eventtimes event.id as comment_count %}
Note: [context_var_containing_obj_id] can also be a hard-coded integer, like this:
{% get_comment_count for lcom.eventtimes 23 as comment_count %}
"""
def __init__(self, free, tag_name):
self.free, self.tag_name = free, tag_name
def __call__(self, parser, token):
tokens = token.contents.split()
# Now tokens is a list like this:
# ['get_comment_list', 'for', 'lcom.eventtimes', 'event.id', 'as', 'comment_list']
if len(tokens) != 6:
raise template.TemplateSyntaxError, "%s block tag requires 5 arguments" % self.tag_name
if tokens[1] != 'for':
raise template.TemplateSyntaxError, "Second argument in '%s' tag must be 'for'" % self.tag_name
try:
package, module = tokens[2].split('.')
except ValueError: # unpack list of wrong size
raise template.TemplateSyntaxError, "Third argument in '%s' tag must be in the format 'package.module'" % self.tag_name
try:
content_type = contenttypes.get_object(package__label__exact=package, python_module_name__exact=module)
except contenttypes.ContentTypeDoesNotExist:
raise template.TemplateSyntaxError, "'%s' tag has invalid content-type '%s.%s'" % (self.tag_name, package, module)
var_name, obj_id = None, None
if tokens[3].isdigit():
obj_id = tokens[3]
try: # ensure the object ID is valid
content_type.get_object_for_this_type(id__exact=obj_id)
except ObjectDoesNotExist:
raise template.TemplateSyntaxError, "'%s' tag refers to %s object with ID %s, which doesn't exist" % (self.tag_name, content_type.name, obj_id)
else:
var_name = tokens[3]
if tokens[4] != 'as':
raise template.TemplateSyntaxError, "Fourth argument in '%s' must be 'as'" % self.tag_name
return CommentCountNode(package, module, var_name, obj_id, tokens[5], self.free)
class DoGetCommentList:
"""
Gets comments for the given params and populates the template context with
a special comment_package variable, whose name is defined by the 'as'
clause. Syntax:
{% get_comment_list for [pkg].[py_module_name] [context_var_containing_obj_id] as [varname] %}
Example usage:
{% get_comment_list for lcom.eventtimes event.id as comment_list %}
Note: [context_var_containing_obj_id] can also be a hard-coded integer, like this:
{% get_comment_list for lcom.eventtimes 23 as comment_list %}
"""
def __init__(self, free, tag_name):
self.free, self.tag_name = free, tag_name
def __call__(self, parser, token):
tokens = token.contents.split()
# Now tokens is a list like this:
# ['get_comment_list', 'for', 'lcom.eventtimes', 'event.id', 'as', 'comment_list']
if len(tokens) != 6:
raise template.TemplateSyntaxError, "%s block tag requires 5 arguments" % self.tag_name
if tokens[1] != 'for':
raise template.TemplateSyntaxError, "Second argument in '%s' tag must be 'for'" % self.tag_name
try:
package, module = tokens[2].split('.')
except ValueError: # unpack list of wrong size
raise template.TemplateSyntaxError, "Third argument in '%s' tag must be in the format 'package.module'" % self.tag_name
try:
content_type = contenttypes.get_object(package__label__exact=package, python_module_name__exact=module)
except contenttypes.ContentTypeDoesNotExist:
raise template.TemplateSyntaxError, "'%s' tag has invalid content-type '%s.%s'" % (self.tag_name, package, module)
var_name, obj_id = None, None
if tokens[3].isdigit():
obj_id = tokens[3]
try: # ensure the object ID is valid
content_type.get_object_for_this_type(id__exact=obj_id)
except ObjectDoesNotExist:
raise template.TemplateSyntaxError, "'%s' tag refers to %s object with ID %s, which doesn't exist" % (self.tag_name, content_type.name, obj_id)
else:
var_name = tokens[3]
if tokens[4] != 'as':
raise template.TemplateSyntaxError, "Fourth argument in '%s' must be 'as'" % self.tag_name
return CommentListNode(package, module, var_name, obj_id, tokens[5], self.free)
# registration comments
template.register_tag('get_comment_list', DoGetCommentList(free=False, tag_name='get_comment_list'))
template.register_tag('comment_form', DoCommentForm(free=False, tag_name='comment_form'))
template.register_tag('get_comment_count', DoCommentCount(free=False, tag_name='get_comment_count'))
# free comments
template.register_tag('get_free_comment_list', DoGetCommentList(free=True, tag_name='get_free_comment_list'))
template.register_tag('free_comment_form', DoCommentForm(free=True, tag_name='free_comment_form'))
template.register_tag('get_free_comment_count', DoCommentCount(free=True, tag_name='get_free_comment_count'))

View File

@ -0,0 +1,45 @@
from django.models.auth import log
from django.core import template
class AdminLogNode(template.Node):
def __init__(self, limit, varname, user):
self.limit, self.varname, self.user = limit, varname, user
def __repr__(self):
return "<GetAdminLog Node>"
def render(self, context):
if self.user is not None and not self.user.isdigit():
self.user = context[self.user].id
context[self.varname] = log.get_list(user_id__exact=self.user, limit=self.limit, select_related=True)
return ''
class DoGetAdminLog:
"""
Populates a template variable with the admin log for the given criteria.
Usage:
{% get_admin_log [limit] as [varname] for_user [context_var_containing_user_obj] %}
Examples:
{% get_admin_log 10 as admin_log for_user 23 %}
{% get_admin_log 10 as admin_log for_user user %}
{% get_admin_log 10 as admin_log %}
Note that [context_var_containing_user_obj] can be a hard-coded integer (user ID) or the
name of a template context variable containing the user object whose ID you want.
"""
def __init__(self, tag_name):
self.tag_name = tag_name
def __call__(self, parser, token):
tokens = token.contents.split()
if len(tokens) < 4:
raise template.TemplateSyntaxError, "'%s' statements require two arguments" % self.tag_name
if not tokens[1].isdigit():
raise template.TemplateSyntaxError, "First argument in '%s' must be an integer" % self.tag_name
if tokens[2] != 'as':
raise template.TemplateSyntaxError, "Second argument in '%s' must be 'as'" % self.tag_name
if len(tokens) > 4:
if tokens[4] != 'for_user':
raise template.TemplateSyntaxError, "Fourth argument in '%s' must be 'for_user'" % self.tag_name
return AdminLogNode(limit=tokens[1], varname=tokens[3], user=(len(tokens) > 5 and tokens[5] or None))
template.register_tag('get_admin_log', DoGetAdminLog('get_admin_log'))

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

119
django/tests/cache_tests.py Normal file
View File

@ -0,0 +1,119 @@
"""
Unit tests for django.core.cache
If you don't have memcached running on localhost port 11211, the memcached tests
will fail.
"""
from django.core import cache
import unittest
import time
# functions/classes for complex data type tests
def f():
return 42
class C:
def m(n):
return 24
class CacheBackendsTest(unittest.TestCase):
def testBackends(self):
sc = cache.get_cache('simple://')
mc = cache.get_cache('memcached://127.0.0.1:11211/')
self.failUnless(isinstance(sc, cache._SimpleCache))
self.failUnless(isinstance(mc, cache._MemcachedCache))
def testInvalidBackends(self):
self.assertRaises(cache.InvalidCacheBackendError, cache.get_cache, 'nothing://foo/')
self.assertRaises(cache.InvalidCacheBackendError, cache.get_cache, 'not a uri')
def testDefaultTimeouts(self):
sc = cache.get_cache('simple:///?timeout=15')
mc = cache.get_cache('memcached://127.0.0.1:11211/?timeout=15')
self.assertEquals(sc.default_timeout, 15)
self.assertEquals(sc.default_timeout, 15)
class SimpleCacheTest(unittest.TestCase):
def setUp(self):
self.cache = cache.get_cache('simple://')
def testGetSet(self):
self.cache.set('key', 'value')
self.assertEqual(self.cache.get('key'), 'value')
def testNonExistantKeys(self):
self.assertEqual(self.cache.get('does not exist'), None)
self.assertEqual(self.cache.get('does not exist', 'bang!'), 'bang!')
def testGetMany(self):
self.cache.set('a', 'a')
self.cache.set('b', 'b')
self.cache.set('c', 'c')
self.cache.set('d', 'd')
self.assertEqual(self.cache.get_many(['a', 'c', 'd']), {'a' : 'a', 'c' : 'c', 'd' : 'd'})
self.assertEqual(self.cache.get_many(['a', 'b', 'e']), {'a' : 'a', 'b' : 'b'})
def testDelete(self):
self.cache.set('key1', 'spam')
self.cache.set('key2', 'eggs')
self.assertEqual(self.cache.get('key1'), 'spam')
self.cache.delete('key1')
self.assertEqual(self.cache.get('key1'), None)
self.assertEqual(self.cache.get('key2'), 'eggs')
def testHasKey(self):
self.cache.set('hello', 'goodbye')
self.assertEqual(self.cache.has_key('hello'), True)
self.assertEqual(self.cache.has_key('goodbye'), False)
def testDataTypes(self):
items = {
'string' : 'this is a string',
'int' : 42,
'list' : [1, 2, 3, 4],
'tuple' : (1, 2, 3, 4),
'dict' : {'A': 1, 'B' : 2},
'function' : f,
'class' : C,
}
for (key, value) in items.items():
self.cache.set(key, value)
self.assertEqual(self.cache.get(key), value)
def testExpiration(self):
self.cache.set('expire', 'very quickly', 1)
time.sleep(2)
self.assertEqual(self.cache.get('expire'), None)
def testCull(self):
c = cache.get_cache('simple://?max_entries=9&cull_frequency=3')
for i in range(10):
c.set('culltest%i' % i, i)
n = 0
for i in range(10):
if c.get('culltest%i' % i):
n += 1
self.assertEqual(n, 6)
def testCullAll(self):
c = cache.get_cache('simple://?max_entries=9&cull_frequency=0')
for i in range(10):
c.set('cullalltest%i' % i, i)
for i in range(10):
self.assertEqual(self.cache.get('cullalltest%i' % i), None)
class MemcachedCacheTest(SimpleCacheTest):
def setUp(self):
self.cache = cache.get_cache('memcached://127.0.0.1:11211/')
testCull = testCullAll = lambda s: None
def tests():
s = unittest.TestLoader().loadTestsFromName(__name__)
unittest.TextTestRunner(verbosity=0).run(s)
if __name__ == "__main__":
tests()

View File

@ -0,0 +1,102 @@
from django.core import template, template_loader
# SYNTAX --
# 'template_name': ('template contents', 'context dict', 'expected string output' or Exception class)
TEMPLATE_TESTS = {
# Standard template with no inheritance
'test01': ("1{% block first %}_{% endblock %}3{% block second %}_{% endblock %}", {}, '1_3_'),
# Standard two-level inheritance
'test02': ("{% extends 'test01' %}{% block first %}2{% endblock %}{% block second %}4{% endblock %}", {}, '1234'),
# Three-level with no redefinitions on third level
'test03': ("{% extends 'test02' %}", {}, '1234'),
# Two-level with no redefinitions on second level
'test04': ("{% extends 'test01' %}", {}, '1_3_'),
# Two-level with double quotes instead of single quotes
'test05': ('{% extends "test02" %}', {}, '1234'),
# Three-level with variable parent-template name
'test06': ("{% extends foo %}", {'foo': 'test02'}, '1234'),
# Two-level with one block defined, one block not defined
'test07': ("{% extends 'test01' %}{% block second %}5{% endblock %}", {}, '1_35'),
# Three-level with one block defined on this level, two blocks defined next level
'test08': ("{% extends 'test02' %}{% block second %}5{% endblock %}", {}, '1235'),
# Three-level with second and third levels blank
'test09': ("{% extends 'test04' %}", {}, '1_3_'),
# Three-level with space NOT in a block -- should be ignored
'test10': ("{% extends 'test04' %} ", {}, '1_3_'),
# Three-level with both blocks defined on this level, but none on second level
'test11': ("{% extends 'test04' %}{% block first %}2{% endblock %}{% block second %}4{% endblock %}", {}, '1234'),
# Three-level with this level providing one and second level providing the other
'test12': ("{% extends 'test07' %}{% block first %}2{% endblock %}", {}, '1235'),
# Three-level with this level overriding second level
'test13': ("{% extends 'test02' %}{% block first %}a{% endblock %}{% block second %}b{% endblock %}", {}, '1a3b'),
# A block defined only in a child template shouldn't be displayed
'test14': ("{% extends 'test01' %}{% block newblock %}NO DISPLAY{% endblock %}", {}, '1_3_'),
# A block within another block
'test15': ("{% extends 'test01' %}{% block first %}2{% block inner %}inner{% endblock %}{% endblock %}", {}, '12inner3_'),
# A block within another block (level 2)
'test16': ("{% extends 'test15' %}{% block inner %}out{% endblock %}", {}, '12out3_'),
# {% load %} tag (parent -- setup for test-exception04)
'test17': ("{% load polls.polls %}{% block first %}1234{% endblock %}", {}, '1234'),
# {% load %} tag (standard usage, without inheritance)
'test18': ("{% load polls.polls %}{% voteratio choice poll 400 %}5678", {}, '05678'),
# {% load %} tag (within a child template)
'test19': ("{% extends 'test01' %}{% block first %}{% load polls.polls %}{% voteratio choice poll 400 %}5678{% endblock %}", {}, '1056783_'),
# Raise exception for invalid template name
'test-exception01': ("{% extends 'nonexistent' %}", {}, template.TemplateSyntaxError),
# Raise exception for invalid template name (in variable)
'test-exception02': ("{% extends nonexistent %}", {}, template.TemplateSyntaxError),
# Raise exception for extra {% extends %} tags
'test-exception03': ("{% extends 'test01' %}{% block first %}2{% endblock %}{% extends 'test16' %}", {}, template.TemplateSyntaxError),
# Raise exception for custom tags used in child with {% load %} tag in parent, not in child
'test-exception04': ("{% extends 'test17' %}{% block first %}{% votegraph choice poll 400 %}5678{% endblock %}", {}, template.TemplateSyntaxError),
}
# This replaces the standard template_loader.
def test_template_loader(template_name):
try:
return TEMPLATE_TESTS[template_name][0]
except KeyError:
raise template.TemplateDoesNotExist, template_name
template_loader.load_template_source = test_template_loader
def run_tests():
tests = TEMPLATE_TESTS.items()
tests.sort()
for name, vals in tests:
try:
output = template_loader.get_template(name).render(template.Context(vals[1]))
except Exception, e:
if e.__class__ == vals[2]:
print "%s -- Passed" % name
else:
print "%s -- FAILED. Got %s, exception: %s" % (name, e.__class__, e)
continue
if output == vals[2]:
print "%s -- Passed" % name
else:
print "%s -- FAILED. Expected %r, got %r" % (name, vals[2], output)
if __name__ == "__main__":
run_tests()

View File

@ -0,0 +1,707 @@
"""
Unit tests for template.py
These tests assume the following template syntax:
FILTER_SEPARATOR = '|'
VARIABLE_ATTRIBUTE_SEPARATOR = '.'
BLOCK_TAG_START = '{%'
BLOCK_TAG_END = '%}'
VARIABLE_TAG_START = '{{'
VARIABLE_TAG_END = '}}'
"""
from django.core import template
import unittest
class RandomSyntaxErrorsCheck(unittest.TestCase):
def testTagsOnOneLine(self):
"Tags straddling more than one line are not interpreted"
c = template.Context({'key':'value'})
t = template.Template('<h1>{{key\n}}</h1>')
expected = '<h1>{{key\n}}</h1>'
self.assertEqual(expected, t.render(c))
class PlainTextCheck(unittest.TestCase):
def testPlainText(self):
"Plain text should go through the template parser untouched"
c = template.Context()
t = template.Template('<h1>Success</h1>')
expected = '<h1>Success</h1>'
self.assertEqual(expected, t.render(c))
class VariableSubstitutionCheck(unittest.TestCase):
def testSingleTag(self):
"Variables should be replaced with their value in the current context"
c = template.Context({'headline':'Success'})
t = template.Template('<h1>{{headline}}</h1>')
expected = '<h1>Success</h1>'
self.assertEqual(expected, t.render(c))
def testDoubleTag(self):
"More than one replacement variable is allowed in a template"
c = template.Context({'firsttag':'it', 'secondtag':'worked'})
t = template.Template('<h1>{{firsttag}} {{secondtag}}</h1>')
expected = '<h1>it worked</h1>'
self.assertEqual(expected, t.render(c))
def testNonexistentVariable(self):
"Fail silently when a variable is not found in the current context"
c = template.Context({})
t = template.Template('<h1>{{unknownvar}}</h1>')
expected = '<h1></h1>'
self.assertEqual(expected, t.render(c))
def testVariablesWithSpaces(self):
"A replacement-variable tag may not contain more than one word"
t = '<h1>{{multi word tag}}</h1>'
self.assertRaises(template.TemplateSyntaxError, template.Template, t)
def testEmptyTag(self):
"Raise TemplateSyntaxError for empty variable tags"
t = '{{ }}'
self.assertRaises(template.TemplateSyntaxError, template.Template, t)
t = '{{ }}'
self.assertRaises(template.TemplateSyntaxError, template.Template, t)
def testIntegerContextValue(self):
"Accept integers as variable values"
c = template.Context({'var':55})
t = template.Template('<h1>{{var}}</h1>')
expected = '<h1>55</h1>'
self.assertEqual(expected, t.render(c))
def textIntegerContextKey(self):
"Accept integers as variable keys"
c = template.Context({55:'var'})
t = template.Template('<h1>{{55}}</h1>')
expected = '<h1>var</h1>'
self.assertEqual(expected, t.render(c))
def testVariableAttributeAccess1(self):
"Attribute syntax allows a template to call an object's attribute"
class AClass: pass
obj = AClass()
obj.att = 'attvalue'
c = template.Context({'var':obj})
t = template.Template('<h1>{{ var.att }}</h1>')
expected = '<h1>attvalue</h1>'
self.assertEqual(expected, t.render(c))
def testVariableAttributeAccess2(self):
"Attribute syntax allows a template to call an object's attribute (with getattr defined)"
class AClass:
def __getattr__(self, attr):
return "attvalue"
obj = AClass()
c = template.Context({'var':obj})
t = template.Template('<h1>{{ var.att }}</h1>')
expected = '<h1>attvalue</h1>'
self.assertEqual(expected, t.render(c))
def testVariableAttributeAccessMultiple(self):
"Multiple levels of attribute access are allowed"
class AClass: pass
obj = AClass()
obj.article = AClass()
obj.article.section = AClass()
obj.article.section.title = 'Headline'
c = template.Context({'obj':obj})
t = template.Template('<h1>{{ obj.article.section.title }}</h1>')
expected = '<h1>Headline</h1>'
self.assertEqual(expected, t.render(c))
def testNonexistentVariableAttributeObject(self):
"Fail silently when a variable's attribute isn't found"
class AClass: pass
obj = AClass()
obj.att = 'attvalue'
c = template.Context({'var':obj})
t = template.Template('<h1>{{ var.nonexistentatt }}</h1>')
expected = '<h1></h1>'
self.assertEqual(expected, t.render(c))
def testIllegalUnderscoreInVariableName(self):
"Raise TemplateSyntaxError when trying to access a variable beginning with an underscore"
t = '<h1>{{ var._att }}</h1>'
self.assertRaises(template.TemplateSyntaxError, template.Template, t)
t = '<h1>{{ _att }}</h1>'
self.assertRaises(template.TemplateSyntaxError, template.Template, t)
def testIllegalCharacterInVariableName(self):
"Raise TemplateSyntaxError when trying to access a variable containing an illegal character"
t = '<h1>{{ (blah }}</h1>'
self.assertRaises(template.TemplateSyntaxError, template.Template, t)
t = '<h1>{{ (blah.test) }}</h1>'
self.assertRaises(template.TemplateSyntaxError, template.Template, t)
t = '<h1>{{ bl(ah.test) }}</h1>'
self.assertRaises(template.TemplateSyntaxError, template.Template, t)
def testVariableAttributeDictionary(self):
"Attribute syntax allows a template to call a dictionary key's value"
obj = {'att':'attvalue'}
c = template.Context({'var':obj})
t = template.Template('<h1>{{ var.att }}</h1>')
expected = '<h1>attvalue</h1>'
self.assertEqual(expected, t.render(c))
def testNonexistentVariableAttributeDictionary(self):
"Fail silently when a variable's dictionary key isn't found"
obj = {'att':'attvalue'}
c = template.Context({'var':obj})
t = template.Template('<h1>{{ var.nonexistentatt }}</h1>')
expected = '<h1></h1>'
self.assertEqual(expected, t.render(c))
def testVariableAttributeCallable(self):
"Attribute syntax allows a template to call a simple method"
class AClass:
def hello(self): return 'hi'
obj = AClass()
c = template.Context({'var':obj})
t = template.Template('<h1>{{ var.hello }}</h1>')
expected = '<h1>hi</h1>'
self.assertEqual(expected, t.render(c))
def testVariableAttributeCallableWrongArguments(self):
"Fail silently when accessing a non-simple method"
class AClass:
def hello(self, name): return 'hi, %s' % name
obj = AClass()
c = template.Context({'var':obj})
t = template.Template('<h1>{{ var.hello }}</h1>')
expected = '<h1></h1>'
self.assertEqual(expected, t.render(c))
class VariableFiltersCheck(unittest.TestCase):
def setUp(self):
self.c = template.Context({'var':'Hello There Programmer'})
def tearDown(self):
self.c = None
def testUpper(self):
"The 'upper' filter converts a string into all uppercase"
t = template.Template('<h1>{{ var|upper }}</h1>')
expected = '<h1>HELLO THERE PROGRAMMER</h1>'
self.assertEqual(expected, t.render(self.c))
def testLower(self):
"The 'lower' filter converts a string into all lowercase"
t = template.Template('<h1>{{ var|lower }}</h1>')
expected = '<h1>hello there programmer</h1>'
self.assertEqual(expected, t.render(self.c))
def testUpperThenLower(self):
"Filters may be applied in succession (upper|lower)"
t = template.Template('<h1>{{ var|upper|lower }}</h1>')
expected = '<h1>hello there programmer</h1>'
self.assertEqual(expected, t.render(self.c))
def testLowerThenUpper(self):
"Filters may be applied in succession (lower|upper)"
t = template.Template('<h1>{{ var|lower|upper }}</h1>')
expected = '<h1>HELLO THERE PROGRAMMER</h1>'
self.assertEqual(expected, t.render(self.c))
def testSpaceBetweenVariableAndFilterPipe(self):
"Raise TemplateSyntaxError for space between a variable and filter pipe"
t = '<h1>{{ var |lower }}</h1>'
self.assertRaises(template.TemplateSyntaxError, template.Template, t)
def testSpaceBetweenFilterPipeAndFilterName1(self):
"Raise TemplateSyntaxError for space after a filter pipe"
t = '<h1>{{ var| lower }}</h1>'
expected = '<h1>Hello There Programmer</h1>'
self.assertRaises(template.TemplateSyntaxError, template.Template, t)
def testNonexistentFilter(self):
"Raise TemplateSyntaxError for a nonexistent filter"
t = '<h1>{{ var|nonexistentfilter }}</h1>'
self.assertRaises(template.TemplateSyntaxError, template.Template, t)
def testDefaultFilter1(self):
"Ignore the default argument when a variable passed through the 'default' filter already exists"
c = template.Context({'var':'Variable'})
t = template.Template('<h1>{{ var|default:"Default" }}</h1>')
expected = '<h1>Variable</h1>'
self.assertEqual(expected, t.render(c))
def testDefaultFilter2(self):
"Use the default argument when a variable passed through the 'default' filter doesn't exist"
c = template.Context({'var':'Variable'})
t = template.Template('<h1>{{ nonvar|default:"Default" }}</h1>')
expected = '<h1>Default</h1>'
self.assertEqual(expected, t.render(c))
def testDefaultFilter3(self):
"Use the default argument when a variable passed through the 'default' filter doesn't exist (spaces)"
c = template.Context({'var':'Variable'})
t = template.Template('<h1>{{ nonvar|default:"Default value" }}</h1>')
expected = '<h1>Default value</h1>'
self.assertEqual(expected, t.render(c))
def testDefaultFilter4(self):
"Use the default argument when a variable passed through the 'default' filter doesn't exist (quoted)"
c = template.Context({'var':'Variable'})
t = template.Template('<h1>{{ nonvar|default:"Default \"quoted\" value" }}</h1>')
expected = '<h1>Default "quoted" value</h1>'
self.assertEqual(expected, t.render(c))
def testDefaultFilter4(self):
"Use the default argument when a variable passed through the 'default' filter doesn't exist (escaped backslash)"
c = template.Context({'var':'Variable'})
t = template.Template('<h1>{{ nonvar|default:"Default \\\\ slash" }}</h1>')
expected = '<h1>Default \\ slash</h1>'
self.assertEqual(expected, t.render(c))
def testDefaultFilter4(self):
"Use the default argument when a variable passed through the 'default' filter doesn't exist (single backslash)"
t = '<h1>{{ nonvar|default:"Default \\ slash" }}</h1>'
self.assertRaises(template.TemplateSyntaxError, template.Template, t)
def testIllegalCharacterInFilterName(self):
"Raise TemplateSyntaxError when trying to access a filter containing an illegal character"
t = '<h1>{{ blah|(lower) }}</h1>'
self.assertRaises(template.TemplateSyntaxError, template.Template, t)
t = '<h1>{{ blah|low(er) }}</h1>'
self.assertRaises(template.TemplateSyntaxError, template.Template, t)
class BlockTagCheck(unittest.TestCase):
def testNonexistentTag(self):
"Raise TemplateSyntaxError for invalid block tags"
t = '<h1>{% not-a-tag %}</h1>'
self.assertRaises(template.TemplateSyntaxError, template.Template, t)
def testEmptyTag(self):
"Raise TemplateSyntaxError for empty block tags"
t = '{% %}'
self.assertRaises(template.TemplateSyntaxError, template.Template, t)
class FirstOfCheck(unittest.TestCase):
def testFirstOfDisplaysFirstIfSet(self):
"A firstof tag should display the first item if it evaluates to true somehow"
c = template.Context({'first': 'one', 'second': 'two'})
t = template.Template('<h1>{% firstof first second %}</h1>')
expected = '<h1>one</h1>'
self.assertEqual(expected, t.render(c))
def testFirstOfDisplaysSecondIfFirstIsFalse(self):
"A firstof tag should display the second item if it evaluates to true and the first is false"
c = template.Context({'first': '', 'second': 'two'})
t = template.Template('<h1>{% firstof first second %}</h1>')
expected = '<h1>two</h1>'
self.assertEqual(expected, t.render(c))
def testFirstOfRaisesErrorIfEmpty(self):
"A firstof tag should raise a syntax error if it doesn't have any arguments"
t = '{% firstof %}'
self.assertRaises(template.TemplateSyntaxError, template.Template, t)
def testFirstOfDoesNothingIfAllAreFalse(self):
"A firstof tag should display nothing if no arguments evaluate to true"
c = template.Context({'first': '', 'second': False})
t = template.Template('<h1>{% firstof first second third %}</h1>')
expected = '<h1></h1>'
self.assertEqual(expected, t.render(c))
def testFirstOfWorksWithInts(self):
"Can a firstof tag display an integer?"
c = template.Context({'first': 1, 'second': False})
t = template.Template('<h1>{% firstof first second %}</h1>')
expected = '<h1>1</h1>'
self.assertEqual(expected, t.render(c))
class IfStatementCheck(unittest.TestCase):
def testSingleIfStatementTrue(self):
"An if statement should display its contents if the test evaluates true"
c = template.Context({'test':True})
t = template.Template('<h1>{% if test %}Yes{% endif %}</h1>')
expected = '<h1>Yes</h1>'
self.assertEqual(expected, t.render(c))
def testSingleIfStatementFalse(self):
"An if statement should not display its contents if the test is false"
c = template.Context({'test':False})
t = template.Template('<h1>{% if test %}Should not see this{% endif %}</h1>')
expected = '<h1></h1>'
self.assertEqual(expected, t.render(c))
def testNestedIfStatementTrueThenTrue(self):
"Nested if statements should work properly (case 1)"
c = template.Context({'test1':True, 'test2':True})
t = template.Template('<h1>{% if test1 %} First {% if test2 %} Second {% endif %} First again {% endif %}</h1>')
expected = '<h1> First Second First again </h1>'
self.assertEqual(expected, t.render(c))
def testNestedIfStatementTrueThenFalse(self):
"Nested if statements should work properly (case 2)"
c = template.Context({'test1':True, 'test2':False})
t = template.Template('<h1>{% if test1 %} First {% if test2 %} Second {% endif %} First again {% endif %}</h1>')
expected = '<h1> First First again </h1>'
self.assertEqual(expected, t.render(c))
def testNestedIfStatementFalseThenTrue(self):
"Nested if statements should work properly (case 3)"
c = template.Context({'test1':False, 'test2':True})
t = template.Template('<h1>{% if test1 %} First {% if test2 %} Second {% endif %} First again {% endif %}</h1>')
expected = '<h1></h1>'
self.assertEqual(expected, t.render(c))
def testNestedIfStatementFalseThenFalse(self):
"Nested if statements should work properly (case 4)"
c = template.Context({'test1':False, 'test2':False})
t = template.Template('<h1>{% if test1 %} First {% if test2 %} Second {% endif %} First again {% endif %}</h1>')
expected = '<h1></h1>'
self.assertEqual(expected, t.render(c))
def testElseIfTrue(self):
"An else statement should not execute if the test evaluates to true"
c = template.Context({'test':True})
t = template.Template('<h1>{% if test %}Correct{% else %}Incorrect{% endif %}</h1>')
expected = '<h1>Correct</h1>'
self.assertEqual(expected, t.render(c))
def testElseIfFalse(self):
"An else statement should execute if the test evaluates to false"
c = template.Context({'test':False})
t = template.Template('<h1>{% if test %}Incorrect{% else %}Correct{% endif %}</h1>')
expected = '<h1>Correct</h1>'
self.assertEqual(expected, t.render(c))
def testNonClosedIfTag(self):
"Raise TemplateSyntaxError for non-closed 'if' tags"
c = template.Context({'test':True})
t = '<h1>{% if test %}</h1>'
self.assertRaises(template.TemplateSyntaxError, template.Template, t)
def testNonexistentTest(self):
"Fail silently when an if statement accesses a nonexistent test"
c = template.Context({'var':'value'})
t = template.Template('<h1>{% if nonexistent %}Hello{% endif %}</h1>')
expected = '<h1></h1>'
self.assertEqual(expected, t.render(c))
def testIfTagNoArgs(self):
"If statements must have one argument (case 1)"
t = '<h1>{% if %}Hello{% endif %}</h1>'
self.assertRaises(template.TemplateSyntaxError, template.Template, t)
def testIfTagManyArgs(self):
"If statements must have one argument (case 2)"
t = '<h1>{% if multiple tests %}Hello{% endif %}</h1>'
self.assertRaises(template.TemplateSyntaxError, template.Template, t)
def testAttributeAccessInIfNode(self):
"An if node should resolve a variable's attributes before checking it as a test"
class AClass: pass
obj = AClass()
obj.article = AClass()
obj.article.section = AClass()
obj.article.section.title = 'Headline'
c = template.Context({'obj':obj})
t = template.Template('<h1>{% if obj.article.section.title %}Hello{% endif %}</h1>')
expected = '<h1>Hello</h1>'
self.assertEqual(expected, t.render(c))
t = template.Template('<h1>{% if obj.article.section.not_here %}Hello{% endif %}</h1>')
expected = '<h1></h1>'
self.assertEqual(expected, t.render(c))
def testIfNot(self):
"If statements supports 'not' as an optional argument"
t = template.Template('{% if not a %}Not a{% endif %}')
c = template.Context({'a': False})
expected = 'Not a'
self.assertEqual(expected, t.render(c))
c['a'] = True
expected = ''
self.assertEqual(expected, t.render(c))
def testIfOr(self):
"If statements support 'or'"
t = template.Template('{% if a or b %}Hello{% endif %}')
c = template.Context({'a': False, 'b': True})
expected = 'Hello'
self.assertEqual(expected, t.render(c))
c['b'] = False
expected = ''
self.assertEqual(expected, t.render(c))
def testIfOrNot(self):
"If statements support 'or' clauses with optional 'not's"
t = template.Template('{% if a or not b or c%}Hello{% endif %}')
c = template.Context({'a': False, 'b': False, 'c': False})
expected = 'Hello'
self.assertEqual(expected, t.render(c))
c['b'] = True
expected = ''
self.assertEqual(expected, t.render(c))
class ForLoopCheck(unittest.TestCase):
def testNormalForLoop(self):
"A for loop should work as expected, given one or more values"
c = template.Context({'pieces': ('1', '2', '3')})
t = template.Template('<h1>{% for piece in pieces %}{{ piece }}{% endfor %}</h1>')
expected = '<h1>123</h1>'
self.assertEqual(expected, t.render(c))
def testBlankForLoop(self):
"A for loop should work as expected, given an empty list"
c = template.Context({'pieces': []})
t = template.Template('<h1>{% for piece in pieces %}{{ piece }}{% endfor %}</h1>')
expected = '<h1></h1>'
self.assertEqual(expected, t.render(c))
def testInvalidForTagFourWords(self):
"Raise TemplateSyntaxError if a 'for' statement is not exactly 4 words"
t = '<h1>{% for article %}</h1>'
self.assertRaises(template.TemplateSyntaxError, template.Template, t)
def testInvalidForTagThirdWord(self):
"Raise TemplateSyntaxError if 3rd word in a 'for' statement isn't 'in'"
t = '<h1>{% for article NOTIN blah %}{% endfor %}</h1>'
self.assertRaises(template.TemplateSyntaxError, template.Template, t)
def testNonClosedForTag(self):
"Raise TemplateSyntaxError for non-closed 'for' tags"
t = '<h1>{% for i in numbers %}{{ i }}</h1>'
self.assertRaises(template.TemplateSyntaxError, template.Template, t)
def testNonexistentVariable1(self):
"Fail silently in loops with nonexistent variables in defn"
c = template.Context({'var':'value'})
t = template.Template('<h1>{% for i in nonexistent %}<p>{{ var }}</p>{% endfor %}</h1>')
expected = '<h1></h1>'
self.assertEqual(expected, t.render(c))
def testNonexistentVariable2(self):
"Raise TemplateSyntaxError in loops with nonexistent variables in loop"
c = template.Context({'set':('val1', 'val2')})
t = template.Template('<h1>{% for i in set %}<p>{{ nonexistent }}</p>{% endfor %}</h1>')
expected = '<h1><p></p><p></p></h1>'
self.assertEqual(expected, t.render(c))
def testAttributeAccessInForNode(self):
"A for node should resolve a variable's attributes before looping through it"
c = template.Context({'article': {'authors':('Simon', 'Adrian')}})
t = template.Template('<p>{% for i in article.authors %}{{ i }}{% endfor %}</p>')
self.assertEqual('<p>SimonAdrian</p>', t.render(c))
t = template.Template('<p>{% for i in article.nonexistent %}{{ i }}{% endfor %}</p>')
self.assertEqual('<p></p>', t.render(c))
def testForLoopFirst(self):
"A for loop's 'first' variable should work as expected"
c = template.Context({'pieces': ('1', '2', '3')})
t = template.Template('<h1>{% for piece in pieces %}{% if forloop.first %}<h2>First</h2>{% endif %}{{ piece }}{% endfor %}</h1>')
expected = '<h1><h2>First</h2>123</h1>'
self.assertEqual(expected, t.render(c))
def testForLoopLast(self):
"A for loop's 'last' variable should work as expected"
c = template.Context({'pieces': ('1', '2', '3')})
t = template.Template('<h1>{% for piece in pieces %}{% if forloop.last %}<h2>Last</h2>{% endif %}{{ piece }}{% endfor %}</h1>')
expected = '<h1>12<h2>Last</h2>3</h1>'
self.assertEqual(expected, t.render(c))
class CycleNodeCheck(unittest.TestCase):
def testNormalUsage(self):
"A cycle tag should work as expected"
c = template.Context({'set':range(10)})
t = template.Template('{% for i in set %}{% cycle red, green %}-{{ i }} {% endfor %}')
expected = 'red-0 green-1 red-2 green-3 red-4 green-5 red-6 green-7 red-8 green-9 '
self.assertEqual(expected, t.render(c))
def testNoArguments(self):
"Raise TemplateSyntaxError in cycle tags with no arguments"
t = '{% cycle %}'
self.assertRaises(template.TemplateSyntaxError, template.Template, t)
def testOneArgument(self):
"Raise TemplateSyntaxError in cycle tags with only one argument"
t = '{% cycle hello %}'
self.assertRaises(template.TemplateSyntaxError, template.Template, t)
def testExtraInitialSpaces(self):
"Extra spaces around cycle tags and their arguments should be ignored"
c = template.Context({'set':range(5)})
t = template.Template('{% for i in set %}{% cycle red, green %}{% endfor %}')
expected = 'redgreenredgreenred'
self.assertEqual(expected, t.render(c))
class TemplateTagNodeCheck(unittest.TestCase):
def testNormalUsage(self):
"A templatetag tag should work as expected"
c = template.Context()
t = template.Template('{% templatetag openblock %}{% templatetag closeblock %}{% templatetag openvariable %}{% templatetag closevariable %}')
expected = '{%%}{{}}'
self.assertEqual(expected, t.render(c))
def testNoArguments(self):
"Raise TemplateSyntaxError in templatetag tags with no arguments"
t = '{% templatetag %}'
self.assertRaises(template.TemplateSyntaxError, template.Template, t)
def testTwoArguments(self):
"Raise TemplateSyntaxError in templatetag tags with more than one argument"
t = '{% templatetag hello goodbye %}'
self.assertRaises(template.TemplateSyntaxError, template.Template, t)
t = '{% templatetag hello goodbye helloagain %}'
self.assertRaises(template.TemplateSyntaxError, template.Template, t)
def testBadArgument(self):
"Raise TemplateSyntaxError in templatetag tags with invalid arguments"
t = '{% templatetag hello %}'
self.assertRaises(template.TemplateSyntaxError, template.Template, t)
class PluginFilterCheck(unittest.TestCase):
def custom_filter(self, value, arg):
"Temporary filter used to verify the filter plugin system is working"
return "_%s_%s_" % (value, arg)
def testPluginFilter(self):
"Plugin support allows for custom filters"
template.register_filter('unittest', self.custom_filter, True)
c = template.Context({'var':'value'})
t = template.Template('<body>{{ var|unittest:"hello" }}</body>')
expected = '<body>_value_hello_</body>'
self.assertEqual(expected, t.render(c))
template.unregister_filter('unittest')
def testUnregisterPluginFilter(self):
"Plugin support allows custom filters to be unregistered"
template.register_filter('unittest', self.custom_filter, True)
c = template.Context({'var':'value'})
t = template.Template('<body>{{ var|unittest:"hello" }}</body>')
rendered = t.render(c) # should run with no exception
template.unregister_filter('unittest')
class PluginTagCheck(unittest.TestCase):
class CustomNode(template.Node):
"Prints argument"
def __init__(self, arg):
self.arg = arg
def render(self, context):
return '_%s_' % self.arg
def do_custom_node(self, parser, token):
"Handle the 'unittest' custom tag"
bits = token.contents.split()
return self.CustomNode(bits[1])
def testPluginTag(self):
"Plugin support allows for custom tags"
template.register_tag('unittest', self.do_custom_node)
c = template.Context({})
t = template.Template('<body>{% unittest hello %}</body>')
expected = '<body>_hello_</body>'
self.assertEqual(expected, t.render(c))
template.unregister_tag('unittest')
def testUnregisterPluginTag(self):
"Plugin support allows custom tags to be unregistered"
template.register_tag('unittest', self.do_custom_node)
c = template.Context({})
t = template.Template('<body>{% unittest hello %}</body>')
rendered = t.render(c) # should run with no exception
del(t)
template.unregister_tag('unittest')
t = '<body>{% unittest hello %}</body>'
self.assertRaises(template.TemplateSyntaxError, template.Template, t)
class ContextUsageCheck(unittest.TestCase):
def testVariableContext2(self):
"Variables should fall through additional block-level contexts"
c = template.Context({'global':'out', 'set': ('1', '2', '3')})
t = template.Template('<body><h1>{{ global }}</h1>{% for i in set %}<p>{{ i }} {{ global }}</p>{% endfor %}</body>')
expected = '<body><h1>out</h1><p>1 out</p><p>2 out</p><p>3 out</p></body>'
self.assertEqual(expected, t.render(c))
def testVariableContext2(self):
"Variables set within a block statement override like-named variables within their scope"
c = template.Context({'i':'out', 'set': ('1', '2', '3')})
t = template.Template('<body><h1>{{ i }}</h1>{% for i in set %}<p>{{ i }}</p>{% endfor %}{{ i }}</body>')
expected = '<body><h1>out</h1><p>1</p><p>2</p><p>3</p>out</body>'
self.assertEqual(expected, t.render(c))
def testVariableContextDelete(self):
"Variables can be deleted from the current context"
c = template.Context({'a':'first', 'b':'second'})
del c['a']
self.assertEqual(c.__repr__(), template.Context({'b':'second'}).__repr__())
def testInvalidVariableContextDelete(self):
"Raise KeyError if code tries to delete a variable that doesn't exist in the current context"
c = template.Context({'a':'first'})
self.assertRaises(KeyError, c.__delitem__, 'b')
class AdvancedUsageCheck(unittest.TestCase):
def testIfInsideFor(self):
"An if statement should be executed repeatedly inside a for statement"
c = template.Context({'set':(True, False, True, True, False)})
t = template.Template('<ul>{% for i in set %}{% if i %}<li>1</li>{% endif %}{% endfor %}</ul>')
expected = '<ul><li>1</li><li>1</li><li>1</li></ul>'
self.assertEqual(expected, t.render(c))
def testIfElseInsideFor(self):
"An if/else statement should be executed repeatedly inside a for statement"
c = template.Context({'set':(True, False, True, True, False)})
t = template.Template('<ul>{% for i in set %}<li>{% if i %}1{% else %}0{% endif %}</li>{% endfor %}</ul>')
expected = '<ul><li>1</li><li>0</li><li>1</li><li>1</li><li>0</li></ul>'
self.assertEqual(expected, t.render(c))
def testForInsideIf_True(self):
"A for loop inside an if statement should be executed if the test=true"
c = template.Context({'test':True, 'set':('1', '2', '3')})
t = template.Template('<body>{% if test %}<ul>{% for i in set %}<li>{{ i }}</li>{% endfor %}</ul>{% endif %}</body>')
expected = '<body><ul><li>1</li><li>2</li><li>3</li></ul></body>'
self.assertEqual(expected, t.render(c))
def testForInsideIf_False(self):
"A for loop inside an if statement shouldn't be executed if the test=false"
c = template.Context({'test':False, 'set':('1', '2', '3')})
t = template.Template('<body>{% if test %}<ul>{% for i in set %}<li>{{ i }}</li>{% endfor %}</ul>{% endif %}</body>')
expected = '<body></body>'
self.assertEqual(expected, t.render(c))
def testForInsideIfInsideFor(self):
"A for loop inside an if statement inside a for loop should work properly"
c = template.Context({'set1': (True, False, False, False, True), 'set2': ('1', '2', '3')})
t = template.Template('<body>{% for i in set1 %}{% if i %}{% for j in set2 %}{{ j }}{% endfor %}{% endif %}{% endfor %}</body>')
expected = '<body>123123</body>'
self.assertEqual(expected, t.render(c))
def testMultipleRendersWhenCompiled(self):
"A template can render multiple contexts without having to be recompiled"
t = template.Template('<body>{% for i in set1 %}{% if i %}{% for j in set2 %}{{ j }}{% endfor %}{% endif %}{% endfor %}</body>')
c = template.Context({'set1': (True, False, False, False, False), 'set2': ('1', '2', '3')})
self.assertEqual('<body>123</body>', t.render(c))
c = template.Context({'set1': (True, True, False, False, False), 'set2': ('1', '2', '3')})
self.assertEqual('<body>123123</body>', t.render(c))
c = template.Context({'set1': (True, True, True, False, False), 'set2': ('1', '2', '3')})
self.assertEqual('<body>123123123</body>', t.render(c))
c = template.Context({'set1': (True, True, True, True, False), 'set2': ('1', '2', '3')})
self.assertEqual('<body>123123123123</body>', t.render(c))
c = template.Context({'set1': (True, True, True, True, True), 'set2': ('1', '2', '3')})
self.assertEqual('<body>123123123123123</body>', t.render(c))
def tests():
s = unittest.TestLoader().loadTestsFromName(__name__)
unittest.TextTestRunner(verbosity=0).run(s)
if __name__ == "__main__":
tests()

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

View File

@ -0,0 +1,171 @@
class MergeDict:
"""
A simple class for creating new "virtual" dictionaries that actualy look
up values in more than one dictionary, passed in the constructor.
"""
def __init__(self, *dicts):
self.dicts = dicts
def __getitem__(self, key):
for dict in self.dicts:
try:
return dict[key]
except KeyError:
pass
raise KeyError
def get(self, key, default):
try:
return self[key]
except KeyError:
return default
def getlist(self, key):
for dict in self.dicts:
try:
return dict.getlist(key)
except KeyError:
pass
raise KeyError
def items(self):
item_list = []
for dict in self.dicts:
item_list.extend(dict.items())
return item_list
def has_key(self, key):
for dict in self.dicts:
if dict.has_key(key):
return True
return False
class MultiValueDictKeyError(KeyError):
pass
class MultiValueDict:
"""
A dictionary-like class customized to deal with multiple values for the same key.
>>> d = MultiValueDict({'name': ['Adrian', 'Simon'], 'position': ['Developer']})
>>> d['name']
'Simon'
>>> d.getlist('name')
['Adrian', 'Simon']
>>> d.get('lastname', 'nonexistent')
'nonexistent'
>>> d.setlist('lastname', ['Holovaty', 'Willison'])
This class exists to solve the irritating problem raised by cgi.parse_qs,
which returns a list for every key, even though most Web forms submit
single name-value pairs.
"""
def __init__(self, key_to_list_mapping=None):
self.data = key_to_list_mapping or {}
def __repr__(self):
return repr(self.data)
def __getitem__(self, key):
"Returns the data value for this key; raises KeyError if not found"
if self.data.has_key(key):
try:
return self.data[key][-1] # in case of duplicates, use last value ([-1])
except IndexError:
return []
raise MultiValueDictKeyError, "Key '%s' not found in MultiValueDict %s" % (key, self.data)
def __setitem__(self, key, value):
self.data[key] = [value]
def __len__(self):
return len(self.data)
def get(self, key, default):
"Returns the default value if the requested data doesn't exist"
try:
val = self[key]
except (KeyError, IndexError):
return default
if val == []:
return default
return val
def getlist(self, key):
"Returns an empty list if the requested data doesn't exist"
try:
return self.data[key]
except KeyError:
return []
def setlist(self, key, list_):
self.data[key] = list_
def appendlist(self, key, item):
"Appends an item to the internal list associated with key"
try:
self.data[key].append(item)
except KeyError:
self.data[key] = [item]
def has_key(self, key):
return self.data.has_key(key)
def items(self):
# we don't just return self.data.items() here, because we want to use
# self.__getitem__() to access the values as *strings*, not lists
return [(key, self[key]) for key in self.data.keys()]
def keys(self):
return self.data.keys()
def update(self, other_dict):
if isinstance(other_dict, MultiValueDict):
for key, value_list in other_dict.data.items():
self.data.setdefault(key, []).extend(value_list)
elif type(other_dict) == type({}):
for key, value in other_dict.items():
self.data.setdefault(key, []).append(value)
else:
raise ValueError, "MultiValueDict.update() takes either a MultiValueDict or dictionary"
def copy(self):
"Returns a copy of this object"
import copy
cp = copy.deepcopy(self)
return cp
class DotExpandedDict(dict):
"""
A special dictionary constructor that takes a dictionary in which the keys
may contain dots to specify inner dictionaries. It's confusing, but this
example should make sense.
>>> d = DotExpandedDict({'person.1.firstname': ['Simon'],
'person.1.lastname': ['Willison'],
'person.2.firstname': ['Adrian'],
'person.2.lastname': ['Holovaty']})
>>> d
{'person': {'1': {'lastname': ['Willison'], 'firstname': ['Simon']},
'2': {'lastname': ['Holovaty'], 'firstname': ['Adrian']}}}
>>> d['person']
{'1': {'firstname': ['Simon'], 'lastname': ['Willison'],
'2': {'firstname': ['Adrian'], 'lastname': ['Holovaty']}
>>> d['person']['1']
{'firstname': ['Simon'], 'lastname': ['Willison']}
# Gotcha: Results are unpredictable if the dots are "uneven":
>>> DotExpandedDict({'c.1': 2, 'c.2': 3, 'c': 1})
>>> {'c': 1}
"""
def __init__(self, key_to_list_mapping):
for k, v in key_to_list_mapping.items():
current = self
bits = k.split('.')
for bit in bits[:-1]:
current = current.setdefault(bit, {})
# Now assign value to current position
try:
current[bits[-1]] = v
except TypeError: # Special-case if current isn't a dict.
current = {bits[-1]: v}

317
django/utils/dateformat.py Normal file
View File

@ -0,0 +1,317 @@
"""
PHP date() style date formatting
See http://www.php.net/date for format strings
Usage:
>>> import datetime
>>> d = datetime.datetime.now()
>>> df = DateFormat(d)
>>> print df.format('jS F Y H:i')
7th October 2003 11:39
>>>
"""
from calendar import isleap
from dates import MONTHS, MONTHS_AP, WEEKDAYS
class DateFormat:
year_days = [None, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]
def __init__(self, d):
self.date = d
def a(self):
"'a.m.' or 'p.m.'"
if self.date.hour > 11:
return 'p.m.'
return 'a.m.'
def A(self):
"'AM' or 'PM'"
if self.date.hour > 11:
return 'PM'
return 'AM'
def B(self):
"Swatch Internet time"
raise NotImplementedError
def d(self):
"Day of the month, 2 digits with leading zeros; i.e. '01' to '31'"
return '%02d' % self.date.day
def D(self):
"Day of the week, textual, 3 letters; e.g. 'Fri'"
return WEEKDAYS[self.date.weekday()][0:3]
def f(self):
"""
Time, in 12-hour hours and minutes, with minutes left off if they're zero.
Examples: '1', '1:30', '2:05', '2'
Proprietary extension.
"""
if self.date.minute == 0:
return self.g()
return '%s:%s' % (self.g(), self.i())
def F(self):
"Month, textual, long; e.g. 'January'"
return MONTHS[self.date.month]
def g(self):
"Hour, 12-hour format without leading zeros; i.e. '1' to '12'"
if self.date.hour == 0:
return 12
if self.date.hour > 12:
return self.date.hour - 12
return self.date.hour
def G(self):
"Hour, 24-hour format without leading zeros; i.e. '0' to '23'"
return self.date.hour
def h(self):
"Hour, 12-hour format; i.e. '01' to '12'"
return '%02d' % self.g()
def H(self):
"Hour, 24-hour format; i.e. '00' to '23'"
return '%02d' % self.G()
def i(self):
"Minutes; i.e. '00' to '59'"
return '%02d' % self.date.minute
def I(self):
"'1' if Daylight Savings Time, '0' otherwise."
raise NotImplementedError
def j(self):
"Day of the month without leading zeros; i.e. '1' to '31'"
return self.date.day
def l(self):
"Day of the week, textual, long; e.g. 'Friday'"
return WEEKDAYS[self.date.weekday()]
def L(self):
"Boolean for whether it is a leap year; i.e. True or False"
return isleap(self.date.year)
def m(self):
"Month; i.e. '01' to '12'"
return '%02d' % self.date.month
def M(self):
"Month, textual, 3 letters; e.g. 'Jan'"
return MONTHS[self.date.month][0:3]
def n(self):
"Month without leading zeros; i.e. '1' to '12'"
return self.date.month
def N(self):
"Month abbreviation in Associated Press style. Proprietary extension."
return MONTHS_AP[self.date.month]
def O(self):
"Difference to Greenwich time in hours; e.g. '+0200'"
raise NotImplementedError
def P(self):
"""
Time, in 12-hour hours, minutes and 'a.m.'/'p.m.', with minutes left off
if they're zero and the strings 'midnight' and 'noon' if appropriate.
Examples: '1 a.m.', '1:30 p.m.', 'midnight', 'noon', '12:30 p.m.'
Proprietary extension.
"""
if self.date.minute == 0 and self.date.hour == 0:
return 'midnight'
if self.date.minute == 0 and self.date.hour == 12:
return 'noon'
return '%s %s' % (self.f(), self.a())
def r(self):
"RFC 822 formatted date; e.g. 'Thu, 21 Dec 2000 16:01:07 +0200'"
raise NotImplementedError
def s(self):
"Seconds; i.e. '00' to '59'"
return '%02d' % self.date.second
def S(self):
"English ordinal suffix for the day of the month, 2 characters; i.e. 'st', 'nd', 'rd' or 'th'"
if self.date.day in (11, 12, 13): # Special case
return 'th'
last = self.date.day % 10
if last == 1:
return 'st'
if last == 2:
return 'nd'
if last == 3:
return 'rd'
return 'th'
def t(self):
"Number of days in the given month; i.e. '28' to '31'"
raise NotImplementedError
def T(self):
"Time zone of this machine; e.g. 'EST' or 'MDT'"
raise NotImplementedError
def U(self):
"Seconds since the Unix epoch (January 1 1970 00:00:00 GMT)"
raise NotImplementedError
def w(self):
"Day of the week, numeric, i.e. '0' (Sunday) to '6' (Saturday)"
weekday = self.date.weekday()
if weekday == 0:
return 6
return weekday - 1
def W(self):
"ISO-8601 week number of year, weeks starting on Monday"
# Algorithm from http://www.personal.ecu.edu/mccartyr/ISOwdALG.txt
week_number = None
jan1_weekday = self.date.replace(month=1, day=1).weekday() + 1
weekday = self.date.weekday() + 1
day_of_year = self.z()
if day_of_year <= (8 - jan1_weekday) and jan1_weekday > 4:
if jan1_weekday == 5 or (jan1_weekday == 6 and isleap(self.date.year-1)):
week_number = 53
else:
week_number = 52
else:
if isleap(self.date.year):
i = 366
else:
i = 365
if (i - day_of_year) < (4 - weekday):
week_number = 1
else:
j = day_of_year + (7 - weekday) + (jan1_weekday - 1)
week_number = j / 7
if jan1_weekday > 4:
week_number -= 1
return week_number
def Y(self):
"Year, 4 digits; e.g. '1999'"
return self.date.year
def y(self):
"Year, 2 digits; e.g. '99'"
return str(self.date.year)[2:]
def z(self):
"Day of the year; i.e. '0' to '365'"
doy = self.year_days[self.date.month] + self.date.day
if self.L() and self.date.month > 2:
doy += 1
return doy
def Z(self):
"""Time zone offset in seconds (i.e. '-43200' to '43200'). The offset
for timezones west of UTC is always negative, and for those east of UTC
is always positive."""
raise NotImplementedError
def format(self, formatstr):
result = ''
for char in formatstr:
try:
result += str(getattr(self, char)())
except AttributeError:
result += char
return result
class TimeFormat:
def __init__(self, t):
self.time = t
def a(self):
"'a.m.' or 'p.m.'"
if self.time.hour > 11:
return 'p.m.'
else:
return 'a.m.'
def A(self):
"'AM' or 'PM'"
return self.a().upper()
def B(self):
"Swatch Internet time"
raise NotImplementedError
def f(self):
"""
Time, in 12-hour hours and minutes, with minutes left off if they're zero.
Examples: '1', '1:30', '2:05', '2'
Proprietary extension.
"""
if self.time.minute == 0:
return self.g()
return '%s:%s' % (self.g(), self.i())
def g(self):
"Hour, 12-hour format without leading zeros; i.e. '1' to '12'"
if self.time.hour == 0:
return 12
if self.time.hour > 12:
return self.time.hour - 12
return self.time.hour
def G(self):
"Hour, 24-hour format without leading zeros; i.e. '0' to '23'"
return self.time.hour
def h(self):
"Hour, 12-hour format; i.e. '01' to '12'"
return '%02d' % self.g()
def H(self):
"Hour, 24-hour format; i.e. '00' to '23'"
return '%02d' % self.G()
def i(self):
"Minutes; i.e. '00' to '59'"
return '%02d' % self.time.minute
def P(self):
"""
Time, in 12-hour hours, minutes and 'a.m.'/'p.m.', with minutes left off
if they're zero and the strings 'midnight' and 'noon' if appropriate.
Examples: '1 a.m.', '1:30 p.m.', 'midnight', 'noon', '12:30 p.m.'
Proprietary extension.
"""
if self.time.minute == 0 and self.time.hour == 0:
return 'midnight'
if self.time.minute == 0 and self.time.hour == 12:
return 'noon'
return '%s %s' % (self.f(), self.a())
def s(self, s):
"Seconds; i.e. '00' to '59'"
return '%02d' % self.time.second
def format(self, formatstr):
result = ''
for char in formatstr:
try:
result += str(getattr(self, char)())
except AttributeError:
result += char
return result
def format(value, format_string):
"Convenience function"
df = DateFormat(value)
return df.format(format_string)
def time_format(value, format_string):
"Convenience function"
tf = TimeFormat(value)
return tf.format(format_string)

27
django/utils/dates.py Normal file
View File

@ -0,0 +1,27 @@
"Commonly-used date structures"
WEEKDAYS = {
0:'Monday', 1:'Tuesday', 2:'Wednesday', 3:'Thursday', 4:'Friday',
5:'Saturday', 6:'Sunday'
}
WEEKDAYS_REV = {
'monday':0, 'tuesday':1, 'wednesday':2, 'thursday':3, 'friday':4,
'saturday':5, 'sunday':6
}
MONTHS = {
1:'January', 2:'February', 3:'March', 4:'April', 5:'May', 6:'June',
7:'July', 8:'August', 9:'September', 10:'October', 11:'November',
12:'December'
}
MONTHS_3 = {
1:'jan', 2:'feb', 3:'mar', 4:'apr', 5:'may', 6:'jun', 7:'jul', 8:'aug',
9:'sep', 10:'oct', 11:'nov', 12:'dec'
}
MONTHS_3_REV = {
'jan':1, 'feb':2, 'mar':3, 'apr':4, 'may':5, 'jun':6, 'jul':7, 'aug':8,
'sep':9, 'oct':10, 'nov':11, 'dec':12
}
MONTHS_AP = { # month names in Associated Press style
1:'Jan.', 2:'Feb.', 3:'March', 4:'April', 5:'May', 6:'June', 7:'July',
8:'Aug.', 9:'Sept.', 10:'Oct.', 11:'Nov.', 12:'Dec.'
}

View File

@ -0,0 +1,152 @@
"""
Syndication feed generation library -- used for generating RSS, etc.
By Adrian Holovaty
Released under the Python license
Sample usage:
>>> feed = feedgenerator.Rss201rev2Feed(
... title=u"Poynter E-Media Tidbits",
... link=u"http://www.poynter.org/column.asp?id=31",
... description=u"A group weblog by the sharpest minds in online media/journalism/publishing.",
... language=u"en",
... )
>>> feed.add_item(title="Hello", link=u"http://www.holovaty.com/test/", description="Testing.")
>>> fp = open('test.rss', 'w')
>>> feed.write(fp, 'utf-8')
>>> fp.close()
For definitions of the different versions of RSS, see:
http://diveintomark.org/archives/2004/02/04/incompatible-rss
"""
from django.utils.xmlutils import SimplerXMLGenerator
class SyndicationFeed:
"Base class for all syndication feeds. Subclasses should provide write()"
def __init__(self, title, link, description, language=None):
self.feed_info = {
'title': title,
'link': link,
'description': description,
'language': language,
}
self.items = []
def add_item(self, title, link, description, author_email=None,
author_name=None, pubdate=None, comments=None, unique_id=None,
enclosure=None):
"""
Adds an item to the feed. All args are expected to be Python Unicode
objects except pubdate, which is a datetime.datetime object, and
enclosure, which is an instance of the Enclosure class.
"""
self.items.append({
'title': title,
'link': link,
'description': description,
'author_email': author_email,
'author_name': author_name,
'pubdate': pubdate,
'comments': comments,
'unique_id': unique_id,
'enclosure': enclosure,
})
def num_items(self):
return len(self.items)
def write(self, outfile, encoding):
"""
Outputs the feed in the given encoding to outfile, which is a file-like
object. Subclasses should override this.
"""
raise NotImplementedError
def writeString(self, encoding):
"""
Returns the feed in the given encoding as a string.
"""
from StringIO import StringIO
s = StringIO()
self.write(s, encoding)
return s.getvalue()
class Enclosure:
"Represents an RSS enclosure"
def __init__(self, url, length, mime_type):
"All args are expected to be Python Unicode objects"
self.url, self.length, self.mime_type = url, length, mime_type
class RssFeed(SyndicationFeed):
def write(self, outfile, encoding):
handler = SimplerXMLGenerator(outfile, encoding)
handler.startDocument()
self.writeRssElement(handler)
self.writeChannelElement(handler)
for item in self.items:
self.writeRssItem(handler, item)
self.endChannelElement(handler)
self.endRssElement(handler)
def writeRssElement(self, handler):
"Adds the <rss> element to handler, taking care of versioning, etc."
raise NotImplementedError
def endRssElement(self, handler):
"Ends the <rss> element."
handler.endElement(u"rss")
def writeChannelElement(self, handler):
handler.startElement(u"channel", {})
handler.addQuickElement(u"title", self.feed_info['title'], {})
handler.addQuickElement(u"link", self.feed_info['link'], {})
handler.addQuickElement(u"description", self.feed_info['description'], {})
if self.feed_info['language'] is not None:
handler.addQuickElement(u"language", self.feed_info['language'], {})
def endChannelElement(self, handler):
handler.endElement(u"channel")
class RssUserland091Feed(RssFeed):
def startRssElement(self, handler):
handler.startElement(u"rss", {u"version": u"0.91"})
def writeRssItem(self, handler, item):
handler.startElement(u"item", {})
handler.addQuickElement(u"title", item['title'], {})
handler.addQuickElement(u"link", item['link'], {})
if item['description'] is not None:
handler.addQuickElement(u"description", item['description'], {})
handler.endElement(u"item")
class Rss201rev2Feed(RssFeed):
# Spec: http://blogs.law.harvard.edu/tech/rss
def writeRssElement(self, handler):
handler.startElement(u"rss", {u"version": u"2.0"})
def writeRssItem(self, handler, item):
handler.startElement(u"item", {})
handler.addQuickElement(u"title", item['title'], {})
handler.addQuickElement(u"link", item['link'], {})
if item['description'] is not None:
handler.addQuickElement(u"description", item['description'], {})
if item['author_email'] is not None and item['author_name'] is not None:
handler.addQuickElement(u"author", u"%s (%s)" % \
(item['author_email'], item['author_name']), {})
if item['pubdate'] is not None:
handler.addQuickElement(u"pubDate", item['pubdate'].strftime('%a, %d %b %Y %H:%M:%S %Z'), {})
if item['comments'] is not None:
handler.addQuickElement(u"comments", item['comments'], {})
if item['unique_id'] is not None:
handler.addQuickElement(u"guid", item['unique_id'], {})
if item['enclosure'] is not None:
handler.addQuickElement(u"enclosure", '',
{u"url": item['enclosure'].url, u"length": item['enclosure'].length,
u"type": item['enclosure'].mime_type})
handler.endElement(u"item")
# This isolates the decision of what the system default is, so calling code can
# do "feedgenerator.DefaultRssFeed" instead of "feedgenerator.Rss201rev2Feed".
DefaultRssFeed = Rss201rev2Feed

110
django/utils/html.py Normal file
View File

@ -0,0 +1,110 @@
"Useful HTML utilities suitable for global use by World Online projects."
import re, string
# Configuration for urlize() function
LEADING_PUNCTUATION = ['(', '<', '&lt;']
TRAILING_PUNCTUATION = ['.', ',', ')', '>', '\n', '&gt;']
# list of possible strings used for bullets in bulleted lists
DOTS = ['&middot;', '*', '\xe2\x80\xa2', '&#149;', '&bull;', '&#8226;']
UNENCODED_AMPERSANDS_RE = re.compile(r'&(?!(\w+|#\d+);)')
WORD_SPLIT_RE = re.compile(r'(\s+)')
PUNCTUATION_RE = re.compile('^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % \
('|'.join([re.escape(p) for p in LEADING_PUNCTUATION]),
'|'.join([re.escape(p) for p in TRAILING_PUNCTUATION])))
SIMPLE_EMAIL_RE = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$')
LINK_TARGET_ATTRIBUTE = re.compile(r'(<a [^>]*?)target=[^\s>]+')
HTML_GUNK = re.compile(r'(?:<br clear="all">|<i><\/i>|<b><\/b>|<em><\/em>|<strong><\/strong>|<\/?smallcaps>|<\/?uppercase>)', re.IGNORECASE)
HARD_CODED_BULLETS = re.compile(r'((?:<p>(?:%s).*?[a-zA-Z].*?</p>\s*)+)' % '|'.join([re.escape(d) for d in DOTS]), re.DOTALL)
TRAILING_EMPTY_CONTENT = re.compile(r'(?:<p>(?:&nbsp;|\s|<br \/>)*?</p>\s*)+\Z')
def escape(html):
"Returns the given HTML with ampersands, quotes and carets encoded"
if not isinstance(html, basestring):
html = str(html)
return html.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;')
def linebreaks(value):
"Converts newlines into <p> and <br />s"
value = re.sub(r'\r\n|\r|\n', '\n', value) # normalize newlines
paras = re.split('\n{2,}', value)
paras = ['<p>%s</p>' % p.strip().replace('\n', '<br />') for p in paras]
return '\n\n'.join(paras)
def strip_tags(value):
"Returns the given HTML with all tags stripped"
return re.sub(r'<[^>]*?>', '', value)
def strip_entities(value):
"Returns the given HTML with all entities (&something;) stripped"
return re.sub(r'&(?:\w+|#\d);', '', value)
def fix_ampersands(value):
"Returns the given HTML with all unencoded ampersands encoded correctly"
return UNENCODED_AMPERSANDS_RE.sub('&amp;', value)
def urlize(text, trim_url_limit=None, nofollow=False):
"""
Converts any URLs in text into clickable links. Works on http://, https:// and
www. links. Links can have trailing punctuation (periods, commas, close-parens)
and leading punctuation (opening parens) and it'll still do the right thing.
If trim_url_limit is not None, the URLs in link text will be limited to
trim_url_limit characters.
If nofollow is True, the URLs in link text will get a rel="nofollow" attribute.
"""
trim_url = lambda x, limit=trim_url_limit: limit is not None and (x[:limit] + (len(x) >=limit and '...' or '')) or x
words = WORD_SPLIT_RE.split(text)
nofollow_attr = nofollow and ' rel="nofollow"' or ''
for i, word in enumerate(words):
match = PUNCTUATION_RE.match(word)
if match:
lead, middle, trail = match.groups()
if middle.startswith('www.') or ('@' not in middle and not middle.startswith('http://') and \
len(middle) > 0 and middle[0] in string.letters + string.digits and \
(middle.endswith('.org') or middle.endswith('.net') or middle.endswith('.com'))):
middle = '<a href="http://%s"%s>%s</a>' % (middle, nofollow_attr, trim_url(middle))
if middle.startswith('http://') or middle.startswith('https://'):
middle = '<a href="%s"%s>%s</a>' % (middle, nofollow_attr, trim_url(middle))
if '@' in middle and not middle.startswith('www.') and not ':' in middle \
and SIMPLE_EMAIL_RE.match(middle):
middle = '<a href="mailto:%s">%s</a>' % (middle, middle)
if lead + middle + trail != word:
words[i] = lead + middle + trail
return ''.join(words)
def clean_html(text):
"""
Cleans the given HTML. Specifically, it does the following:
* Converts <b> and <i> to <strong> and <em>.
* Encodes all ampersands correctly.
* Removes all "target" attributes from <a> tags.
* Removes extraneous HTML, such as presentational tags that open and
immediately close and <br clear="all">.
* Converts hard-coded bullets into HTML unordered lists.
* Removes stuff like "<p>&nbsp;&nbsp;</p>", but only if it's at the
bottom of the text.
"""
from django.utils.text import normalize_newlines
text = normalize_newlines(text)
text = re.sub(r'<(/?)\s*b\s*>', '<\\1strong>', text)
text = re.sub(r'<(/?)\s*i\s*>', '<\\1em>', text)
text = fix_ampersands(text)
# Remove all target="" attributes from <a> tags.
text = LINK_TARGET_ATTRIBUTE.sub('\\1', text)
# Trim stupid HTML such as <br clear="all">.
text = HTML_GUNK.sub('', text)
# Convert hard-coded bullets into HTML unordered lists.
def replace_p_tags(match):
s = match.group().replace('</p>', '</li>')
for d in DOTS:
s = s.replace('<p>%s' % d, '<li>')
return '<ul>\n%s\n</ul>' % s
text = HARD_CODED_BULLETS.sub(replace_p_tags, text)
# Remove stuff like "<p>&nbsp;&nbsp;</p>", but only if it's at the bottom of the text.
text = TRAILING_EMPTY_CONTENT.sub('', text)
return text

View File

@ -0,0 +1,319 @@
from Cookie import SimpleCookie
from pprint import pformat
import datastructures
DEFAULT_MIME_TYPE = 'text/html'
class HttpRequest(object): # needs to be new-style class because subclasses define "property"s
"A basic HTTP request"
def __init__(self):
self.GET, self.POST, self.COOKIES, self.META, self.FILES = {}, {}, {}, {}, {}
self.path = ''
def __repr__(self):
return '<HttpRequest\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' % \
(pformat(self.GET), pformat(self.POST), pformat(self.COOKIES),
pformat(self.META))
def __getitem__(self, key):
for d in (self.POST, self.GET):
if d.has_key(key):
return d[key]
raise KeyError, "%s not found in either POST or GET" % key
def get_full_path(self):
return ''
class ModPythonRequest(HttpRequest):
def __init__(self, req):
self._req = req
self.path = req.uri
def __repr__(self):
return '<ModPythonRequest\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' % \
(pformat(self.GET), pformat(self.POST), pformat(self.COOKIES),
pformat(self.META))
def get_full_path(self):
return '%s%s' % (self.path, self._req.args and ('?' + self._req.args) or '')
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 = parse_file_upload(self._req)
else:
self._post, self._files = QueryDict(self._req.read()), datastructures.MultiValueDict()
def _get_request(self):
if not hasattr(self, '_request'):
self._request = datastructures.MergeDict(self.POST, self.GET)
return self._request
def _get_get(self):
if not hasattr(self, '_get'):
self._get = QueryDict(self._req.args)
return self._get
def _set_get(self, get):
self._get = get
def _get_post(self):
if not hasattr(self, '_post'):
self._load_post_and_files()
return self._post
def _set_post(self, post):
self._post = post
def _get_cookies(self):
if not hasattr(self, '_cookies'):
self._cookies = parse_cookie(self._req.headers_in.get('cookie', ''))
return self._cookies
def _set_cookies(self, cookies):
self._cookies = cookies
def _get_files(self):
if not hasattr(self, '_files'):
self._load_post_and_files()
return self._files
def _get_meta(self):
"Lazy loader that returns self.META dictionary"
if not hasattr(self, '_meta'):
self._meta = {
'AUTH_TYPE': self._req.ap_auth_type,
'CONTENT_LENGTH': self._req.clength, # This may be wrong
'CONTENT_TYPE': self._req.content_type, # This may be wrong
'GATEWAY_INTERFACE': 'CGI/1.1',
'PATH_INFO': self._req.path_info,
'PATH_TRANSLATED': None, # Not supported
'QUERY_STRING': self._req.args,
'REMOTE_ADDR': self._req.connection.remote_ip,
'REMOTE_HOST': None, # DNS lookups not supported
'REMOTE_IDENT': self._req.connection.remote_logname,
'REMOTE_USER': self._req.user,
'REQUEST_METHOD': self._req.method,
'SCRIPT_NAME': None, # Not supported
'SERVER_NAME': self._req.server.server_hostname,
'SERVER_PORT': self._req.server.port,
'SERVER_PROTOCOL': self._req.protocol,
'SERVER_SOFTWARE': 'mod_python'
}
for key, value in self._req.headers_in.items():
key = 'HTTP_' + key.upper().replace('-', '_')
self._meta[key] = value
return self._meta
GET = property(_get_get, _set_get)
POST = property(_get_post, _set_post)
COOKIES = property(_get_cookies, _set_cookies)
FILES = property(_get_files)
META = property(_get_meta)
REQUEST = property(_get_request)
def parse_file_upload(req):
"Returns a tuple of (POST MultiValueDict, FILES MultiValueDict), given a mod_python req object"
import email, email.Message
from cgi import parse_header
raw_message = '\r\n'.join(['%s:%s' % pair for pair in req.headers_in.items()])
raw_message += '\r\n\r\n' + req.read()
msg = email.message_from_string(raw_message)
POST = datastructures.MultiValueDict()
FILES = datastructures.MultiValueDict()
for submessage in msg.get_payload():
if isinstance(submessage, email.Message.Message):
name_dict = parse_header(submessage['Content-Disposition'])[1]
# name_dict is something like {'name': 'file', 'filename': 'test.txt'} for file uploads
# or {'name': 'blah'} for POST fields
# We assume all uploaded files have a 'filename' set.
if name_dict.has_key('filename'):
assert type([]) != type(submessage.get_payload()), "Nested MIME messages are not supported"
if not name_dict['filename'].strip():
continue
# IE submits the full path, so trim everything but the basename.
# (We can't use os.path.basename because it expects Linux paths.)
filename = name_dict['filename'][name_dict['filename'].rfind("\\")+1:]
FILES.appendlist(name_dict['name'], {
'filename': filename,
'content-type': (submessage.has_key('Content-Type') and submessage['Content-Type'] or None),
'content': submessage.get_payload(),
})
else:
POST.appendlist(name_dict['name'], submessage.get_payload())
return POST, FILES
class QueryDict(datastructures.MultiValueDict):
"""A specialized MultiValueDict that takes a query string when initialized.
This is immutable unless you create a copy of it."""
def __init__(self, query_string):
try:
from mod_python.util import parse_qsl
except ImportError:
from cgi import parse_qsl
if not query_string:
self.data = {}
self._keys = []
else:
self.data = {}
self._keys = []
for name, value in parse_qsl(query_string, True): # keep_blank_values=True
if name in self.data:
self.data[name].append(value)
else:
self.data[name] = [value]
if name not in self._keys:
self._keys.append(name)
self._mutable = False
def __setitem__(self, key, value):
if not self._mutable:
raise AttributeError, "This QueryDict instance is immutable"
else:
self.data[key] = [value]
if not key in self._keys:
self._keys.append(key)
def setlist(self, key, list_):
if not self._mutable:
raise AttributeError, "This QueryDict instance is immutable"
else:
self.data[key] = list_
if not key in self._keys:
self._keys.append(key)
def copy(self):
"Returns a mutable copy of this object"
cp = datastructures.MultiValueDict.copy(self)
cp._mutable = True
return cp
def assert_synchronized(self):
assert(len(self._keys) == len(self.data.keys())), \
"QueryDict data structure is out of sync: %s %s" % (str(self._keys), str(self.data))
def items(self):
"Respect order preserved by self._keys"
self.assert_synchronized()
items = []
for key in self._keys:
if key in self.data:
items.append((key, self.data[key][0]))
return items
def keys(self):
self.assert_synchronized()
return self._keys
def parse_cookie(cookie):
if cookie == '':
return {}
c = SimpleCookie()
c.load(cookie)
cookiedict = {}
for key in c.keys():
cookiedict[key] = c.get(key).value
return cookiedict
class HttpResponse:
"A basic HTTP response, with content and dictionary-accessed headers"
def __init__(self, content='', mimetype=DEFAULT_MIME_TYPE):
self.content = content
self.headers = {'Content-Type':mimetype}
self.cookies = SimpleCookie()
self.status_code = 200
def __str__(self):
"Full HTTP message, including headers"
return '\n'.join(['%s: %s' % (key, value)
for key, value in self.headers.items()]) \
+ '\n\n' + self.content
def __setitem__(self, header, value):
self.headers[header] = value
def __delitem__(self, header):
try:
del self.headers[header]
except KeyError:
pass
def __getitem__(self, header):
return self.headers[header]
def has_header(self, header):
"Case-insensitive check for a header"
header = header.lower()
for key in self.headers.keys():
if key.lower() == header:
return True
return False
def set_cookie(self, key, value='', max_age=None, path='/', domain=None, secure=None):
self.cookies[key] = value
for var in ('max_age', 'path', 'domain', 'secure'):
val = locals()[var]
if val is not None:
self.cookies[key][var.replace('_', '-')] = val
def get_content_as_string(self, encoding):
"""
Returns the content as a string, encoding it from a Unicode object if
necessary.
"""
if isinstance(self.content, unicode):
return self.content.encode(encoding)
return self.content
# The remaining methods partially implement the file-like object interface.
# See http://docs.python.org/lib/bltin-file-objects.html
def write(self, content):
self.content += content
def flush(self):
pass
def tell(self):
return len(self.content)
class HttpResponseRedirect(HttpResponse):
def __init__(self, redirect_to):
HttpResponse.__init__(self)
self['Location'] = redirect_to
self.status_code = 302
class HttpResponseNotModified(HttpResponse):
def __init__(self):
HttpResponse.__init__(self)
self.status_code = 304
class HttpResponseNotFound(HttpResponse):
def __init__(self, content='', mimetype=DEFAULT_MIME_TYPE):
HttpResponse.__init__(self, content, mimetype)
self.status_code = 404
class HttpResponseForbidden(HttpResponse):
def __init__(self, content='', mimetype=DEFAULT_MIME_TYPE):
HttpResponse.__init__(self, content, mimetype)
self.status_code = 403
class HttpResponseGone(HttpResponse):
def __init__(self, content='', mimetype=DEFAULT_MIME_TYPE):
HttpResponse.__init__(self, content, mimetype)
self.status_code = 410
class HttpResponseServerError(HttpResponse):
def __init__(self, content='', mimetype=DEFAULT_MIME_TYPE):
HttpResponse.__init__(self, content, mimetype)
self.status_code = 500
def populate_apache_request(http_response, mod_python_req):
"Populates the mod_python request object with an HttpResponse"
mod_python_req.content_type = http_response['Content-Type'] or DEFAULT_MIME_TYPE
del http_response['Content-Type']
if http_response.cookies:
mod_python_req.headers_out['Set-Cookie'] = http_response.cookies.output(header='')
for key, value in http_response.headers.items():
mod_python_req.headers_out[key] = value
mod_python_req.status = http_response.status_code
mod_python_req.write(http_response.get_content_as_string('utf-8'))

22
django/utils/images.py Normal file
View File

@ -0,0 +1,22 @@
"""
Utility functions for handling images.
Requires PIL, as you might imagine.
"""
import ImageFile
def get_image_dimensions(path):
"""Returns the (width, height) of an image at a given path."""
p = ImageFile.Parser()
fp = open(path)
while 1:
data = fp.read(1024)
if not data:
break
p.feed(data)
if p.image:
return p.image.size
break
fp.close()
return None

42
django/utils/stopwords.py Normal file
View File

@ -0,0 +1,42 @@
# Performance note: I benchmarked this code using a set instead of
# a list for the stopwords and was surprised to find that the list
# performed /better/ than the set - maybe because it's only a small
# list.
stopwords = '''
i
a
an
are
as
at
be
by
for
from
how
in
is
it
of
on
or
that
the
this
to
was
what
when
where
'''.split()
def strip_stopwords(sentence):
"Removes stopwords - also normalizes whitespace"
words = sentence.split()
sentence = []
for word in words:
if word.lower() not in stopwords:
sentence.append(word)
return ' '.join(sentence)

108
django/utils/text.py Normal file
View File

@ -0,0 +1,108 @@
import re
def wrap(text, width):
"""
A word-wrap function that preserves existing line breaks and most spaces in
the text. Expects that existing line breaks are posix newlines (\n).
See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061
"""
return reduce(lambda line, word, width=width: '%s%s%s' %
(line,
' \n'[(len(line[line.rfind('\n')+1:])
+ len(word.split('\n',1)[0]
) >= width)],
word),
text.split(' ')
)
def truncate_words(s, num):
"Truncates a string after a certain number of words."
length = int(num)
words = s.split()
if len(words) > length:
words = words[:length]
if not words[-1].endswith('...'):
words.append('...')
return ' '.join(words)
def get_valid_filename(s):
"""
Returns the given string converted to a string that can be used for a clean
filename. Specifically, leading and trailing spaces are removed; other
spaces are converted to underscores; and all non-filename-safe characters
are removed.
>>> get_valid_filename("john's portrait in 2004.jpg")
'johns_portrait_in_2004.jpg'
"""
s = s.strip().replace(' ', '_')
return re.sub(r'[^-A-Za-z0-9_.]', '', s)
def fix_microsoft_characters(s):
"""
Converts Microsoft proprietary characters (e.g. smart quotes, em-dashes)
to sane characters
"""
# Sources:
# http://stsdas.stsci.edu/bps/pythontalk8.html
# http://www.waider.ie/hacks/workshop/perl/rss-fetch.pl
# http://www.fourmilab.ch/webtools/demoroniser/
return s
s = s.replace('\x91', "'")
s = s.replace('\x92', "'")
s = s.replace('\x93', '"')
s = s.replace('\x94', '"')
s = s.replace('\xd2', '"')
s = s.replace('\xd3', '"')
s = s.replace('\xd5', "'")
s = s.replace('\xad', '--')
s = s.replace('\xd0', '--')
s = s.replace('\xd1', '--')
s = s.replace('\xe2\x80\x98', "'") # weird single quote (open)
s = s.replace('\xe2\x80\x99', "'") # weird single quote (close)
s = s.replace('\xe2\x80\x9c', '"') # weird double quote (open)
s = s.replace('\xe2\x80\x9d', '"') # weird double quote (close)
s = s.replace('\xe2\x81\x84', '/')
s = s.replace('\xe2\x80\xa6', '...')
s = s.replace('\xe2\x80\x94', '--')
return s
def get_text_list(list_, last_word='or'):
"""
>>> get_text_list(['a', 'b', 'c', 'd'])
'a, b, c or d'
>>> get_text_list(['a', 'b', 'c'], 'and')
'a, b and c'
>>> get_text_list(['a', 'b'], 'and')
'a and b'
>>> get_text_list(['a'])
'a'
>>> get_text_list([])
''
"""
if len(list_) == 0: return ''
if len(list_) == 1: return list_[0]
return '%s %s %s' % (', '.join([i for i in list_][:-1]), last_word, list_[-1])
def normalize_newlines(text):
return re.sub(r'\r\n|\r|\n', '\n', text)
def recapitalize(text):
"Recapitalizes text, placing caps after end-of-sentence punctuation."
capwords = 'I Jayhawk Jayhawks Lawrence Kansas KS'.split()
text = text.lower()
capsRE = re.compile(r'(?:^|(?<=[\.\?\!] ))([a-z])')
text = capsRE.sub(lambda x: x.group(1).upper(), text)
for capword in capwords:
capwordRE = re.compile(r'\b%s\b' % capword, re.I)
text = capwordRE.sub(capword, text)
return text
def phone2numeric(phone):
"Converts a phone number with letters into its numeric equivalent."
letters = re.compile(r'[A-PR-Y]', re.I)
char2number = lambda m: {'a': '2', 'c': '2', 'b': '2', 'e': '3',
'd': '3', 'g': '4', 'f': '3', 'i': '4', 'h': '4', 'k': '5',
'j': '5', 'm': '6', 'l': '5', 'o': '6', 'n': '6', 'p': '7',
's': '7', 'r': '7', 'u': '8', 't': '8', 'w': '9', 'v': '8',
'y': '9', 'x': '9'}.get(m.group(0).lower())
return letters.sub(char2number, phone)

46
django/utils/timesince.py Normal file
View File

@ -0,0 +1,46 @@
import time, math, datetime
def timesince(d, now=None):
"""
Takes a datetime object, returns the time between then and now
as a nicely formatted string, e.g "10 minutes"
Adapted from http://blog.natbat.co.uk/archive/2003/Jun/14/time_since
"""
original = time.mktime(d.timetuple())
chunks = (
(60 * 60 * 24 * 365, 'year'),
(60 * 60 * 24 * 30, 'month'),
(60 * 60 * 24, 'day'),
(60 * 60, 'hour'),
(60, 'minute')
)
if not now:
now = time.time()
since = now - original
# Crazy iteration syntax because we need i to be current index
for i, (seconds, name) in zip(range(len(chunks)), chunks):
count = math.floor(since / seconds)
if count != 0:
break
if count == 1:
s = '1 %s' % name
else:
s = '%d %ss' % (count, name)
if i + 1 < len(chunks):
# Now get the second item
seconds2, name2 = chunks[i + 1]
count2 = math.floor((since - (seconds * count)) / seconds2)
if count2 != 0:
if count2 == 1:
s += ', 1 %s' % name2
else:
s += ', %d %ss' % (count2, name2)
return s
def timeuntil(d):
"""
Like timesince, but returns a string measuring the time until
the given time.
"""
now = datetime.datetime.now()
return timesince(now, time.mktime(d.timetuple()))

13
django/utils/xmlutils.py Normal file
View File

@ -0,0 +1,13 @@
"""
Utilities for XML generation/parsing.
"""
from xml.sax.saxutils import XMLGenerator
class SimplerXMLGenerator(XMLGenerator):
def addQuickElement(self, name, contents=None, attrs={}):
"Convenience method for adding an element with no children"
self.startElement(name, attrs)
if contents is not None:
self.characters(contents)
self.endElement(name)

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

View File

328
django/views/admin/doc.py Normal file
View File

@ -0,0 +1,328 @@
import os
import re
import inspect
from django.core import meta
from django import templatetags
from django.conf import settings
from django.models.core import sites
from django.views.decorators.cache import cache_page
from django.core.extensions import CMSContext as Context
from django.core.exceptions import Http404, ViewDoesNotExist
from django.utils.httpwrappers import HttpResponse, HttpResponseRedirect
from django.core import template, template_loader, defaulttags, defaultfilters, urlresolvers
try:
from django.parts.admin import doc
except ImportError:
doc = None
# Exclude methods starting with these strings from documentation
MODEL_METHODS_EXCLUDE = ('_', 'add_', 'delete', 'save', 'set_')
def doc_index(request):
if not doc:
return missing_docutils_page(request)
t = template_loader.get_template('doc/index')
c = Context(request, {})
return HttpResponse(t.render(c))
def bookmarklets(request):
t = template_loader.get_template('doc/bookmarklets')
c = Context(request, {
'admin_url' : "%s://%s" % (os.environ.get('HTTPS') == 'on' and 'https' or 'http', request.META['HTTP_HOST']),
})
return HttpResponse(t.render(c))
def template_tag_index(request):
if not doc:
return missing_docutils_page(request)
# We have to jump through some hoops with registered_tags to make sure
# they don't get messed up by loading outside tagsets
saved_tagset = template.registered_tags.copy(), template.registered_filters.copy()
load_all_installed_template_libraries()
# Gather docs
tags = []
for tagname in template.registered_tags:
title, body, metadata = doc.parse_docstring(template.registered_tags[tagname].__doc__)
if title:
title = doc.parse_rst(title, 'tag', 'tag:' + tagname)
if body:
body = doc.parse_rst(body, 'tag', 'tag:' + tagname)
for key in metadata:
metadata[key] = doc.parse_rst(metadata[key], 'tag', 'tag:' + tagname)
library = template.registered_tags[tagname].__module__.split('.')[-1]
if library == 'template_loader' or library == 'defaulttags':
library = None
tags.append({
'name' : tagname,
'title' : title,
'body' : body,
'meta' : metadata,
'library' : library,
})
# Fix registered_tags
template.registered_tags, template.registered_filters = saved_tagset
t = template_loader.get_template('doc/template_tag_index')
c = Context(request, {
'tags' : tags,
})
return HttpResponse(t.render(c))
template_tag_index = cache_page(template_tag_index, 15*60)
def template_filter_index(request):
if not doc:
return missing_docutils_page(request)
saved_tagset = template.registered_tags.copy(), template.registered_filters.copy()
load_all_installed_template_libraries()
filters = []
for filtername in template.registered_filters:
title, body, metadata = doc.parse_docstring(template.registered_filters[filtername][0].__doc__)
if title:
title = doc.parse_rst(title, 'filter', 'filter:' + filtername)
if body:
body = doc.parse_rst(body, 'filter', 'filter:' + filtername)
for key in metadata:
metadata[key] = doc.parse_rst(metadata[key], 'filter', 'filter:' + filtername)
metadata['AcceptsArgument'] = template.registered_filters[filtername][1]
library = template.registered_filters[filtername][0].__module__.split('.')[-1]
if library == 'template_loader' or library == 'defaultfilters':
library = None
filters.append({
'name' : filtername,
'title' : title,
'body' : body,
'meta' : metadata,
'library' : library,
})
template.registered_tags, template.registered_filters = saved_tagset
t = template_loader.get_template('doc/template_filter_index')
c = Context(request, {
'filters' : filters,
})
return HttpResponse(t.render(c))
template_filter_index = cache_page(template_filter_index, 15*60)
def view_index(request):
if not doc:
return missing_docutils_page(request)
views = []
for site_settings_module in settings.ADMIN_FOR:
settings_mod = __import__(site_settings_module, '', '', [''])
urlconf = __import__(settings_mod.ROOT_URLCONF, '', '', [''])
view_functions = extract_views_from_urlpatterns(urlconf.urlpatterns)
for (func, regex) in view_functions:
title, body, metadata = doc.parse_docstring(func.__doc__)
if title:
title = doc.parse_rst(title, 'view', 'view:' + func.__name__)
views.append({
'name' : func.__name__,
'module' : func.__module__,
'title' : title,
'site_id': settings_mod.SITE_ID,
'site' : sites.get_object(id__exact=settings_mod.SITE_ID),
'url' : simplify_regex(regex),
})
t = template_loader.get_template('doc/view_index')
c = Context(request, {
'views' : views,
})
return HttpResponse(t.render(c))
view_index = cache_page(view_index, 15*60)
def view_detail(request, view):
if not doc:
return missing_docutils_page(request)
mod, func = urlresolvers.get_mod_func(view)
try:
view_func = getattr(__import__(mod, '', '', ['']), func)
except (ImportError, AttributeError):
raise Http404
title, body, metadata = doc.parse_docstring(view_func.__doc__)
if title:
title = doc.parse_rst(title, 'view', 'view:' + view)
if body:
body = doc.parse_rst(body, 'view', 'view:' + view)
for key in metadata:
metadata[key] = doc.parse_rst(metadata[key], 'view', 'view:' + view)
t = template_loader.get_template('doc/view_detail')
c = Context(request, {
'name' : view,
'summary' : title,
'body' : body,
'meta' : metadata,
})
return HttpResponse(t.render(c))
def model_index(request):
if not doc:
return missing_docutils_page(request)
models = []
for app in meta.get_installed_model_modules():
for model in app._MODELS:
opts = model._meta
models.append({
'name' : '%s.%s' % (opts.app_label, opts.module_name),
'module' : opts.app_label,
'class' : opts.module_name,
})
t = template_loader.get_template('doc/model_index')
c = Context(request, {
'models' : models,
})
return HttpResponse(t.render(c))
def model_detail(request, model):
if not doc:
return missing_docutils_page(request)
try:
model = meta.get_app(model)
except ImportError:
raise Http404
opts = model.Klass._meta
# Gather fields/field descriptions
fields = []
for field in opts.fields:
fields.append({
'name' : field.name,
'data_type': get_readable_field_data_type(field),
'verbose' : field.verbose_name,
'help' : field.help_text,
})
for func_name, func in model.Klass.__dict__.items():
if callable(func) and len(inspect.getargspec(func)[0]) == 0:
try:
for exclude in MODEL_METHODS_EXCLUDE:
if func_name.startswith(exclude):
raise StopIteration
except StopIteration:
continue
verbose = func.__doc__
if verbose:
verbose = doc.parse_rst(doc.trim_docstring(verbose), 'model', 'model:' + opts.module_name)
fields.append({
'name' : func_name,
'data_type' : get_return_data_type(func_name),
'verbose' : verbose,
})
t = template_loader.get_template('doc/model_detail')
c = Context(request, {
'name' : '%s.%s' % (opts.app_label, opts.module_name),
'summary' : "Fields on %s objects" % opts.verbose_name,
'fields' : fields,
})
return HttpResponse(t.render(c))
####################
# Helper functions #
####################
def missing_docutils_page(request):
"""Display an error message for people without docutils"""
t = template_loader.get_template('doc/missing_docutils')
c = Context(request, {})
return HttpResponse(t.render(c))
def load_all_installed_template_libraries():
# Clear out and reload default tags
template.registered_tags.clear()
reload(defaulttags)
reload(template_loader) # template_loader defines the block/extends tags
# Load any template tag libraries from installed apps
for e in templatetags.__path__:
libraries = [os.path.splitext(p)[0] for p in os.listdir(e) if p.endswith('.py') and p[0].isalpha()]
for lib in libraries:
try:
mod = defaulttags.LoadNode.load_taglib(lib)
reload(mod)
except ImportError:
pass
def get_return_data_type(func_name):
"""Return a somewhat-helpful data type given a function name"""
if func_name.startswith('get_'):
if func_name.endswith('_list'):
return 'List'
elif func_name.endswith('_count'):
return 'Integer'
return ''
# Maps Field objects to their human-readable data 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_TYPE_MAPPING = {
'AutoField' : 'Integer',
'BooleanField' : 'Boolean (Either True or False)',
'CharField' : 'String (up to %(maxlength)s)',
'CommaSeparatedIntegerField': 'Comma-separated integers',
'DateField' : 'Date (without time)',
'DateTimeField' : 'Date (with time)',
'EmailField' : 'E-mail address',
'FileField' : 'File path',
'FloatField' : 'Decimal number',
'ImageField' : 'File path',
'IntegerField' : 'Integer',
'IPAddressField' : 'IP address',
'ManyToManyField' : '',
'NullBooleanField' : 'Boolean (Either True, False or None)',
'PhoneNumberField' : 'Phone number',
'PositiveIntegerField' : 'Integer',
'PositiveSmallIntegerField' : 'Integer',
'SlugField' : 'String (up to 50)',
'SmallIntegerField' : 'Integer',
'TextField' : 'Text',
'TimeField' : 'Time',
'URLField' : 'URL',
'USStateField' : 'U.S. state (two uppercase letters)',
'XMLField' : 'XML text',
}
def get_readable_field_data_type(field):
return DATA_TYPE_MAPPING[field.__class__.__name__] % field.__dict__
def extract_views_from_urlpatterns(urlpatterns, base=''):
"""
Return a list of views from a list of urlpatterns.
Each object in the returned list is a two-tuple: (view_func, regex)
"""
views = []
for p in urlpatterns:
if hasattr(p, 'get_callback'):
try:
views.append((p.get_callback(), base + p.regex.pattern))
except ViewDoesNotExist:
continue
elif hasattr(p, 'get_url_patterns'):
views.extend(extract_views_from_urlpatterns(p.get_url_patterns(), base + p.regex.pattern))
else:
raise TypeError, "%s does not appear to be a urlpattern object" % p
return views
# Clean up urlpattern regexes into something somewhat readable by Mere Humans:
# turns something like "^(?P<sport_slug>\w+)/athletes/(?P<athlete_slug>\w+)/$"
# into "<sport_slug>/athletes/<athlete_slug>/"
named_group_matcher = re.compile(r'\(\?P(<\w+>).+?\)')
def simplify_regex(pattern):
pattern = named_group_matcher.sub(lambda m: m.group(1), pattern)
pattern = pattern.replace('^', '').replace('$', '').replace('?', '').replace('//', '/')
if not pattern.startswith('/'):
pattern = '/' + pattern
return pattern

1089
django/views/admin/main.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,70 @@
from django.core import formfields, template_loader, validators
from django.core import template
from django.core.extensions import CMSContext as Context
from django.utils.httpwrappers import HttpResponse
from django.models.core import sites
from django.conf import settings
def template_validator(request):
"""
Displays the template validator form, which finds and displays template
syntax errors.
"""
# get a dict of {site_id : settings_module} for the validator
settings_modules = {}
for mod in settings.ADMIN_FOR:
settings_module = __import__(mod, '', '', [''])
settings_modules[settings_module.SITE_ID] = settings_module
manipulator = TemplateValidator(settings_modules)
new_data, errors = {}, {}
if request.POST:
new_data = request.POST.copy()
errors = manipulator.get_validation_errors(new_data)
if not errors:
request.user.add_message('The template is valid.')
t = template_loader.get_template('template_validator')
c = Context(request, {
'title': 'Template validator',
'form': formfields.FormWrapper(manipulator, new_data, errors),
})
return HttpResponse(t.render(c))
class TemplateValidator(formfields.Manipulator):
def __init__(self, settings_modules):
self.settings_modules = settings_modules
site_list = sites.get_in_bulk(settings_modules.keys()).values()
self.fields = (
formfields.SelectField('site', is_required=True, choices=[(s.id, s.name) for s in site_list]),
formfields.LargeTextField('template', is_required=True, rows=25, validator_list=[self.isValidTemplate]),
)
def isValidTemplate(self, field_data, all_data):
# get the settings module
# if the site isn't set, we don't raise an error since the site field will
try:
site_id = int(all_data.get('site', None))
except (ValueError, TypeError):
return
settings_module = self.settings_modules.get(site_id, None)
if settings_module is None:
return
# so that inheritance works in the site's context, register a new function
# for "extends" that uses the site's TEMPLATE_DIR instead
def new_do_extends(parser, token):
node = template_loader.do_extends(parser, token)
node.template_dirs = settings_module.TEMPLATE_DIRS
return node
template.register_tag('extends', new_do_extends)
# now validate the template using the new template dirs
# making sure to reset the extends function in any case
error = None
try:
tmpl = template_loader.get_template_from_string(field_data)
tmpl.render(template.Context({}))
except template.TemplateSyntaxError, e:
error = e
template.register_tag('extends', template_loader.do_extends)
if error:
raise validators.ValidationError, e.args

View File

View File

@ -0,0 +1,62 @@
from django.parts.auth.formfields import AuthenticationForm
from django.core import formfields, template_loader
from django.core.extensions import CMSContext as Context
from django.models.auth import sessions
from django.models.core import sites
from django.utils.httpwrappers import HttpResponse, HttpResponseRedirect
REDIRECT_FIELD_NAME = 'next'
def login(request):
"Displays the login form and handles the login action."
manipulator = AuthenticationForm(request)
redirect_to = request.REQUEST.get(REDIRECT_FIELD_NAME, '')
if request.POST:
errors = manipulator.get_validation_errors(request.POST)
if not errors:
# Light security check -- make sure redirect_to isn't garbage.
if not redirect_to or '://' in redirect_to or ' ' in redirect_to:
redirect_to = '/accounts/profile/'
response = HttpResponseRedirect(redirect_to)
sessions.start_web_session(manipulator.get_user_id(), request, response)
return response
else:
errors = {}
response = HttpResponse()
# Set this cookie as a test to see whether the user accepts cookies
response.set_cookie(sessions.TEST_COOKIE_NAME, sessions.TEST_COOKIE_VALUE)
t = template_loader.get_template('registration/login')
c = Context(request, {
'form': formfields.FormWrapper(manipulator, request.POST, errors),
REDIRECT_FIELD_NAME: redirect_to,
'site_name': sites.get_current().name,
})
response.write(t.render(c))
return response
def logout(request):
"Logs out the user and displays 'You are logged you' message."
if request.session:
# Do a redirect to this page until the session has been cleared.
response = HttpResponseRedirect(request.path)
# Delete the cookie by setting a cookie with an empty value and max_age=0
response.set_cookie(request.session.get_cookie()[0], '', max_age=0)
request.session.delete()
return response
else:
t = template_loader.get_template('registration/logged_out')
c = Context(request)
return HttpResponse(t.render(c))
def logout_then_login(request):
"Logs out the user if he is logged in. Then redirects to the log-in page."
response = HttpResponseRedirect('/accounts/login/')
if request.session:
# Delete the cookie by setting a cookie with an empty value and max_age=0
response.set_cookie(request.session.get_cookie()[0], '', max_age=0)
request.session.delete()
return response
def redirect_to_login(next):
"Redirects the user to the login page, passing the given 'next' page"
return HttpResponseRedirect('/accounts/login/?%s=%s' % (REDIRECT_FIELD_NAME, next))

View File

View File

@ -0,0 +1,347 @@
from django.core import formfields, template_loader, validators
from django.core.mail import mail_admins, mail_managers
from django.core.exceptions import Http404, ObjectDoesNotExist
from django.core.extensions import CMSContext as Context
from django.models.auth import sessions
from django.models.comments import comments, freecomments
from django.models.core import contenttypes
from django.parts.auth.formfields import AuthenticationForm
from django.utils.httpwrappers import HttpResponse, HttpResponseRedirect
from django.utils.text import normalize_newlines
from django.conf.settings import BANNED_IPS, COMMENTS_ALLOW_PROFANITIES, COMMENTS_SKETCHY_USERS_GROUP, COMMENTS_FIRST_FEW, SITE_ID
import base64, datetime
COMMENTS_PER_PAGE = 20
class PublicCommentManipulator(AuthenticationForm):
"Manipulator that handles public registered comments"
def __init__(self, user, ratings_required, ratings_range, num_rating_choices):
AuthenticationForm.__init__(self)
self.ratings_range, self.num_rating_choices = ratings_range, num_rating_choices
choices = [(c, c) for c in ratings_range]
def get_validator_list(rating_num):
if rating_num <= num_rating_choices:
return [validators.RequiredIfOtherFieldsGiven(['rating%d' % i for i in range(1, 9) if i != rating_num], "This rating is required because you've entered at least one other rating.")]
else:
return []
self.fields.extend([
formfields.LargeTextField(field_name="comment", maxlength=3000, is_required=True,
validator_list=[self.hasNoProfanities]),
formfields.RadioSelectField(field_name="rating1", choices=choices,
is_required=ratings_required and num_rating_choices > 0,
validator_list=get_validator_list(1),
),
formfields.RadioSelectField(field_name="rating2", choices=choices,
is_required=ratings_required and num_rating_choices > 1,
validator_list=get_validator_list(2),
),
formfields.RadioSelectField(field_name="rating3", choices=choices,
is_required=ratings_required and num_rating_choices > 2,
validator_list=get_validator_list(3),
),
formfields.RadioSelectField(field_name="rating4", choices=choices,
is_required=ratings_required and num_rating_choices > 3,
validator_list=get_validator_list(4),
),
formfields.RadioSelectField(field_name="rating5", choices=choices,
is_required=ratings_required and num_rating_choices > 4,
validator_list=get_validator_list(5),
),
formfields.RadioSelectField(field_name="rating6", choices=choices,
is_required=ratings_required and num_rating_choices > 5,
validator_list=get_validator_list(6),
),
formfields.RadioSelectField(field_name="rating7", choices=choices,
is_required=ratings_required and num_rating_choices > 6,
validator_list=get_validator_list(7),
),
formfields.RadioSelectField(field_name="rating8", choices=choices,
is_required=ratings_required and num_rating_choices > 7,
validator_list=get_validator_list(8),
),
])
if not user.is_anonymous():
self["username"].is_required = False
self["username"].validator_list = []
self["password"].is_required = False
self["password"].validator_list = []
self.user_cache = user
def hasNoProfanities(self, field_data, all_data):
if COMMENTS_ALLOW_PROFANITIES:
return
return validators.hasNoProfanities(field_data, all_data)
def get_comment(self, new_data):
"Helper function"
return comments.Comment(None, self.get_user_id(), new_data["content_type_id"],
new_data["object_id"], new_data.get("headline", "").strip(),
new_data["comment"].strip(), new_data.get("rating1", None),
new_data.get("rating2", None), new_data.get("rating3", None),
new_data.get("rating4", None), new_data.get("rating5", None),
new_data.get("rating6", None), new_data.get("rating7", None),
new_data.get("rating8", None), new_data.get("rating1", None) is not None,
datetime.datetime.now(), new_data["is_public"], new_data["ip_address"], False, SITE_ID)
def save(self, new_data):
today = datetime.date.today()
c = self.get_comment(new_data)
for old in comments.get_list(content_type_id__exact=new_data["content_type_id"],
object_id__exact=new_data["object_id"], user_id__exact=self.get_user_id()):
# Check that this comment isn't duplicate. (Sometimes people post
# comments twice by mistake.) If it is, fail silently by pretending
# the comment was posted successfully.
if old.submit_date.date() == today and old.comment == c.comment \
and old.rating1 == c.rating1 and old.rating2 == c.rating2 \
and old.rating3 == c.rating3 and old.rating4 == c.rating4 \
and old.rating5 == c.rating5 and old.rating6 == c.rating6 \
and old.rating7 == c.rating7 and old.rating8 == c.rating8:
return old
# If the user is leaving a rating, invalidate all old ratings.
if c.rating1 is not None:
old.valid_rating = False
old.save()
c.save()
# If the commentor has posted fewer than COMMENTS_FIRST_FEW comments,
# send the comment to the managers.
if self.user_cache.get_comments_comment_count() <= COMMENTS_FIRST_FEW:
message = 'This comment was posted by a user who has posted fewer than %s comments:\n\n%s' % \
(COMMENTS_FIRST_FEW, c.get_as_text())
mail_managers("Comment posted by rookie user", message)
if COMMENTS_SKETCHY_USERS_GROUP and COMMENTS_SKETCHY_USERS_GROUP in [g.id for g in self.user_cache.get_groups()]:
message = 'This comment was posted by a sketchy user:\n\n%s' % c.get_as_text()
mail_managers("Comment posted by sketchy user (%s)" % self.user_cache.username, c.get_as_text())
return c
class PublicFreeCommentManipulator(formfields.Manipulator):
"Manipulator that handles public free (unregistered) comments"
def __init__(self):
self.fields = (
formfields.TextField(field_name="person_name", maxlength=50, is_required=True,
validator_list=[self.hasNoProfanities]),
formfields.LargeTextField(field_name="comment", maxlength=3000, is_required=True,
validator_list=[self.hasNoProfanities]),
)
def hasNoProfanities(self, field_data, all_data):
if COMMENTS_ALLOW_PROFANITIES:
return
return validators.hasNoProfanities(field_data, all_data)
def get_comment(self, new_data):
"Helper function"
return freecomments.FreeComment(None, new_data["content_type_id"],
new_data["object_id"], new_data["comment"].strip(),
new_data["person_name"].strip(), datetime.datetime.now(), new_data["is_public"],
new_data["ip_address"], False, SITE_ID)
def save(self, new_data):
today = datetime.date.today()
c = self.get_comment(new_data)
# Check that this comment isn't duplicate. (Sometimes people post
# comments twice by mistake.) If it is, fail silently by pretending
# the comment was posted successfully.
for old_comment in freecomments.get_list(content_type_id__exact=new_data["content_type_id"],
object_id__exact=new_data["object_id"], person_name__exact=new_data["person_name"],
submit_date__year=today.year, submit_date__month=today.month,
submit_date__day=today.day):
if old_comment.comment == c.comment:
return old_comment
c.save()
return c
def post_comment(request):
"""
Post a comment
Redirects to the `comments.comments.comment_was_posted` view upon success.
Templates: `comment_preview`
Context:
comment
the comment being posted
comment_form
the comment form
options
comment options
target
comment target
hash
security hash (must be included in a posted form to succesfully
post a comment).
rating_options
comment ratings options
ratings_optional
are ratings optional?
ratings_required
are ratings required?
rating_range
range of ratings
rating_choices
choice of ratings
"""
if not request.POST:
raise Http404, "Only POSTs are allowed"
try:
options, target, security_hash = request.POST['options'], request.POST['target'], request.POST['gonzo']
except KeyError:
raise Http404, "One or more of the required fields wasn't submitted"
photo_options = request.POST.get('photo_options', '')
rating_options = normalize_newlines(request.POST.get('rating_options', ''))
if comments.get_security_hash(options, photo_options, rating_options, target) != security_hash:
raise Http404, "Somebody tampered with the comment form (security violation)"
# Now we can be assured the data is valid.
if rating_options:
rating_range, rating_choices = comments.get_rating_options(base64.decodestring(rating_options))
else:
rating_range, rating_choices = [], []
content_type_id, object_id = target.split(':') # target is something like '52:5157'
try:
obj = contenttypes.get_object(id__exact=content_type_id).get_object_for_this_type(id__exact=object_id)
except ObjectDoesNotExist:
raise Http404, "The comment form had an invalid 'target' parameter -- the object ID was invalid"
option_list = options.split(',') # options is something like 'pa,ra'
new_data = request.POST.copy()
new_data['content_type_id'] = content_type_id
new_data['object_id'] = object_id
new_data['ip_address'] = request.META['REMOTE_ADDR']
new_data['is_public'] = comments.IS_PUBLIC in option_list
response = HttpResponse()
manipulator = PublicCommentManipulator(request.user,
ratings_required=comments.RATINGS_REQUIRED in option_list,
ratings_range=rating_range,
num_rating_choices=len(rating_choices))
errors = manipulator.get_validation_errors(new_data)
# If user gave correct username/password and wasn't already logged in, log them in
# so they don't have to enter a username/password again.
if manipulator.get_user() and new_data.has_key('password') and manipulator.get_user().check_password(new_data['password']):
sessions.start_web_session(manipulator.get_user_id(), request, response)
if errors or request.POST.has_key('preview'):
class CommentFormWrapper(formfields.FormWrapper):
def __init__(self, manipulator, new_data, errors, rating_choices):
formfields.FormWrapper.__init__(self, manipulator, new_data, errors)
self.rating_choices = rating_choices
def ratings(self):
field_list = [self['rating%d' % (i+1)] for i in range(len(rating_choices))]
for i, f in enumerate(field_list):
f.choice = rating_choices[i]
return field_list
comment = errors and '' or manipulator.get_comment(new_data)
comment_form = CommentFormWrapper(manipulator, new_data, errors, rating_choices)
t = template_loader.get_template('comments/preview')
c = Context(request, {
'comment': comment,
'comment_form': comment_form,
'options': options,
'target': target,
'hash': security_hash,
'rating_options': rating_options,
'ratings_optional': comments.RATINGS_OPTIONAL in option_list,
'ratings_required': comments.RATINGS_REQUIRED in option_list,
'rating_range': rating_range,
'rating_choices': rating_choices,
})
elif request.POST.has_key('post'):
# If the IP is banned, mail the admins, do NOT save the comment, and
# serve up the "Thanks for posting" page as if the comment WAS posted.
if request.META['REMOTE_ADDR'] in BANNED_IPS:
mail_admins("Banned IP attempted to post comment", str(request.POST) + "\n\n" + str(request.META))
else:
manipulator.do_html2python(new_data)
comment = manipulator.save(new_data)
return HttpResponseRedirect("/comments/posted/?c=%s:%s" % (content_type_id, object_id))
else:
raise Http404, "The comment form didn't provide either 'preview' or 'post'"
response.write(t.render(c))
return response
def post_free_comment(request):
"""
Post a free comment (not requiring a log in)
Redirects to `comments.comments.comment_was_posted` view on success.
Templates: `comment_free_preview`
Context:
comment
comment being posted
comment_form
comment form object
options
comment options
target
comment target
hash
security hash (must be included in a posted form to succesfully
post a comment).
"""
if not request.POST:
raise Http404, "Only POSTs are allowed"
try:
options, target, security_hash = request.POST['options'], request.POST['target'], request.POST['gonzo']
except KeyError:
raise Http404, "One or more of the required fields wasn't submitted"
if comments.get_security_hash(options, '', '', target) != security_hash:
raise Http404, "Somebody tampered with the comment form (security violation)"
content_type_id, object_id = target.split(':') # target is something like '52:5157'
content_type = contenttypes.get_object(id__exact=content_type_id)
try:
obj = content_type.get_object_for_this_type(id__exact=object_id)
except ObjectDoesNotExist:
raise Http404, "The comment form had an invalid 'target' parameter -- the object ID was invalid"
option_list = options.split(',')
new_data = request.POST.copy()
new_data['content_type_id'] = content_type_id
new_data['object_id'] = object_id
new_data['ip_address'] = request.META['REMOTE_ADDR']
new_data['is_public'] = comments.IS_PUBLIC in option_list
response = HttpResponse()
manipulator = PublicFreeCommentManipulator()
errors = manipulator.get_validation_errors(new_data)
if errors or request.POST.has_key('preview'):
comment = errors and '' or manipulator.get_comment(new_data)
t = template_loader.get_template('comments/free_preview')
c = Context(request, {
'comment': comment,
'comment_form': formfields.FormWrapper(manipulator, new_data, errors),
'options': options,
'target': target,
'hash': security_hash,
})
elif request.POST.has_key('post'):
# If the IP is banned, mail the admins, do NOT save the comment, and
# serve up the "Thanks for posting" page as if the comment WAS posted.
if request.META['REMOTE_ADDR'] in BANNED_IPS:
from django.core.mail import mail_admins
mail_admins("Practical joker", str(request.POST) + "\n\n" + str(request.META))
else:
manipulator.do_html2python(new_data)
comment = manipulator.save(new_data)
return HttpResponseRedirect("/comments/posted/?c=%s:%s" % (content_type_id, object_id))
else:
raise Http404, "The comment form didn't provide either 'preview' or 'post'"
response.write(t.render(c))
return response
def comment_was_posted(request):
"""
Display "comment was posted" success page
Templates: `comment_posted`
Context:
object
The object the comment was posted on
"""
obj = None
if request.GET.has_key('c'):
content_type_id, object_id = request.GET['c'].split(':')
try:
content_type = contenttypes.get_object(id__exact=content_type_id)
obj = content_type.get_object_for_this_type(id__exact=object_id)
except ObjectDoesNotExist:
pass
t = template_loader.get_template('comments/posted')
c = Context(request, {
'object': obj,
})
return HttpResponse(t.render(c))

View File

@ -0,0 +1,34 @@
from django.core import template_loader
from django.core.extensions import CMSContext as Context
from django.core.exceptions import Http404
from django.models.comments import comments, karma
from django.utils.httpwrappers import HttpResponse
def vote(request, comment_id, vote):
"""
Rate a comment (+1 or -1)
Templates: `karma_vote_accepted`
Context:
comment
`comments.comments` object being rated
"""
rating = {'up': 1, 'down': -1}.get(vote, False)
if not rating:
raise Http404, "Invalid vote"
if request.user.is_anonymous():
raise Http404, "Anonymous users cannot vote"
try:
comment = comments.get_object(id__exact=comment_id)
except comments.CommentDoesNotExist:
raise Http404, "Invalid comment ID"
if comment.user_id == request.user.id:
raise Http404, "No voting for yourself"
karma.vote(request.user.id, comment_id, rating)
# Reload comment to ensure we have up to date karma count
comment = comments.get_object(id__exact=comment_id)
t = template_loader.get_template('comments/karma_vote_accepted')
c = Context(request, {
'comment': comment
})
return HttpResponse(t.render(c))

Some files were not shown because too many files have changed in this diff Show More