+ {%% 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('
%s
' % 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('
')
+ 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;"
diff --git a/django/bin/profiling/__init__.py b/django/bin/profiling/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/django/bin/profiling/gather_profile_stats.py b/django/bin/profiling/gather_profile_stats.py
new file mode 100644
index 0000000000..852f16229d
--- /dev/null
+++ b/django/bin/profiling/gather_profile_stats.py
@@ -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])
diff --git a/django/bin/profiling/handler.py b/django/bin/profiling/handler.py
new file mode 100644
index 0000000000..8a7512b079
--- /dev/null
+++ b/django/bin/profiling/handler.py
@@ -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 ..prof where is
+ the request path with "/" replaced by ".", and 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)
diff --git a/django/bin/setup.py b/django/bin/setup.py
new file mode 100644
index 0000000000..086be541a0
--- /dev/null
+++ b/django/bin/setup.py
@@ -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',
+)
diff --git a/django/bin/validate.py b/django/bin/validate.py
new file mode 100644
index 0000000000..f0c37d01cb
--- /dev/null
+++ b/django/bin/validate.py
@@ -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)
diff --git a/django/conf/__init__.py b/django/conf/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/django/conf/app_template/__init__.py b/django/conf/app_template/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/django/conf/app_template/models/__init__.py b/django/conf/app_template/models/__init__.py
new file mode 100644
index 0000000000..502a7d0738
--- /dev/null
+++ b/django/conf/app_template/models/__init__.py
@@ -0,0 +1 @@
+__all__ = ['{{ app_name }}']
diff --git a/django/conf/app_template/models/app_name.py b/django/conf/app_template/models/app_name.py
new file mode 100644
index 0000000000..6fce302e01
--- /dev/null
+++ b/django/conf/app_template/models/app_name.py
@@ -0,0 +1,3 @@
+from django.core import meta
+
+# Create your models here.
diff --git a/django/conf/app_template/urls/__init__.py b/django/conf/app_template/urls/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/django/conf/app_template/urls/app_name.py b/django/conf/app_template/urls/app_name.py
new file mode 100644
index 0000000000..de814a56d1
--- /dev/null
+++ b/django/conf/app_template/urls/app_name.py
@@ -0,0 +1,5 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('{{ project_name }}.apps.{{ app_name }}.views',
+# (r'', ''),
+)
diff --git a/django/conf/app_template/views/__init__.py b/django/conf/app_template/views/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
new file mode 100644
index 0000000000..871ef95a93
--- /dev/null
+++ b/django/conf/global_settings.py
@@ -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'
diff --git a/django/conf/project_template/__init__.py b/django/conf/project_template/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/django/conf/project_template/apps/__init__.py b/django/conf/project_template/apps/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/django/conf/project_template/settings/__init__.py b/django/conf/project_template/settings/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/django/conf/project_template/settings/main.py b/django/conf/project_template/settings/main.py
new file mode 100644
index 0000000000..3db0e7961c
--- /dev/null
+++ b/django/conf/project_template/settings/main.py
@@ -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 = (
+)
diff --git a/django/conf/settings.py b/django/conf/settings.py
new file mode 100644
index 0000000000..dda46dba2f
--- /dev/null
+++ b/django/conf/settings.py
@@ -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
+
diff --git a/django/conf/urls/__init__.py b/django/conf/urls/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/django/conf/urls/admin.py b/django/conf/urls/admin.py
new file mode 100644
index 0000000000..92d5d1bc9e
--- /dev/null
+++ b/django/conf/urls/admin.py
@@ -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[^/]+)/$', 'django.views.admin.doc.view_detail'),
+ ('^doc/models/$', 'django.views.admin.doc.model_index'),
+ ('^doc/models/(?P[^/]+)/$', 'django.views.admin.doc.model_detail'),
+)
+
+if 'ellington.events' in INSTALLED_APPS:
+ urlpatterns += (
+ ("^events/usersubmittedevents/(?P\d+)/$", 'ellington.events.views.admin.user_submitted_event_change_stage'),
+ ("^events/usersubmittedevents/(?P\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
', '')
+ for d in DOTS:
+ s = s.replace('
%s' % d, '
')
+ return '
\n%s\n
' % s
+ text = HARD_CODED_BULLETS.sub(replace_p_tags, text)
+ # Remove stuff like "
", but only if it's at the bottom of the text.
+ text = TRAILING_EMPTY_CONTENT.sub('', text)
+ return text
+
diff --git a/django/utils/httpwrappers.py b/django/utils/httpwrappers.py
new file mode 100644
index 0000000000..513a5bc0d7
--- /dev/null
+++ b/django/utils/httpwrappers.py
@@ -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 '' % \
+ (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 '' % \
+ (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'))
diff --git a/django/utils/images.py b/django/utils/images.py
new file mode 100644
index 0000000000..75424f16a2
--- /dev/null
+++ b/django/utils/images.py
@@ -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
diff --git a/django/utils/stopwords.py b/django/utils/stopwords.py
new file mode 100644
index 0000000000..dea5660413
--- /dev/null
+++ b/django/utils/stopwords.py
@@ -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)
+
diff --git a/django/utils/text.py b/django/utils/text.py
new file mode 100644
index 0000000000..cb9e9454d7
--- /dev/null
+++ b/django/utils/text.py
@@ -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)
diff --git a/django/utils/timesince.py b/django/utils/timesince.py
new file mode 100644
index 0000000000..c11cef0342
--- /dev/null
+++ b/django/utils/timesince.py
@@ -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()))
diff --git a/django/utils/xmlutils.py b/django/utils/xmlutils.py
new file mode 100644
index 0000000000..6638573857
--- /dev/null
+++ b/django/utils/xmlutils.py
@@ -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)
diff --git a/django/views/__init__.py b/django/views/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/django/views/admin/__init__.py b/django/views/admin/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/django/views/admin/doc.py b/django/views/admin/doc.py
new file mode 100644
index 0000000000..7f4e686a82
--- /dev/null
+++ b/django/views/admin/doc.py
@@ -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\w+)/athletes/(?P\w+)/$"
+# into "/athletes//"
+
+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
\ No newline at end of file
diff --git a/django/views/admin/main.py b/django/views/admin/main.py
new file mode 100644
index 0000000000..73a1a5fda6
--- /dev/null
+++ b/django/views/admin/main.py
@@ -0,0 +1,1089 @@
+# Generic admin views, with admin templates created dynamically at runtime.
+
+from django.core import formfields, meta, template_loader
+from django.core.exceptions import Http404, ObjectDoesNotExist, PermissionDenied
+from django.core.extensions import CMSContext as Context
+from django.models.auth import log
+from django.utils.html import strip_tags
+from django.utils.httpwrappers import HttpResponse, HttpResponseRedirect
+from django.utils.text import get_text_list
+import operator
+
+# Text to display within changelist table cells if the value is blank.
+EMPTY_CHANGELIST_VALUE = '(None)'
+
+def _get_mod_opts(app_label, module_name):
+ "Helper function that returns a tuple of (module, opts), raising Http404 if necessary."
+ try:
+ mod = meta.get_module(app_label, module_name)
+ except ImportError:
+ raise Http404 # Invalid app or module name. Maybe it's not in INSTALLED_APPS.
+ opts = mod.Klass._meta
+ if not opts.admin:
+ raise Http404 # This object is valid but has no admin interface.
+ return mod, opts
+
+def get_query_string(original_params, new_params={}, remove=[]):
+ """
+ >>> get_query_string({'first_name': 'adrian', 'last_name': 'smith'})
+ '?first_name=adrian&last_name=smith'
+ >>> get_query_string({'first_name': 'adrian', 'last_name': 'smith'}, {'first_name': 'john'})
+ '?first_name=john&last_name=smith'
+ >>> get_query_string({'test': 'yes'}, {'blah': 'no'}, ['te'])
+ '?blah=no'
+ """
+ p = original_params.copy()
+ for r in remove:
+ for k in p.keys():
+ if k.startswith(r):
+ del p[k]
+ for k, v in new_params.items():
+ if p.has_key(k) and v is None:
+ del p[k]
+ elif v is not None:
+ p[k] = v
+ return '?' + '&'.join(['%s=%s' % (k, v) for k, v in p.items()]).replace(' ', '%20')
+
+def index(request):
+ t = template_loader.get_template('index')
+ c = Context(request, {'title': 'Site administration'})
+ return HttpResponse(t.render(c))
+
+def logout(request):
+ request.session.delete()
+ t = template_loader.get_template('logged_out')
+ c = Context(request, {
+ 'title': "You're logged out",
+ })
+ return HttpResponse(t.render(c))
+
+def change_list(request, app_label, module_name):
+ from django.core import paginator
+ from django.utils import dateformat
+ from django.utils.dates import MONTHS
+ from django.utils.html import escape
+ import datetime
+
+ # The system will display a "Show all" link only if the total result count
+ # is less than or equal to this setting.
+ MAX_SHOW_ALL_ALLOWED = 200
+
+ DEFAULT_RESULTS_PER_PAGE = 100
+
+ ALL_VAR = 'all'
+ ORDER_VAR = 'o'
+ ORDER_TYPE_VAR = 'ot'
+ PAGE_VAR = 'p'
+ SEARCH_VAR = 'q'
+ IS_POPUP_VAR = 'pop'
+
+ mod, opts = _get_mod_opts(app_label, module_name)
+ if not request.user.has_perm(app_label + '.' + opts.get_change_permission()):
+ raise PermissionDenied
+
+ lookup_mod, lookup_opts = mod, opts
+
+ if opts.one_to_one_field:
+ lookup_mod = opts.one_to_one_field.rel.to.get_model_module()
+ lookup_opts = lookup_mod.Klass._meta
+
+ # Get search parameters from the query string.
+ try:
+ page_num = int(request.GET.get(PAGE_VAR, 0))
+ except ValueError:
+ page_num = 0
+ show_all = request.GET.has_key(ALL_VAR)
+ is_popup = request.GET.has_key(IS_POPUP_VAR)
+ params = dict(request.GET.copy())
+ if params.has_key(PAGE_VAR):
+ del params[PAGE_VAR]
+ # For ordering, first check the "ordering" parameter in the admin options,
+ # then check the object's default ordering. Finally, look for manually-
+ # specified ordering from the query string.
+ if lookup_opts.admin.ordering is not None:
+ order_field, order_type = lookup_opts.admin.ordering
+ else:
+ order_field, order_type = lookup_opts.ordering[0]
+ if params.has_key(ORDER_VAR):
+ try:
+ order_key = int(params[ORDER_VAR])
+ try:
+ f = lookup_opts.get_field(lookup_opts.admin.list_display[order_key])
+ except meta.FieldDoesNotExist:
+ pass
+ else:
+ if not isinstance(f.rel, meta.ManyToOne) or not f.null:
+ order_field = f.name
+ except (IndexError, ValueError):
+ pass # Invalid ordering specified. Just use the default.
+ if params.has_key(ORDER_TYPE_VAR) and params[ORDER_TYPE_VAR] in ('asc', 'desc'):
+ order_type = params[ORDER_TYPE_VAR]
+ query = request.GET.get(SEARCH_VAR, '')
+
+ # Prepare the lookup parameters for the API lookup.
+ lookup_params = params.copy()
+ for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR):
+ if lookup_params.has_key(i):
+ del lookup_params[i]
+ # If the order-by field is a field with a relationship, order by the value
+ # in the related table.
+ lookup_order_field = order_field
+ if isinstance(lookup_opts.get_field(order_field).rel, meta.ManyToOne):
+ f = lookup_opts.get_field(order_field)
+ lookup_order_field = '%s.%s' % (f.rel.to.db_table, f.rel.to.ordering[0][0])
+ # Use select_related if one of the list_display options is a field with a
+ # relationship.
+ for field_name in lookup_opts.admin.list_display:
+ try:
+ f = lookup_opts.get_field(field_name)
+ except meta.FieldDoesNotExist:
+ pass
+ else:
+ if isinstance(f.rel, meta.ManyToOne):
+ lookup_params['select_related'] = True
+ break
+ lookup_params['order_by'] = ((lookup_order_field, order_type),)
+ if lookup_opts.admin.search_fields and query:
+ or_queries = []
+ for bit in query.split():
+ or_query = []
+ for field_name in lookup_opts.admin.search_fields:
+ or_query.append(('%s__icontains' % field_name, bit))
+ or_queries.append(or_query)
+ lookup_params['_or'] = or_queries
+
+ if opts.one_to_one_field:
+ lookup_params.update(opts.one_to_one_field.rel.limit_choices_to)
+
+ # Get the results.
+ try:
+ p = paginator.ObjectPaginator(lookup_mod, lookup_params, DEFAULT_RESULTS_PER_PAGE)
+ # Naked except! Because we don't have any other way of validating "params".
+ # They might be invalid if the keyword arguments are incorrect, or if the
+ # values are not in the correct type (which would result in a database
+ # error).
+ except:
+ return HttpResponseRedirect(request.path)
+
+ # Get the total number of objects, with no filters applied.
+ real_lookup_params = lookup_params.copy()
+ del real_lookup_params['order_by']
+ if real_lookup_params:
+ full_result_count = lookup_mod.get_count()
+ else:
+ full_result_count = p.hits
+ del real_lookup_params
+ result_count = p.hits
+ can_show_all = result_count <= MAX_SHOW_ALL_ALLOWED
+ multi_page = result_count > DEFAULT_RESULTS_PER_PAGE
+
+ # Get the list of objects to display on this page.
+ if (show_all and can_show_all) or not multi_page:
+ result_list = lookup_mod.get_list(**lookup_params)
+ else:
+ try:
+ result_list = p.get_page(page_num)
+ except paginator.InvalidPage:
+ result_list = []
+
+ # Calculate filters first, because a CSS class high in the document depends
+ # on whether they are available.
+ filter_template = []
+ if lookup_opts.admin.list_filter and not opts.one_to_one_field:
+ filter_fields = [lookup_opts.get_field(field_name) for field_name in lookup_opts.admin.list_filter]
+ for f in filter_fields:
+ # Many-to-many or many-to-one filter.
+ if f.rel:
+ if isinstance(f, meta.ManyToManyField):
+ lookup_kwarg = '%s__id__exact' % f.name
+ lookup_title = f.rel.to.verbose_name
+ else:
+ lookup_kwarg = '%s__exact' % f.name
+ lookup_title = f.verbose_name
+ lookup_val = request.GET.get(lookup_kwarg, None)
+ lookup_choices = f.rel.to.get_model_module().get_list()
+ if len(lookup_choices) > 1:
+ filter_template.append('
\n' % \
+ ((lookup_val is None and ' class="selected"' or ''),
+ get_query_string(params, {}, [lookup_kwarg])))
+ for val in lookup_choices:
+ filter_template.append('
\n' % \
+ ((lookup_val is None and ' class="selected"' or ''),
+ get_query_string(params, {}, [lookup_kwarg])))
+ for k, v in f.choices:
+ filter_template.append('
\n' % \
+ (((lookup_val == v and not lookup_val2) and ' class="selected"' or ''),
+ get_query_string(params, {lookup_kwarg: v}, [lookup_kwarg2]), k))
+ if isinstance(f, meta.NullBooleanField):
+ filter_template.append('
')
+ del filter_template
+
+ # Result table.
+ if result_list:
+ # Table headers.
+ raw_template.append('
\n\n
\n')
+ for i, field_name in enumerate(lookup_opts.admin.list_display):
+ try:
+ f = lookup_opts.get_field(field_name)
+ except meta.FieldDoesNotExist:
+ # For non-field list_display values, check for the function
+ # attribute "short_description". If that doesn't exist, fall
+ # back to the method name. And __repr__ is a special-case.
+ if field_name == '__repr__':
+ header = lookup_opts.verbose_name
+ else:
+ func = getattr(mod.Klass, field_name) # Let AttributeErrors propogate.
+ try:
+ header = func.short_description
+ except AttributeError:
+ header = func.__name__
+ # Non-field list_display values don't get ordering capability.
+ raw_template.append('
%s
' % meta.capfirst(header))
+ else:
+ if isinstance(f.rel, meta.ManyToOne) and f.null:
+ raw_template.append('
' % \
+ ((th_classes and ' class="%s"' % ' '.join(th_classes) or ''),
+ get_query_string(params, {ORDER_VAR: i, ORDER_TYPE_VAR: new_order_type}),
+ meta.capfirst(f.verbose_name)))
+ raw_template.append('
\n\n')
+ # Result rows.
+ pk = lookup_opts.pk.name
+ for i, result in enumerate(result_list):
+ raw_template.append('
\n' % (i % 2 + 1))
+ for j, field_name in enumerate(lookup_opts.admin.list_display):
+ row_class = ''
+ try:
+ f = lookup_opts.get_field(field_name)
+ except meta.FieldDoesNotExist:
+ # For non-field list_display values, the value is a method
+ # name. Execute the method.
+ try:
+ result_repr = strip_tags(str(getattr(result, field_name)()))
+ except ObjectDoesNotExist:
+ result_repr = EMPTY_CHANGELIST_VALUE
+ else:
+ field_val = getattr(result, f.name)
+ # Foreign-key fields are special: Use the repr of the
+ # related object.
+ if isinstance(f.rel, meta.ManyToOne):
+ if field_val is not None:
+ result_repr = getattr(result, 'get_%s' % f.rel.name)()
+ else:
+ result_repr = EMPTY_CHANGELIST_VALUE
+ # Dates are special: They're formatted in a certain way.
+ elif isinstance(f, meta.DateField):
+ if field_val:
+ if isinstance(f, meta.DateTimeField):
+ result_repr = dateformat.format(field_val, 'N j, Y, P')
+ else:
+ result_repr = dateformat.format(field_val, 'N j, Y')
+ else:
+ result_repr = EMPTY_CHANGELIST_VALUE
+ row_class = ' class="nowrap"'
+ # Booleans are special: We use images.
+ elif isinstance(f, meta.BooleanField) or isinstance(f, meta.NullBooleanField):
+ BOOLEAN_MAPPING = {True: 'yes', False: 'no', None: 'unknown'}
+ result_repr = '' % (BOOLEAN_MAPPING[field_val], field_val)
+ # ImageFields are special: Use a thumbnail.
+ elif isinstance(f, meta.ImageField):
+ from django.parts.media.photos import get_thumbnail_url
+ result_repr = '' % (get_thumbnail_url(getattr(result, 'get_%s_url' % f.name)(), '120'), field_val, field_val)
+ # FloatFields are special: Zero-pad the decimals.
+ elif isinstance(f, meta.FloatField):
+ if field_val is not None:
+ result_repr = ('%%.%sf' % f.decimal_places) % field_val
+ else:
+ result_repr = EMPTY_CHANGELIST_VALUE
+ # Fields with choices are special: Use the representation
+ # of the choice.
+ elif f.choices:
+ result_repr = dict(f.choices).get(field_val, EMPTY_CHANGELIST_VALUE)
+ else:
+ result_repr = strip_tags(str(field_val))
+ # Some browsers don't like empty "
"s.
+ if result_repr == '':
+ result_repr = ' '
+ if j == 0: # First column is a special case
+ result_id = getattr(result, pk)
+ raw_template.append('
')
+ if (show_all and can_show_all) or not multi_page:
+ pass
+ else:
+ raw_template.append('Page › ')
+ ON_EACH_SIDE = 3
+ ON_ENDS = 2
+ DOT = '.'
+ # If there are 10 or fewer pages, display links to every page.
+ # Otherwise, do some fancy
+ if p.pages <= 10:
+ page_range = range(p.pages)
+ else:
+ # Insert "smart" pagination links, so that there are always ON_ENDS
+ # links at either end of the list of pages, and there are always
+ # ON_EACH_SIDE links at either end of the "current page" link.
+ page_range = []
+ if page_num > (ON_EACH_SIDE + ON_ENDS):
+ page_range.extend(range(0, ON_EACH_SIDE - 1))
+ page_range.append(DOT)
+ page_range.extend(range(page_num - ON_EACH_SIDE, page_num + 1))
+ else:
+ page_range.extend(range(0, page_num + 1))
+ if page_num < (p.pages - ON_EACH_SIDE - ON_ENDS - 1):
+ page_range.extend(range(page_num + 1, page_num + ON_EACH_SIDE + 1))
+ page_range.append(DOT)
+ page_range.extend(range(p.pages - ON_ENDS, p.pages))
+ else:
+ page_range.extend(range(page_num + 1, p.pages))
+ for i in page_range:
+ if i == DOT:
+ raw_template.append('... ')
+ elif i == page_num:
+ raw_template.append('%d ' % (i+1))
+ else:
+ raw_template.append('%d ' % \
+ (get_query_string(params, {PAGE_VAR: i}), (i == p.pages-1 and ' class="end"' or ''), i+1))
+ raw_template.append('%s %s' % (result_count, result_count == 1 and opts.verbose_name or opts.verbose_name_plural))
+ if can_show_all and not show_all and multi_page:
+ raw_template.append(' Show all' % \
+ get_query_string(params, {ALL_VAR: ''}))
+ raw_template.append('
')
+
+ raw_template.append('
\n
')
+ raw_template.append('{% endblock %}\n')
+ t = template_loader.get_template_from_string(''.join(raw_template))
+ c = Context(request, {
+ 'title': (is_popup and 'Select %s' % opts.verbose_name or 'Select %s to change' % opts.verbose_name),
+ 'is_popup': is_popup,
+ })
+ return HttpResponse(t.render(c))
+
+def _get_flattened_data(field, val):
+ """
+ Returns a dictionary mapping the field's manipulator field names to its
+ "flattened" string values for the admin view. "val" is an instance of the
+ field's value.
+ """
+ if isinstance(field, meta.DateTimeField):
+ date_field, time_field = field.get_manipulator_field_names('')
+ return {date_field: (val is not None and val.strftime("%Y-%m-%d") or ''),
+ time_field: (val is not None and val.strftime("%H:%M:%S") or '')}
+ elif isinstance(field, meta.DateField):
+ return {field.name: (val is not None and val.strftime("%Y-%m-%d") or '')}
+ elif isinstance(field, meta.TimeField):
+ return {field.name: (val is not None and val.strftime("%H:%M:%S") or '')}
+ else:
+ return {field.name: val}
+
+use_raw_id_admin = lambda field: isinstance(field.rel, meta.ManyToOne) and field.rel.raw_id_admin
+
+def _get_submit_row_template(opts, app_label, add, change, show_delete, ordered_objects):
+ t = ['
']
+ if change or show_delete:
+ t.append('{%% if perms.%s.%s %%}{%% if not is_popup %%}
{%% endif %%}{%% endif %%}' % \
+ (app_label, opts.get_delete_permission()))
+ if change and opts.admin.save_as:
+ t.append('{%% if not is_popup %%}{%% endif %%}' % \
+ (ordered_objects and change and 'onclick="submitOrderForm();"' or ''))
+ if not opts.admin.save_as or add:
+ t.append('{%% if not is_popup %%}{%% endif %%}' % \
+ (ordered_objects and change and 'onclick="submitOrderForm();"' or ''))
+ t.append('' % \
+ (ordered_objects and change and 'onclick="submitOrderForm();"' or ''))
+ t.append('' % \
+ (ordered_objects and change and 'onclick="submitOrderForm();"' or ''))
+ t.append('
\n')
+ return t
+
+def _get_template(opts, app_label, add=False, change=False, show_delete=False, form_url=''):
+ ordered_objects = opts.get_ordered_objects()[:]
+ auto_populated_fields = [f for f in opts.fields if f.prepopulate_from]
+ t = ['{% extends "base_site" %}\n']
+ t.append('{% block extrahead %}')
+
+ # Put in any necessary JavaScript imports.
+ javascript_imports = ['/m/js/core.js', '/m/js/admin/RelatedObjectLookups.js']
+ if 'collapse' in ' '.join([f[1].get('classes', '') for f in opts.admin.fields]):
+ javascript_imports.append('/m/js/admin/CollapsedFieldsets.js')
+ if auto_populated_fields:
+ javascript_imports.append('/m/js/urlify.js')
+ if opts.has_field_type(meta.DateTimeField) or opts.has_field_type(meta.TimeField) or opts.has_field_type(meta.DateField):
+ javascript_imports.extend(['/m/js/calendar.js', '/m/js/admin/DateTimeShortcuts.js'])
+ if ordered_objects:
+ javascript_imports.extend(['/m/js/getElementsBySelector.js', '/m/js/dom-drag.js', '/m/js/admin/ordering.js'])
+ if opts.admin.js:
+ javascript_imports.extend(opts.admin.js)
+ for _, options in opts.admin.fields:
+ try:
+ for field_list in options['fields']:
+ for f in field_list:
+ if f.rel and isinstance(f, meta.ManyToManyField) and f.rel.filter_interface:
+ javascript_imports.extend(['/m/js/SelectBox.js', '/m/js/SelectFilter2.js'])
+ raise StopIteration
+ except StopIteration:
+ break
+ for j in javascript_imports:
+ t.append('' % j)
+
+ t.append('{% endblock %}\n')
+ if ordered_objects:
+ coltype = 'colMS'
+ else:
+ coltype = 'colM'
+ t.append('{%% block coltype %%}%s{%% endblock %%}\n' % coltype)
+ t.append('{%% block bodyclass %%}%s-%s change-form{%% endblock %%}\n' % (app_label, opts.object_name.lower()))
+ breadcrumb_title = add and "Add %s" % opts.verbose_name or '{{ original|striptags|truncatewords:"18" }}'
+ t.append('{%% block breadcrumbs %%}{%% if not is_popup %%}
\n{% endblock %}')
+ return ''.join(t)
+
+def _get_admin_field(field_list, name_prefix, rel, add, change):
+ "Returns the template code for editing the given list of fields in the admin template."
+ field_names = []
+ for f in field_list:
+ field_names.extend(f.get_manipulator_field_names(name_prefix))
+ div_class_names = ['form-row', '{%% if %s %%} error{%% endif %%}' % ' or '.join(['%s.errors' % n for n in field_names])]
+ # Assumes BooleanFields won't be stacked next to each other!
+ if isinstance(field_list[0], meta.BooleanField):
+ div_class_names.append('checkbox-row')
+ t = []
+ t.append('
\n' % ' '.join(div_class_names))
+ for n in field_names:
+ t.append('{%% if %s.errors %%}{{ %s.html_error_list }}{%% endif %%}\n' % (n, n))
+ for i, field in enumerate(field_list):
+ label_name = 'id_%s%s' % ((rel and "%s{{ forloop.counter0 }}." % name_prefix or ""), field.get_manipulator_field_names('')[0])
+ # BooleanFields are a special case, because the checkbox widget appears to
+ # the *left* of the label.
+ if isinstance(field, meta.BooleanField):
+ t.append(_get_admin_field_form_widget(field, name_prefix, rel, add, change))
+ t.append(' %s' % (label_name, meta.capfirst(field.verbose_name)))
+ else:
+ class_names = []
+ if not field.blank:
+ class_names.append('required')
+ if i > 0:
+ class_names.append('inline')
+ t.append('%s: ' % (label_name, class_names and ' class="%s"' % ' '.join(class_names) or '', meta.capfirst(field.verbose_name)))
+ t.append(_get_admin_field_form_widget(field, name_prefix, rel, add, change))
+ if change and use_raw_id_admin(field):
+ obj_repr = '%soriginal.get_%s|truncatewords:"14"' % (rel and name_prefix or '', field.rel.name)
+ t.append('{%% if %s %%} {{ %s }}{%% endif %%}' % (obj_repr, obj_repr))
+ if field.help_text:
+ t.append('
%s
\n' % field.help_text)
+ t.append('
\n\n')
+ return ''.join(t)
+
+def _get_admin_field_form_widget(field, name_prefix, rel, add, change):
+ "Returns JUST the formfield widget for the field's admin interface."
+ field_names = field.get_manipulator_field_names(name_prefix)
+ if isinstance(field, meta.DateTimeField):
+ return '
Date: {{ %s }} Time: {{ %s }}
' % tuple(field_names)
+ t = ['{{ %s }}' % n for n in field_names]
+ if change and isinstance(field, meta.FileField):
+ return '{%% if %soriginal.%s %%}Currently: {{ %soriginal.%s }} Change: %s{%% else %%}%s{%% endif %%}' % \
+ (name_prefix, field.name, name_prefix, field.name, name_prefix, field.name, ''.join(t), ''.join(t))
+ field_id = 'id_%s%s' % ((rel and "%s{{ forloop.counter0 }}." % name_prefix or ""), field.get_manipulator_field_names('')[0])
+ # raw_id_admin fields get the little lookup link next to them
+ if use_raw_id_admin(field):
+ t.append(' ' % \
+ (field.rel.to.app_label, field.rel.to.module_name, field_id))
+ t.append('')
+ # fields with relationships to editable objects get an "add another" link,
+ # but only if the field doesn't have raw_admin ('cause in that case they get
+ # the "add" button in the popup)
+ elif field.rel and isinstance(field.rel, meta.ManyToOne) and field.rel.to.admin:
+ t.append('{%% if perms.%s.%s %%}' % (field.rel.to.app_label, field.rel.to.get_add_permission()))
+ t.append(' ' % \
+ (field.rel.to.app_label, field.rel.to.module_name, field_id))
+ t.append('')
+ t.append('{% endif %}')
+ return ''.join(t)
+
+def add_stage(request, app_label, module_name, show_delete=False, form_url='', post_url='../', post_url_continue='../%s/', object_id_override=None):
+ mod, opts = _get_mod_opts(app_label, module_name)
+ if not request.user.has_perm(app_label + '.' + opts.get_add_permission()):
+ raise PermissionDenied
+ manipulator = mod.AddManipulator()
+ if request.POST:
+ new_data = request.POST.copy()
+ if opts.has_field_type(meta.FileField):
+ new_data.update(request.FILES)
+ errors = manipulator.get_validation_errors(new_data)
+ if not errors and not request.POST.has_key("_preview"):
+ manipulator.do_html2python(new_data)
+ new_object = manipulator.save(new_data)
+ log.log_action(request.user.id, opts.get_content_type_id(), getattr(new_object, opts.pk.name), repr(new_object), log.ADDITION)
+ msg = 'The %s "%s" was added successfully.' % (opts.verbose_name, new_object)
+ # Here, we distinguish between different save types by checking for
+ # the presence of keys in request.POST.
+ if request.POST.has_key("_continue"):
+ request.user.add_message("%s You may edit it again below." % msg)
+ if request.POST.has_key("_popup"):
+ post_url_continue += "?_popup=1"
+ return HttpResponseRedirect(post_url_continue % new_object.id)
+ if request.POST.has_key("_popup"):
+ return HttpResponse('' % \
+ (getattr(new_object, opts.pk.name), repr(new_object).replace('"', '\\"')))
+ elif request.POST.has_key("_addanother"):
+ request.user.add_message("%s You may add another %s below." % (msg, opts.verbose_name))
+ return HttpResponseRedirect(request.path)
+ else:
+ request.user.add_message(msg)
+ return HttpResponseRedirect(post_url)
+ if request.POST.has_key("_preview"):
+ manipulator.do_html2python(new_data)
+ else:
+ new_data = {}
+ # Add default data.
+ for f in opts.fields:
+ if f.has_default():
+ new_data.update(_get_flattened_data(f, f.get_default()))
+ # In required many-to-one fields with only one available choice,
+ # select that one available choice. Note: We have to check that
+ # the length of choices is *2*, not 1, because SelectFields always
+ # have an initial "blank" value.
+ elif not f.blank and ((isinstance(f.rel, meta.ManyToOne) and not f.rel.raw_id_admin) or f.choices) and len(manipulator[f.name].choices) == 2:
+ new_data[f.name] = manipulator[f.name].choices[1][0]
+ # In required many-to-many fields with only one available choice,
+ # select that one available choice.
+ for f in opts.many_to_many:
+ if not f.blank and not f.rel.edit_inline and len(manipulator[f.name].choices) == 1:
+ new_data[f.name] = [manipulator[f.name].choices[0][0]]
+ # Add default data for related objects.
+ for rel_opts, rel_field in opts.get_inline_related_objects():
+ var_name = rel_opts.object_name.lower()
+ for i in range(rel_field.rel.num_in_admin):
+ for f in rel_opts.fields + rel_opts.many_to_many:
+ if f.has_default():
+ for field_name in f.get_manipulator_field_names(''):
+ new_data['%s.%d.%s' % (var_name, i, field_name)] = f.get_default()
+ # Override the defaults with request.GET, if it exists.
+ new_data.update(request.GET)
+ errors = {}
+
+ # Populate the FormWrapper.
+ form = formfields.FormWrapper(manipulator, new_data, errors)
+ for rel_opts, rel_field in opts.get_inline_related_objects():
+ var_name = rel_opts.object_name.lower()
+ wrapper = []
+ for i in range(rel_field.rel.num_in_admin):
+ collection = {}
+ for f in rel_opts.fields + rel_opts.many_to_many:
+ if f.editable and f != rel_field and not isinstance(f, meta.AutoField):
+ for field_name in f.get_manipulator_field_names(''):
+ full_field_name = '%s.%d.%s' % (var_name, i, field_name)
+ collection[field_name] = formfields.FormFieldWrapper(manipulator[full_field_name], new_data.get(full_field_name, ''), errors.get(full_field_name, []))
+ wrapper.append(formfields.FormFieldCollection(collection))
+ setattr(form, rel_opts.module_name, wrapper)
+
+ c = Context(request, {
+ 'title': 'Add %s' % opts.verbose_name,
+ "form": form,
+ "is_popup": request.REQUEST.has_key("_popup"),
+ })
+ if object_id_override is not None:
+ c['object_id'] = object_id_override
+ raw_template = _get_template(opts, app_label, add=True, show_delete=show_delete, form_url=form_url)
+# return HttpResponse(raw_template, mimetype='text/plain')
+ t = template_loader.get_template_from_string(raw_template)
+ return HttpResponse(t.render(c))
+
+def change_stage(request, app_label, module_name, object_id):
+ mod, opts = _get_mod_opts(app_label, module_name)
+ if not request.user.has_perm(app_label + '.' + opts.get_change_permission()):
+ raise PermissionDenied
+ if request.POST and request.POST.has_key("_saveasnew"):
+ return add_stage(request, app_label, module_name, form_url='../add/')
+ try:
+ manipulator = mod.ChangeManipulator(object_id)
+ except ObjectDoesNotExist:
+ raise Http404
+ inline_related_objects = opts.get_inline_related_objects()
+ if request.POST:
+ new_data = request.POST.copy()
+ if opts.has_field_type(meta.FileField):
+ new_data.update(request.FILES)
+ errors = manipulator.get_validation_errors(new_data)
+ if not errors and not request.POST.has_key("_preview"):
+ manipulator.do_html2python(new_data)
+ new_object = manipulator.save(new_data)
+
+ # Construct the change message.
+ change_message = []
+ if manipulator.fields_added:
+ change_message.append('Added %s.' % get_text_list(manipulator.fields_added, 'and'))
+ if manipulator.fields_changed:
+ change_message.append('Changed %s.' % get_text_list(manipulator.fields_changed, 'and'))
+ if manipulator.fields_deleted:
+ change_message.append('Deleted %s.' % get_text_list(manipulator.fields_deleted, 'and'))
+ change_message = ' '.join(change_message)
+ if not change_message:
+ change_message = 'No fields changed.'
+
+ log.log_action(request.user.id, opts.get_content_type_id(), getattr(new_object, opts.pk.name), repr(new_object), log.CHANGE, change_message)
+ msg = 'The %s "%s" was changed successfully.' % (opts.verbose_name, new_object)
+ if request.POST.has_key("_continue"):
+ request.user.add_message("%s You may edit it again below." % msg)
+ if request.REQUEST.has_key('_popup'):
+ return HttpResponseRedirect(request.path + "?_popup=1")
+ else:
+ return HttpResponseRedirect(request.path)
+ elif request.POST.has_key("_saveasnew"):
+ request.user.add_message('The %s "%s" was added successfully. You may edit it again below.' % (opts.verbose_name, new_object))
+ return HttpResponseRedirect("../%s/" % new_object.id)
+ elif request.POST.has_key("_addanother"):
+ request.user.add_message("%s You may add another %s below." % (msg, opts.verbose_name))
+ return HttpResponseRedirect("../add/")
+ else:
+ request.user.add_message(msg)
+ return HttpResponseRedirect("../")
+ if request.POST.has_key("_preview"):
+ manipulator.do_html2python(new_data)
+ else:
+ # Populate new_data with a "flattened" version of the current data.
+ new_data = {}
+ obj = manipulator.original_object
+ for f in opts.fields:
+ new_data.update(_get_flattened_data(f, getattr(obj, f.name)))
+ for f in opts.many_to_many:
+ if not f.rel.edit_inline:
+ new_data[f.name] = [i.id for i in getattr(obj, 'get_%s' % f.name)()]
+ for rel_obj, rel_field in inline_related_objects:
+ var_name = rel_obj.object_name.lower()
+ for i, rel_instance in enumerate(getattr(obj, 'get_%s_list' % opts.get_rel_object_method_name(rel_obj, rel_field))()):
+ for f in rel_obj.fields:
+ if f.editable and f != rel_field:
+ for k, v in _get_flattened_data(f, getattr(rel_instance, f.name)).items():
+ new_data['%s.%d.%s' % (var_name, i, k)] = v
+ for f in rel_obj.many_to_many:
+ new_data['%s.%d.%s' % (var_name, i, f.name)] = [j.id for j in getattr(rel_instance, 'get_%s' % f.name)()]
+
+ # If the object has ordered objects on its admin page, get the existing
+ # order and flatten it into a comma-separated list of IDs.
+ id_order_list = []
+ for rel_obj in opts.get_ordered_objects():
+ id_order_list.extend(getattr(obj, 'get_%s_order' % rel_obj.object_name.lower())())
+ if id_order_list:
+ new_data['order_'] = ','.join(map(str, id_order_list))
+ errors = {}
+
+ # Populate the FormWrapper.
+ form = formfields.FormWrapper(manipulator, new_data, errors)
+ form.original = manipulator.original_object
+ form.order_objects = []
+ for rel_opts, rel_field in inline_related_objects:
+ var_name = rel_opts.object_name.lower()
+ wrapper = []
+ orig_list = getattr(manipulator.original_object, 'get_%s_list' % opts.get_rel_object_method_name(rel_opts, rel_field))()
+ count = len(orig_list) + rel_field.rel.num_extra_on_change
+ if rel_field.rel.min_num_in_admin:
+ count = max(count, rel_field.rel.min_num_in_admin)
+ if rel_field.rel.max_num_in_admin:
+ count = min(count, rel_field.rel.max_num_in_admin)
+ for i in range(count):
+ collection = {'original': (i < len(orig_list) and orig_list[i] or None)}
+ for f in rel_opts.fields + rel_opts.many_to_many:
+ if f.editable and f != rel_field:
+ for field_name in f.get_manipulator_field_names(''):
+ full_field_name = '%s.%d.%s' % (var_name, i, field_name)
+ collection[field_name] = formfields.FormFieldWrapper(manipulator[full_field_name], new_data.get(full_field_name, ''), errors.get(full_field_name, []))
+ wrapper.append(formfields.FormFieldCollection(collection))
+ setattr(form, rel_opts.module_name, wrapper)
+ if rel_opts.order_with_respect_to and rel_opts.order_with_respect_to.rel and rel_opts.order_with_respect_to.rel.to == opts:
+ form.order_objects.extend(orig_list)
+
+ c = Context(request, {
+ 'title': 'Change %s' % opts.verbose_name,
+ "form": form,
+ 'object_id': object_id,
+ 'original': manipulator.original_object,
+ 'is_popup' : request.REQUEST.has_key('_popup'),
+ })
+ raw_template = _get_template(opts, app_label, change=True)
+# return HttpResponse(raw_template, mimetype='text/plain')
+ t = template_loader.get_template_from_string(raw_template)
+ return HttpResponse(t.render(c))
+
+def _nest_help(obj, depth, val):
+ current = obj
+ for i in range(depth):
+ current = current[-1]
+ current.append(val)
+
+def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current_depth):
+ "Helper function that recursively populates deleted_objects."
+ nh = _nest_help # Bind to local variable for performance
+ if current_depth > 16:
+ return # Avoid recursing too deep.
+ objects_seen = []
+ for rel_opts, rel_field in opts.get_all_related_objects():
+ if rel_opts in objects_seen:
+ continue
+ objects_seen.append(rel_opts)
+ rel_opts_name = opts.get_rel_object_method_name(rel_opts, rel_field)
+ if isinstance(rel_field.rel, meta.OneToOne):
+ try:
+ sub_obj = getattr(obj, 'get_%s' % rel_opts_name)()
+ except ObjectDoesNotExist:
+ pass
+ else:
+ if rel_opts.admin:
+ p = '%s.%s' % (rel_opts.app_label, rel_opts.get_delete_permission())
+ if not user.has_perm(p):
+ perms_needed.add(rel_opts.verbose_name)
+ # We don't care about populating deleted_objects now.
+ continue
+ if rel_field.rel.edit_inline or not rel_opts.admin:
+ # Don't display link to edit, because it either has no
+ # admin or is edited inline.
+ nh(deleted_objects, current_depth, ['%s: %r' % (meta.capfirst(rel_opts.verbose_name), sub_obj), []])
+ else:
+ # Display a link to the admin page.
+ nh(deleted_objects, current_depth, ['%s: %r' % \
+ (meta.capfirst(rel_opts.verbose_name), rel_opts.app_label, rel_opts.module_name,
+ getattr(sub_obj, rel_opts.pk.name), sub_obj), []])
+ _get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, rel_opts, current_depth+2)
+ else:
+ has_related_objs = False
+ for sub_obj in getattr(obj, 'get_%s_list' % rel_opts_name)():
+ has_related_objs = True
+ if rel_field.rel.edit_inline or not rel_opts.admin:
+ # Don't display link to edit, because it either has no
+ # admin or is edited inline.
+ nh(deleted_objects, current_depth, ['%s: %s' % (meta.capfirst(rel_opts.verbose_name), strip_tags(repr(sub_obj))), []])
+ else:
+ # Display a link to the admin page.
+ nh(deleted_objects, current_depth, ['%s: %s' % \
+ (meta.capfirst(rel_opts.verbose_name), rel_opts.app_label, rel_opts.module_name, sub_obj.id, strip_tags(repr(sub_obj))), []])
+ _get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, rel_opts, current_depth+2)
+ # If there were related objects, and the user doesn't have
+ # permission to delete them, add the missing perm to perms_needed.
+ if rel_opts.admin and has_related_objs:
+ p = '%s.%s' % (rel_opts.app_label, rel_opts.get_delete_permission())
+ if not user.has_perm(p):
+ perms_needed.add(rel_opts.verbose_name)
+ for rel_opts, rel_field in opts.get_all_related_many_to_many_objects():
+ if rel_opts in objects_seen:
+ continue
+ objects_seen.append(rel_opts)
+ rel_opts_name = opts.get_rel_object_method_name(rel_opts, rel_field)
+ has_related_objs = False
+ for sub_obj in getattr(obj, 'get_%s_list' % rel_opts_name)():
+ has_related_objs = True
+ if rel_field.rel.edit_inline or not rel_opts.admin:
+ # Don't display link to edit, because it either has no
+ # admin or is edited inline.
+ nh(deleted_objects, current_depth, ['One or more %s in %s: %s' % \
+ (rel_field.name, rel_opts.verbose_name, strip_tags(repr(sub_obj))), []])
+ else:
+ # Display a link to the admin page.
+ nh(deleted_objects, current_depth, ['One or more %s in %s: %s' % \
+ (rel_field.name, rel_opts.verbose_name, rel_opts.app_label, rel_opts.module_name, sub_obj.id, strip_tags(repr(sub_obj))), []])
+ # If there were related objects, and the user doesn't have
+ # permission to change them, add the missing perm to perms_needed.
+ if rel_opts.admin and has_related_objs:
+ p = '%s.%s' % (rel_opts.app_label, rel_opts.get_change_permission())
+ if not user.has_perm(p):
+ perms_needed.add(rel_opts.verbose_name)
+
+def delete_stage(request, app_label, module_name, object_id):
+ import sets
+ mod, opts = _get_mod_opts(app_label, module_name)
+ if not request.user.has_perm(app_label + '.' + opts.get_delete_permission()):
+ raise PermissionDenied
+ try:
+ obj = mod.get_object(**{'%s__exact' % opts.pk.name: object_id})
+ except ObjectDoesNotExist:
+ raise Http404
+
+ # Populate deleted_objects, a data structure of all related objects that
+ # will also be deleted.
+ deleted_objects = ['%s: %r' % (meta.capfirst(opts.verbose_name), object_id, obj), []]
+ perms_needed = sets.Set()
+ _get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1)
+
+ if request.POST: # The user has already confirmed the deletion.
+ if perms_needed:
+ raise PermissionDenied
+ obj.delete()
+ obj_repr = repr(obj)
+ log.log_action(request.user.id, opts.get_content_type_id(), object_id, obj_repr, log.DELETION)
+ request.user.add_message('The %s "%s" was deleted successfully.' % (opts.verbose_name, obj_repr))
+ return HttpResponseRedirect("../../")
+ t = template_loader.get_template("delete_confirmation_generic")
+ c = Context(request, {
+ "title": "Are you sure?",
+ "object_name": opts.verbose_name,
+ "object": obj,
+ "deleted_objects": deleted_objects,
+ "perms_lacking": perms_needed,
+ })
+ return HttpResponse(t.render(c))
+
+def history(request, app_label, module_name, object_id):
+ mod, opts = _get_mod_opts(app_label, module_name)
+ action_list = log.get_list(object_id__exact=object_id, content_type_id__exact=opts.get_content_type_id(),
+ order_by=(("action_time", "ASC"),), select_related=True)
+ # If no history was found, see whether this object even exists.
+ try:
+ obj = mod.get_object(id__exact=object_id)
+ except ObjectDoesNotExist:
+ raise Http404
+ t = template_loader.get_template('admin_object_history')
+ c = Context(request, {
+ 'title': 'Change history: %r' % obj,
+ 'action_list': action_list,
+ 'module_name': meta.capfirst(opts.verbose_name_plural),
+ 'object': obj,
+ })
+ return HttpResponse(t.render(c))
diff --git a/django/views/admin/template.py b/django/views/admin/template.py
new file mode 100644
index 0000000000..7c5e41e237
--- /dev/null
+++ b/django/views/admin/template.py
@@ -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
diff --git a/django/views/auth/__init__.py b/django/views/auth/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/django/views/auth/login.py b/django/views/auth/login.py
new file mode 100644
index 0000000000..97988d0444
--- /dev/null
+++ b/django/views/auth/login.py
@@ -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))
diff --git a/django/views/comments/__init__.py b/django/views/comments/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/django/views/comments/comments.py b/django/views/comments/comments.py
new file mode 100644
index 0000000000..213faf42e6
--- /dev/null
+++ b/django/views/comments/comments.py
@@ -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))
diff --git a/django/views/comments/karma.py b/django/views/comments/karma.py
new file mode 100644
index 0000000000..84f0ca852d
--- /dev/null
+++ b/django/views/comments/karma.py
@@ -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))
diff --git a/django/views/comments/userflags.py b/django/views/comments/userflags.py
new file mode 100644
index 0000000000..051ecb19fe
--- /dev/null
+++ b/django/views/comments/userflags.py
@@ -0,0 +1,82 @@
+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, moderatordeletions, userflags
+from django.views.decorators.auth import login_required
+from django.utils.httpwrappers import HttpResponse, HttpResponseRedirect
+from django.conf.settings import SITE_ID
+
+def flag(request, comment_id):
+ """
+ Flags a comment. Confirmation on GET, action on POST.
+
+ Templates: `comments/flag_verify`, `comments/flag_done`
+ Context:
+ comment
+ the flagged `comments.comments` object
+ """
+ try:
+ comment = comments.get_object(id__exact=comment_id, site_id__exact=SITE_ID)
+ except comments.CommentDoesNotExist:
+ raise Http404
+ if request.POST:
+ userflags.flag(comment, request.user)
+ return HttpResponseRedirect('%sdone/' % request.path)
+ t = template_loader.get_template('comments/flag_verify')
+ c = Context(request, {
+ 'comment': comment,
+ })
+ return HttpResponse(t.render(c))
+flag = login_required(flag)
+
+def flag_done(request, comment_id):
+ try:
+ comment = comments.get_object(id__exact=comment_id, site_id__exact=SITE_ID)
+ except comments.CommentDoesNotExist:
+ raise Http404
+ t = template_loader.get_template('comments/flag_done')
+ c = Context(request, {
+ 'comment': comment,
+ })
+ return HttpResponse(t.render(c))
+
+def delete(request, comment_id):
+ """
+ Deletes a comment. Confirmation on GET, action on POST.
+
+ Templates: `comments/delete_verify`, `comments/delete_done`
+ Context:
+ comment
+ the flagged `comments.comments` object
+ """
+ try:
+ comment = comments.get_object(id__exact=comment_id, site_id__exact=SITE_ID)
+ except comments.CommentDoesNotExist:
+ raise Http404
+ if not comments.user_is_moderator(request.user):
+ raise Http404
+ if request.POST:
+ # If the comment has already been removed, silently fail.
+ if not comment.is_removed:
+ comment.is_removed = True
+ comment.save()
+ m = moderatordeletions.ModeratorDeletion(None, request.user.id, comment.id, None)
+ m.save()
+ return HttpResponseRedirect('%sdone/' % request.path)
+ t = template_loader.get_template('comments/delete_verify')
+ c = Context(request, {
+ 'comment': comment,
+ })
+ return HttpResponse(t.render(c))
+delete = login_required(delete)
+
+def delete_done(request, comment_id):
+ try:
+ comment = comments.get_object(id__exact=comment_id, site_id__exact=SITE_ID)
+ except comments.CommentDoesNotExist:
+ raise Http404
+ t = template_loader.get_template('comments/delete_done')
+ c = Context(request, {
+ 'comment': comment,
+ })
+ return HttpResponse(t.render(c))
diff --git a/django/views/core/__init__.py b/django/views/core/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/django/views/core/flatfiles.py b/django/views/core/flatfiles.py
new file mode 100644
index 0000000000..68af68a355
--- /dev/null
+++ b/django/views/core/flatfiles.py
@@ -0,0 +1,34 @@
+from django.core import template_loader
+from django.core.exceptions import Http404
+from django.core.extensions import CMSContext as Context
+from django.models.core import flatfiles
+from django.utils.httpwrappers import HttpResponse
+from django.conf.settings import SITE_ID
+
+def flat_file(request, url):
+ """
+ Flat file view
+
+ Models: `core.flatfiles`
+ Templates: Uses the template defined by the ``template_name`` field,
+ or `flatfiles/default` if template_name is not defined.
+ Context:
+ flatfile
+ `flatfiles.flatfiles` object
+ """
+ if not url.startswith('/'):
+ url = "/" + url
+ try:
+ f = flatfiles.get_object(url__exact=url, sites__id__exact=SITE_ID)
+ except flatfiles.FlatFileDoesNotExist:
+ raise Http404
+ # If registration is required for accessing this page, and the user isn't
+ # logged in, redirect to the login page.
+ if request.user.is_anonymous() and f.registration_required:
+ from django.views.auth.login import redirect_to_login
+ return redirect_to_login(request.path)
+ t = template_loader.select_template([f.template_name, 'flatfiles/default'])
+ c = Context(request, {
+ 'flatfile': f,
+ })
+ return HttpResponse(t.render(c))
diff --git a/django/views/decorators/__init__.py b/django/views/decorators/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/django/views/decorators/auth.py b/django/views/decorators/auth.py
new file mode 100644
index 0000000000..ae27fe33a1
--- /dev/null
+++ b/django/views/decorators/auth.py
@@ -0,0 +1,12 @@
+def login_required(view_func):
+ """
+ Decorator for views that checks that the user is logged in, redirecting
+ to the log-in page if necessary.
+ """
+ from django.views.auth.login import redirect_to_login
+ def _checklogin(request, *args, **kwargs):
+ if request.user.is_anonymous():
+ return redirect_to_login(request.path)
+ else:
+ return view_func(request, *args, **kwargs)
+ return _checklogin
diff --git a/django/views/decorators/cache.py b/django/views/decorators/cache.py
new file mode 100644
index 0000000000..52501f74d5
--- /dev/null
+++ b/django/views/decorators/cache.py
@@ -0,0 +1,64 @@
+from django.core.cache import cache
+from django.utils.httpwrappers import HttpResponseNotModified
+import cStringIO, datetime, gzip, md5
+
+# From http://www.xhaus.com/alan/python/httpcomp.html#gzip
+# Used with permission.
+def compress_string(s):
+ zbuf = cStringIO.StringIO()
+ zfile = gzip.GzipFile(mode='wb', compresslevel=6, fileobj=zbuf)
+ zfile.write(s)
+ zfile.close()
+ return zbuf.getvalue()
+
+def cache_page(view_func, cache_timeout, key_prefix=''):
+ """
+ Decorator for views that tries getting the page from the cache and
+ populates the cache if the page isn't in the cache yet. Also takes care
+ of ETags and gzips the page if the client supports it.
+
+ The cache is keyed off of the page's URL plus the optional key_prefix
+ variable. Use key_prefix if your Django setup has multiple sites that
+ use cache; otherwise the cache for one site would affect the other. A good
+ example of key_prefix is to use sites.get_current().domain, because that's
+ unique across all CMS instances on a particular server.
+ """
+ def _check_cache(request, *args, **kwargs):
+ try:
+ accept_encoding = request.META['HTTP_ACCEPT_ENCODING']
+ except KeyError:
+ accept_encoding = ''
+ accepts_gzip = 'gzip' in accept_encoding
+ cache_key = 'views.decorators.cache.cache_page.%s.%s.%s' % (key_prefix, request.path, accepts_gzip)
+ response = cache.get(cache_key, None)
+ if response is None:
+ response = view_func(request, *args, **kwargs)
+ content = response.get_content_as_string('utf-8')
+ if accepts_gzip:
+ content = compress_string(content)
+ response.content = content
+ response['Content-Encoding'] = 'gzip'
+ response['ETag'] = md5.new(content).hexdigest()
+ response['Content-Length'] = '%d' % len(content)
+ response['Last-Modified'] = datetime.datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT')
+ cache.set(cache_key, response, cache_timeout)
+ else:
+ # Logic is from http://simon.incutio.com/archive/2003/04/23/conditionalGet
+ try:
+ if_none_match = request.META['HTTP_IF_NONE_MATCH']
+ except KeyError:
+ if_none_match = None
+ try:
+ if_modified_since = request.META['HTTP_IF_MODIFIED_SINCE']
+ except KeyError:
+ if_modified_since = None
+ if if_none_match is None and if_modified_since is None:
+ pass
+ elif if_none_match is not None and response['ETag'] != if_none_match:
+ pass
+ elif if_modified_since is not None and response['Last-Modified'] != if_modified_since:
+ pass
+ else:
+ return HttpResponseNotModified()
+ return response
+ return _check_cache
diff --git a/django/views/defaults.py b/django/views/defaults.py
new file mode 100644
index 0000000000..72cb027b00
--- /dev/null
+++ b/django/views/defaults.py
@@ -0,0 +1,72 @@
+from django.core import template_loader
+from django.core.exceptions import Http404, ObjectDoesNotExist
+from django.core.extensions import CMSContext as Context
+from django.models.core import sites
+from django.utils import httpwrappers
+
+def shortcut(request, content_type_id, object_id):
+ from django.models.core import contenttypes
+ 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:
+ raise Http404, "Content type %s object %s doesn't exist" % (content_type_id, object_id)
+ if not hasattr(obj, 'get_absolute_url'):
+ raise Http404, "%s objects don't have get_absolute_url() methods" % content_type.name
+ object_domain = None
+ if hasattr(obj, 'get_sites'):
+ site_list = obj.get_sites()
+ if site_list:
+ object_domain = site_list[0].domain
+ elif hasattr(obj, 'get_site'):
+ try:
+ object_domain = obj.get_site().domain
+ except sites.SiteDoesNotExist:
+ pass
+ try:
+ object_domain = sites.get_current().domain
+ except sites.SiteDoesNotExist:
+ pass
+ if not object_domain:
+ return httpwrappers.HttpResponseRedirect(obj.get_absolute_url())
+ return httpwrappers.HttpResponseRedirect('http://%s%s' % (object_domain, obj.get_absolute_url()))
+
+def page_not_found(request):
+ """
+ Default 404 handler, which looks for the requested URL in the redirects
+ table, redirects if found, and displays 404 page if not redirected.
+
+ Templates: `404`
+ Context: None
+ """
+ from django.models.core import redirects
+ from django.conf.settings import APPEND_SLASH, SITE_ID
+ path = request.get_full_path()
+ try:
+ r = redirects.get_object(site_id__exact=SITE_ID, old_path__exact=path)
+ except redirects.RedirectDoesNotExist:
+ r = None
+ if r is None and APPEND_SLASH:
+ # Try removing the trailing slash.
+ try:
+ r = redirects.get_object(site_id__exact=SITE_ID, old_path__exact=path[:path.rfind('/')]+path[path.rfind('/')+1:])
+ except redirects.RedirectDoesNotExist:
+ pass
+ if r is not None:
+ if r == '':
+ return httpwrappers.HttpResponseGone()
+ return httpwrappers.HttpResponseRedirect(r.new_path)
+ t = template_loader.get_template('404')
+ c = Context(request)
+ return httpwrappers.HttpResponseNotFound(t.render(c))
+
+def server_error(request):
+ """
+ 500 Error handler
+
+ Templates: `500`
+ Context: None
+ """
+ t = template_loader.get_template('500')
+ c = Context(request)
+ return httpwrappers.HttpResponseServerError(t.render(c))
diff --git a/django/views/generic/__init__.py b/django/views/generic/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/django/views/generic/date_based.py b/django/views/generic/date_based.py
new file mode 100644
index 0000000000..7db02813aa
--- /dev/null
+++ b/django/views/generic/date_based.py
@@ -0,0 +1,223 @@
+from django.core import template_loader
+from django.core.exceptions import Http404, ObjectDoesNotExist
+from django.core.extensions import CMSContext as Context
+from django.core.xheaders import populate_xheaders
+from django.models import get_module
+from django.utils.httpwrappers import HttpResponse
+import datetime, time
+
+def archive_index(request, app_label, module_name, date_field, num_latest=15, template_name=None, extra_lookup_kwargs={}, extra_context=None):
+ """
+ Generic top-level archive of date-based objects.
+
+ Templates: ``/_archive``
+ Context:
+ date_list
+ List of years
+ latest
+ Latest N (defaults to 15) objects by date
+ """
+ mod = get_module(app_label, module_name)
+ lookup_kwargs = {'%s__lte' % date_field: datetime.datetime.now()}
+ lookup_kwargs.update(extra_lookup_kwargs)
+ date_list = getattr(mod, "get_%s_list" % date_field)('year', **lookup_kwargs)[::-1]
+ if not date_list:
+ raise Http404("No %s.%s available" % (app_label, module_name))
+
+ if num_latest:
+ lookup_kwargs.update({
+ 'limit': num_latest,
+ 'order_by': ((date_field, 'DESC'),),
+ })
+ latest = mod.get_list(**lookup_kwargs)
+ else:
+ latest = None
+
+ if not template_name:
+ template_name = "%s/%s_archive" % (app_label, module_name)
+ t = template_loader.get_template(template_name)
+ c = Context(request, {
+ 'date_list' : date_list,
+ 'latest' : latest,
+ })
+ if extra_context:
+ c.update(extra_context)
+ return HttpResponse(t.render(c))
+
+def archive_year(request, year, app_label, module_name, date_field, template_name=None, extra_lookup_kwargs={}, extra_context=None):
+ """
+ Generic yearly archive view.
+
+ Templates: ``/_archive_year``
+ Context:
+ date_list
+ List of months in this year with objects
+ year
+ This year
+ """
+ mod = get_module(app_label, module_name)
+ now = datetime.datetime.now()
+ lookup_kwargs = {'%s__year' % date_field: year}
+ # Only bother to check current date if the year isn't in the past.
+ if int(year) >= now.year:
+ lookup_kwargs['%s__lte' % date_field] = now
+ lookup_kwargs.update(extra_lookup_kwargs)
+ date_list = getattr(mod, "get_%s_list" % date_field)('month', **lookup_kwargs)
+ if not date_list:
+ raise Http404
+ if not template_name:
+ template_name = "%s/%s_archive_year" % (app_label, module_name)
+ t = template_loader.get_template(template_name)
+ c = Context(request, {
+ 'date_list': date_list,
+ 'year': year,
+ })
+ if extra_context:
+ c.update(extra_context)
+ return HttpResponse(t.render(c))
+
+def archive_month(request, year, month, app_label, module_name, date_field, template_name=None, extra_lookup_kwargs={}, extra_context=None):
+ """
+ Generic monthly archive view.
+
+ Templates: ``/_archive_month``
+ Context:
+ month:
+ this month
+ object_list:
+ list of objects published in the given month
+ """
+ try:
+ date = datetime.date(*time.strptime(year+month, '%Y%b')[:3])
+ except ValueError:
+ raise Http404
+ mod = get_module(app_label, module_name)
+ now = datetime.datetime.now()
+ # Calculate first and last day of month, for use in a date-range lookup.
+ first_day = date.replace(day=1)
+ last_day = date
+ for i in (31, 30, 29, 28):
+ try:
+ last_day = last_day.replace(day=i)
+ except ValueError:
+ continue
+ else:
+ break
+ lookup_kwargs = {'%s__range' % date_field: (first_day, last_day)}
+ # Only bother to check current date if the month isn't in the past.
+ if date >= now:
+ lookup_kwargs['%s__lte' % date_field] = now
+ lookup_kwargs.update(extra_lookup_kwargs)
+ object_list = mod.get_list(**lookup_kwargs)
+ if not object_list:
+ raise Http404
+ if not template_name:
+ template_name = "%s/%s_archive_month" % (app_label, module_name)
+ t = template_loader.get_template(template_name)
+ c = Context(request, {
+ 'object_list': object_list,
+ 'month': date,
+ })
+ if extra_context:
+ c.update(extra_context)
+ return HttpResponse(t.render(c))
+
+def archive_day(request, year, month, day, app_label, module_name, date_field, template_name=None, extra_lookup_kwargs={}, extra_context=None, allow_empty=False):
+ """
+ Generic daily archive view.
+
+ Templates: ``/_archive_day``
+ Context:
+ object_list:
+ list of objects published that day
+ day:
+ (datetime) the day
+ previous_day
+ (datetime) the previous day
+ next_day
+ (datetime) the next day, or None if the current day is today
+ """
+ try:
+ date = datetime.date(*time.strptime(year+month+day, '%Y%b%d')[:3])
+ except ValueError:
+ raise Http404
+ mod = get_module(app_label, module_name)
+ now = datetime.datetime.now()
+ lookup_kwargs = {
+ '%s__range' % date_field: (datetime.datetime.combine(date, datetime.time.min), datetime.datetime.combine(date, datetime.time.max)),
+ }
+ # Only bother to check current date if the date isn't in the past.
+ if date >= now:
+ lookup_kwargs['%s__lte' % date_field] = now
+ lookup_kwargs.update(extra_lookup_kwargs)
+ object_list = mod.get_list(**lookup_kwargs)
+ if not allow_empty and not object_list:
+ raise Http404
+ if not template_name:
+ template_name = "%s/%s_archive_day" % (app_label, module_name)
+ t = template_loader.get_template(template_name)
+ c = Context(request, {
+ 'object_list': object_list,
+ 'day': date,
+ 'previous_day': date - datetime.timedelta(days=1),
+ 'next_day': (date < datetime.date.today()) and (date + datetime.timedelta(days=1)) or None,
+ })
+ if extra_context:
+ c.update(extra_context)
+ return HttpResponse(t.render(c))
+
+def archive_today(request, **kwargs):
+ """
+ Generic daily archive view for today. Same as archive_day view.
+ """
+ today = datetime.date.today()
+ kwargs.update({
+ 'year': str(today.year),
+ 'month': today.strftime('%b').lower(),
+ 'day': str(today.day),
+ })
+ return archive_day(request, **kwargs)
+
+def object_detail(request, year, month, day, app_label, module_name, date_field, object_id=None, slug=None, slug_field=None, template_name=None, extra_lookup_kwargs={}, extra_context=None):
+ """
+ Generic detail view from year/month/day/slug or year/month/day/id structure.
+
+ Templates: ``/_detail``
+ Context:
+ object:
+ the object to be detailed
+ """
+ try:
+ date = datetime.date(*time.strptime(year+month+day, '%Y%b%d')[:3])
+ except ValueError:
+ raise Http404
+ mod = get_module(app_label, module_name)
+ now = datetime.datetime.now()
+ lookup_kwargs = {
+ '%s__range' % date_field: (datetime.datetime.combine(date, datetime.time.min), datetime.datetime.combine(date, datetime.time.max)),
+ }
+ # Only bother to check current date if the date isn't in the past.
+ if date >= now:
+ lookup_kwargs['%s__lte' % date_field] = now
+ if object_id:
+ lookup_kwargs['%s__exact' % mod.Klass._meta.pk.name] = object_id
+ elif slug and slug_field:
+ lookup_kwargs['%s__exact' % slug_field] = slug
+ else:
+ raise AttributeError("Generic detail view must be called with either an object_id or a slug/slugfield")
+ lookup_kwargs.update(extra_lookup_kwargs)
+ try:
+ object = mod.get_object(**lookup_kwargs)
+ except ObjectDoesNotExist:
+ raise Http404("%s.%s does not exist for %s" % (app_label, module_name, lookup_kwargs))
+ if not template_name:
+ template_name = "%s/%s_detail" % (app_label, module_name)
+ t = template_loader.get_template(template_name)
+ c = Context(request, {
+ 'object': object,
+ })
+ if extra_context:
+ c.update(extra_context)
+ response = HttpResponse(t.render(c))
+ populate_xheaders(request, response, app_label, module_name, getattr(object, object._meta.pk.name))
+ return response
diff --git a/django/views/generic/list_detail.py b/django/views/generic/list_detail.py
new file mode 100644
index 0000000000..8b3ae5b9e9
--- /dev/null
+++ b/django/views/generic/list_detail.py
@@ -0,0 +1,106 @@
+from django import models
+from django.core import template_loader
+from django.utils.httpwrappers import HttpResponse
+from django.core.xheaders import populate_xheaders
+from django.core.extensions import CMSContext as Context
+from django.core.paginator import ObjectPaginator, InvalidPage
+from django.core.exceptions import Http404, ObjectDoesNotExist
+
+def object_list(request, app_label, module_name, paginate_by=None, allow_empty=False, template_name=None, extra_lookup_kwargs={}, extra_context=None):
+ """
+ Generic list of objects.
+
+ Templates: ``/_list``
+ Context:
+ object_list
+ list of objects
+ is_paginated
+ are the results paginated?
+ results_per_page
+ number of objects per page (if paginated)
+ has_next
+ is there a next page?
+ has_previous
+ is there a prev page?
+ page
+ the current page
+ next
+ the next page
+ previous
+ the previous page
+ pages
+ number of pages, total
+ """
+ mod = models.get_module(app_label, module_name)
+ lookup_kwargs = extra_lookup_kwargs.copy()
+ if paginate_by:
+ paginator = ObjectPaginator(mod, lookup_kwargs, paginate_by)
+ page = request.GET.get('page', 0)
+ try:
+ object_list = paginator.get_page(page)
+ except InvalidPage:
+ raise Http404
+ page = int(page)
+ c = Context(request, {
+ 'object_list': object_list,
+ 'is_paginated' : True,
+ 'results_per_page' : paginate_by,
+ 'has_next': paginator.has_next_page(page),
+ 'has_previous': paginator.has_previous_page(page),
+ 'page': page + 1,
+ 'next': page + 1,
+ 'previous': page - 1,
+ 'pages': paginator.pages,
+ })
+ else:
+ object_list = mod.get_list(**lookup_kwargs)
+ c = Context(request, {
+ 'object_list' : object_list,
+ 'is_paginated' : False
+ })
+ if len(object_list) == 0 and not allow_empty:
+ raise Http404
+ if extra_context:
+ c.update(extra_context)
+ if not template_name:
+ template_name = "%s/%s_list" % (app_label, module_name)
+ t = template_loader.get_template(template_name)
+ return HttpResponse(t.render(c))
+
+def object_detail(request, app_label, module_name, object_id=None, slug=None, slug_field=None, template_name=None, template_name_field=None, extra_lookup_kwargs={}, extra_context=None):
+ """
+ Generic list of objects.
+
+ Templates: ``/_list``
+ Context:
+ object
+ the object (whoa!)
+ """
+ mod = models.get_module(app_label, module_name)
+ lookup_kwargs = {}
+ if object_id:
+ lookup_kwargs['%s__exact' % mod.Klass._meta.pk.name] = object_id
+ elif slug and slug_field:
+ lookup_kwargs['%s__exact' % slug_field] = slug
+ else:
+ raise AttributeError("Generic detail view must be called with either an object_id or a slug/slug_field")
+ lookup_kwargs.update(extra_lookup_kwargs)
+ try:
+ object = mod.get_object(**lookup_kwargs)
+ except ObjectDoesNotExist:
+ raise Http404("%s.%s does not exist for %s" % (app_label, module_name, lookup_kwargs))
+ if not template_name:
+ template_name = "%s/%s_detail" % (app_label, module_name)
+ if template_name_field:
+ template_name_list = [getattr(object, template_name_field), template_name]
+ t = template_loader.select_template(template_name_list)
+ else:
+ t = template_loader.get_template(template_name)
+ c = Context(request, {
+ 'object' : object,
+ })
+ if extra_context:
+ c.update(extra_context)
+ response = HttpResponse(t.render(c))
+ populate_xheaders(request, response, app_label, module_name, getattr(object, object._meta.pk.name))
+ return response
diff --git a/django/views/registration/__init__.py b/django/views/registration/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/django/views/registration/passwords.py b/django/views/registration/passwords.py
new file mode 100644
index 0000000000..0c06341af2
--- /dev/null
+++ b/django/views/registration/passwords.py
@@ -0,0 +1,109 @@
+from django.core import formfields, template_loader, validators
+from django.core.extensions import CMSContext as Context
+from django.models.auth import users
+from django.views.decorators.auth import login_required
+from django.utils.httpwrappers import HttpResponse, HttpResponseRedirect
+
+class PasswordResetForm(formfields.Manipulator):
+ "A form that lets a user request a password reset"
+ def __init__(self):
+ self.fields = (
+ formfields.EmailField(field_name="email", length=40, is_required=True,
+ validator_list=[self.isValidUserEmail]),
+ )
+
+ def isValidUserEmail(self, new_data, all_data):
+ "Validates that a user exists with the given e-mail address"
+ try:
+ self.user_cache = users.get_object(email__iexact=new_data)
+ except users.UserDoesNotExist:
+ raise validators.ValidationError, "That e-mail address doesn't have an associated user acount. Are you sure you've registered?"
+
+ def save(self, domain_override=None):
+ "Calculates a new password randomly and sends it to the user"
+ from django.core.mail import send_mail
+ from django.models.core import sites
+ new_pass = users.make_random_password()
+ self.user_cache.set_password(new_pass)
+ self.user_cache.save()
+ if not domain_override:
+ current_site = sites.get_current()
+ site_name = current_site.name
+ domain = current_site.domain
+ else:
+ site_name = domain = domain_override
+ t = template_loader.get_template('registration/password_reset_email')
+ c = {
+ 'new_password': new_pass,
+ 'email': self.user_cache.email,
+ 'domain': domain,
+ 'site_name': site_name,
+ 'user': self.user_cache,
+ }
+ send_mail('Password reset on %s' % site_name, t.render(c), None, [self.user_cache.email])
+
+class PasswordChangeForm(formfields.Manipulator):
+ "A form that lets a user change his password."
+ def __init__(self, user):
+ self.user = user
+ self.fields = (
+ formfields.PasswordField(field_name="old_password", length=30, maxlength=30, is_required=True,
+ validator_list=[self.isValidOldPassword]),
+ formfields.PasswordField(field_name="new_password1", length=30, maxlength=30, is_required=True,
+ validator_list=[validators.AlwaysMatchesOtherField('new_password2', "The two 'new password' fields didn't match.")]),
+ formfields.PasswordField(field_name="new_password2", length=30, maxlength=30, is_required=True),
+ )
+
+ def isValidOldPassword(self, new_data, all_data):
+ "Validates that the old_password field is correct."
+ if not self.user.check_password(new_data):
+ raise validators.ValidationError, "Your old password was entered incorrectly. Please enter it again."
+
+ def save(self, new_data):
+ "Saves the new password."
+ self.user.set_password(new_data['new_password1'])
+ self.user.save()
+
+def password_reset(request, is_admin_site=False):
+ new_data, errors = {}, {}
+ form = PasswordResetForm()
+ if request.POST:
+ new_data = request.POST.copy()
+ errors = form.get_validation_errors(new_data)
+ if not errors:
+ if is_admin_site:
+ form.save(request.META['HTTP_HOST'])
+ else:
+ form.save()
+ return HttpResponseRedirect('%sdone/' % request.path)
+ t = template_loader.get_template('registration/password_reset_form')
+ c = Context(request, {
+ 'form': formfields.FormWrapper(form, new_data, errors),
+ })
+ return HttpResponse(t.render(c))
+
+def password_reset_done(request):
+ t = template_loader.get_template('registration/password_reset_done')
+ c = Context(request, {})
+ return HttpResponse(t.render(c))
+
+def password_change(request):
+ new_data, errors = {}, {}
+ form = PasswordChangeForm(request.user)
+ if request.POST:
+ new_data = request.POST.copy()
+ errors = form.get_validation_errors(new_data)
+ if not errors:
+ form.save(new_data)
+ return HttpResponseRedirect('%sdone/' % request.path)
+ t = template_loader.get_template('registration/password_change_form')
+ c = Context(request, {
+ 'form': formfields.FormWrapper(form, new_data, errors),
+ })
+ return HttpResponse(t.render(c))
+password_change = login_required(password_change)
+
+def password_change_done(request):
+ t = template_loader.get_template('registration/password_change_done')
+ c = Context(request, {})
+ return HttpResponse(t.render(c))
diff --git a/django/views/rss/__init__.py b/django/views/rss/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/django/views/rss/rss.py b/django/views/rss/rss.py
new file mode 100644
index 0000000000..4f77307a91
--- /dev/null
+++ b/django/views/rss/rss.py
@@ -0,0 +1,12 @@
+from django.core import rss
+from django.core.exceptions import Http404
+from django.utils.httpwrappers import HttpResponse
+
+def feed(request, slug, param=None):
+ try:
+ f = rss.get_registered_feed(slug).get_feed(param)
+ except rss.FeedIsNotRegistered:
+ raise Http404
+ response = HttpResponse(mimetype='application/xml')
+ f.write(response, 'utf-8')
+ return response