Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

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

git-svn-id: http://code.djangoproject.com/svn/django/trunk@3 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit ed114e15106192b22ebb78ef5bf5bce72b419d13 1 parent 07ffc7d
@adrianholovaty adrianholovaty authored
Showing with 13,851 additions and 0 deletions.
  1. 0  django/__init__.py
  2. 0  django/bin/__init__.py
  3. +15 −0 django/bin/daily_cleanup.py
  4. +412 −0 django/bin/django-admin.py
  5. 0  django/bin/profiling/__init__.py
  6. +34 −0 django/bin/profiling/gather_profile_stats.py
  7. +22 −0 django/bin/profiling/handler.py
  8. +45 −0 django/bin/setup.py
  9. +36 −0 django/bin/validate.py
  10. 0  django/conf/__init__.py
  11. 0  django/conf/app_template/__init__.py
  12. +1 −0  django/conf/app_template/models/__init__.py
  13. +3 −0  django/conf/app_template/models/app_name.py
  14. 0  django/conf/app_template/urls/__init__.py
  15. +5 −0 django/conf/app_template/urls/app_name.py
  16. 0  django/conf/app_template/views/__init__.py
  17. +199 −0 django/conf/global_settings.py
  18. 0  django/conf/project_template/__init__.py
  19. 0  django/conf/project_template/apps/__init__.py
  20. 0  django/conf/project_template/settings/__init__.py
  21. +31 −0 django/conf/project_template/settings/main.py
  22. +42 −0 django/conf/settings.py
  23. 0  django/conf/urls/__init__.py
  24. +56 −0 django/conf/urls/admin.py
  25. +6 −0 django/conf/urls/admin_password_reset.py
  26. +12 −0 django/conf/urls/comments.py
  27. +17 −0 django/conf/urls/defaults.py
  28. +5 −0 django/conf/urls/flatfiles.py
  29. +19 −0 django/conf/urls/registration.py
  30. +6 −0 django/conf/urls/rss.py
  31. +5 −0 django/conf/urls/shortcut.py
  32. 0  django/core/__init__.py
  33. +255 −0 django/core/cache.py
  34. +28 −0 django/core/db/__init__.py
  35. 0  django/core/db/backends/__init__.py
  36. +107 −0 django/core/db/backends/mysql.py
  37. +109 −0 django/core/db/backends/postgresql.py
  38. +32 −0 django/core/db/base.py
  39. +42 −0 django/core/db/typecasts.py
  40. +466 −0 django/core/defaultfilters.py
  41. +743 −0 django/core/defaulttags.py
  42. +26 −0 django/core/exceptions.py
  43. +79 −0 django/core/extensions.py
  44. +759 −0 django/core/formfields.py
  45. +157 −0 django/core/handler.py
  46. +51 −0 django/core/mail.py
  47. +2,142 −0 django/core/meta.py
  48. +76 −0 django/core/paginator.py
  49. +136 −0 django/core/rss.py
  50. +488 −0 django/core/template.py
  51. +18 −0 django/core/template_file.py
  52. +142 −0 django/core/template_loader.py
  53. +96 −0 django/core/urlresolvers.py
  54. +420 −0 django/core/validators.py
  55. +22 −0 django/core/xheaders.py
  56. 0  django/middleware/__init__.py
  57. +120 −0 django/middleware/admin.py
  58. +104 −0 django/middleware/common.py
  59. +18 −0 django/middleware/doc.py
  60. +91 −0 django/models/__init__.py
  61. +290 −0 django/models/auth.py
  62. +281 −0 django/models/comments.py
  63. +107 −0 django/models/core.py
  64. 0  django/parts/__init__.py
  65. 0  django/parts/admin/__init__.py
  66. +93 −0 django/parts/admin/doc.py
  67. 0  django/parts/auth/__init__.py
  68. +48 −0 django/parts/auth/anonymoususers.py
  69. +46 −0 django/parts/auth/formfields.py
  70. 0  django/parts/media/__init__.py
  71. +6 −0 django/parts/media/photos.py
  72. +7 −0 django/templatetags/__init__.py
  73. +331 −0 django/templatetags/comments.py
  74. +45 −0 django/templatetags/log.py
  75. 0  django/tests/__init__.py
  76. +119 −0 django/tests/cache_tests.py
  77. +102 −0 django/tests/template_inheritance.py
  78. +707 −0 django/tests/template_tests.py
  79. 0  django/utils/__init__.py
  80. +171 −0 django/utils/datastructures.py
  81. +317 −0 django/utils/dateformat.py
  82. +27 −0 django/utils/dates.py
  83. +152 −0 django/utils/feedgenerator.py
  84. +110 −0 django/utils/html.py
  85. +319 −0 django/utils/httpwrappers.py
  86. +22 −0 django/utils/images.py
  87. +42 −0 django/utils/stopwords.py
  88. +108 −0 django/utils/text.py
  89. +46 −0 django/utils/timesince.py
  90. +13 −0 django/utils/xmlutils.py
  91. 0  django/views/__init__.py
  92. 0  django/views/admin/__init__.py
  93. +328 −0 django/views/admin/doc.py
  94. +1,089 −0 django/views/admin/main.py
  95. +70 −0 django/views/admin/template.py
  96. 0  django/views/auth/__init__.py
  97. +62 −0 django/views/auth/login.py
  98. 0  django/views/comments/__init__.py
  99. +347 −0 django/views/comments/comments.py
  100. +34 −0 django/views/comments/karma.py
  101. +82 −0 django/views/comments/userflags.py
  102. 0  django/views/core/__init__.py
  103. +34 −0 django/views/core/flatfiles.py
  104. 0  django/views/decorators/__init__.py
  105. +12 −0 django/views/decorators/auth.py
  106. +64 −0 django/views/decorators/cache.py
  107. +72 −0 django/views/defaults.py
  108. 0  django/views/generic/__init__.py
  109. +223 −0 django/views/generic/date_based.py
  110. +106 −0 django/views/generic/list_detail.py
  111. 0  django/views/registration/__init__.py
  112. +109 −0 django/views/registration/passwords.py
  113. 0  django/views/rss/__init__.py
  114. +12 −0 django/views/rss/rss.py
View
0  django/__init__.py
No changes.
View
0  django/bin/__init__.py
No changes.
View
15 django/bin/daily_cleanup.py
@@ -0,0 +1,15 @@
+"Daily cleanup file"
+
+from django.core.db import db
+
+DOCUMENTATION_DIRECTORY = '/home/html/documentation/'
+
+def clean_up():
+ # Clean up old database records
+ cursor = db.cursor()
+ cursor.execute("DELETE FROM auth_sessions WHERE start_time < NOW() - INTERVAL '2 weeks'")
+ cursor.execute("DELETE FROM registration_challenges WHERE request_date < NOW() - INTERVAL '1 week'")
+ db.commit()
+
+if __name__ == "__main__":
+ clean_up()
View
412 django/bin/django-admin.py
@@ -0,0 +1,412 @@
+#!/usr/bin/python2.3
+from django.core import db, meta
+import django
+import os, re, sys
+
+MODULE_TEMPLATE = ''' {%% if perms.%(app)s.%(addperm)s or perms.%(app)s.%(changeperm)s %%}
+ <tr>
+ <th>{%% if perms.%(app)s.%(changeperm)s %%}<a href="/%(app)s/%(mod)s/">{%% endif %%}%(name)s{%% if perms.%(app)s.%(changeperm)s %%}</a>{%% endif %%}</th>
+ <td class="x50">{%% if perms.%(app)s.%(addperm)s %%}<a href="/%(app)s/%(mod)s/add/" class="addlink">{%% endif %%}Add{%% if perms.%(app)s.%(addperm)s %%}</a>{%% endif %%}</td>
+ <td class="x75">{%% if perms.%(app)s.%(changeperm)s %%}<a href="/%(app)s/%(mod)s/" class="changelink">{%% endif %%}Change{%% if perms.%(app)s.%(changeperm)s %%}</a>{%% endif %%}</td>
+ </tr>
+ {%% endif %%}'''
+
+APP_ARGS = '[app app ...]'
+
+PROJECT_TEMPLATE_DIR = django.__path__[0] + '/conf/%s_template'
+
+def _get_packages_insert(app_label):
+ return "INSERT INTO packages (label, name) VALUES ('%s', '%s');" % (app_label, app_label)
+
+def _get_permission_codename(action, opts):
+ return '%s_%s' % (action, opts.object_name.lower())
+
+def _get_all_permissions(opts):
+ "Returns (codename, name) for all permissions in the given opts."
+ perms = []
+ if opts.admin:
+ for action in ('add', 'change', 'delete'):
+ perms.append((_get_permission_codename(action, opts), 'Can %s %s' % (action, opts.verbose_name)))
+ return perms + list(opts.permissions)
+
+def _get_permission_insert(name, codename, opts):
+ return "INSERT INTO auth_permissions (name, package, codename) VALUES ('%s', '%s', '%s');" % \
+ (name.replace("'", "''"), opts.app_label, codename)
+
+def _get_contenttype_insert(opts):
+ return "INSERT INTO content_types (name, package, python_module_name) VALUES ('%s', '%s', '%s');" % \
+ (opts.verbose_name, opts.app_label, opts.module_name)
+
+def _is_valid_dir_name(s):
+ return bool(re.search(r'^\w+$', s))
+
+def get_sql_create(mod):
+ "Returns a list of the CREATE TABLE SQL statements for the given module."
+ final_output = []
+ for klass in mod._MODELS:
+ opts = klass._meta
+ table_output = []
+ for f in opts.fields:
+ if isinstance(f, meta.ForeignKey):
+ rel_field = f.rel.to.get_field(f.rel.field_name)
+ # If the foreign key points to an AutoField, the foreign key
+ # should be an IntegerField, not an AutoField. Otherwise, the
+ # foreign key should be the same type of field as the field
+ # to which it points.
+ if rel_field.__class__.__name__ == 'AutoField':
+ data_type = 'IntegerField'
+ else:
+ rel_field.__class__.__name__
+ else:
+ rel_field = f
+ data_type = f.__class__.__name__
+ col_type = db.DATA_TYPES[data_type]
+ if col_type is not None:
+ field_output = [f.name, col_type % rel_field.__dict__]
+ field_output.append('%sNULL' % (not f.null and 'NOT ' or ''))
+ if f.unique:
+ field_output.append('UNIQUE')
+ if f.primary_key:
+ field_output.append('PRIMARY KEY')
+ if f.rel:
+ field_output.append('REFERENCES %s (%s)' % \
+ (f.rel.to.db_table, f.rel.to.get_field(f.rel.field_name).name))
+ table_output.append(' '.join(field_output))
+ if opts.order_with_respect_to:
+ table_output.append('_order %s NULL' % db.DATA_TYPES['IntegerField'])
+ for field_constraints in opts.unique_together:
+ table_output.append('UNIQUE (%s)' % ", ".join(field_constraints))
+
+ full_statement = ['CREATE TABLE %s (' % opts.db_table]
+ for i, line in enumerate(table_output): # Combine and add commas.
+ full_statement.append(' %s%s' % (line, i < len(table_output)-1 and ',' or ''))
+ full_statement.append(');')
+ final_output.append('\n'.join(full_statement))
+
+ for klass in mod._MODELS:
+ opts = klass._meta
+ for f in opts.many_to_many:
+ table_output = ['CREATE TABLE %s_%s (' % (opts.db_table, f.name)]
+ table_output.append(' id %s NOT NULL PRIMARY KEY,' % db.DATA_TYPES['AutoField'])
+ table_output.append(' %s_id %s NOT NULL REFERENCES %s (%s),' % \
+ (opts.object_name.lower(), db.DATA_TYPES['IntegerField'], opts.db_table, opts.pk.name))
+ table_output.append(' %s_id %s NOT NULL REFERENCES %s (%s),' % \
+ (f.rel.to.object_name.lower(), db.DATA_TYPES['IntegerField'], f.rel.to.db_table, f.rel.to.pk.name))
+ table_output.append(' UNIQUE (%s_id, %s_id)' % (opts.object_name.lower(), f.rel.to.object_name.lower()))
+ table_output.append(');')
+ final_output.append('\n'.join(table_output))
+ return final_output
+get_sql_create.help_doc = "Prints the CREATE TABLE SQL statements for the given app(s)."
+get_sql_create.args = APP_ARGS
+
+def get_sql_delete(mod):
+ "Returns a list of the DROP TABLE SQL statements for the given module."
+ try:
+ cursor = db.db.cursor()
+ except:
+ cursor = None
+ output = []
+ for klass in mod._MODELS:
+ try:
+ if cursor is not None:
+ # Check whether the table exists.
+ cursor.execute("SELECT 1 FROM %s LIMIT 1" % klass._meta.db_table)
+ except:
+ # The table doesn't exist, so it doesn't need to be dropped.
+ pass
+ else:
+ output.append("DROP TABLE %s;" % klass._meta.db_table)
+ for klass in mod._MODELS:
+ opts = klass._meta
+ for f in opts.many_to_many:
+ try:
+ if cursor is not None:
+ cursor.execute("SELECT 1 FROM %s_%s LIMIT 1" % (opts.db_table, f.name))
+ except:
+ pass
+ else:
+ output.append("DROP TABLE %s_%s;" % (opts.db_table, f.name))
+ output.append("DELETE FROM packages WHERE label = '%s';" % mod._MODELS[0]._meta.app_label)
+ return output
+get_sql_delete.help_doc = "Prints the DROP TABLE SQL statements for the given app(s)."
+get_sql_delete.args = APP_ARGS
+
+def get_sql_reset(mod):
+ "Returns a list of the DROP TABLE SQL, then the CREATE TABLE SQL, for the given module."
+ return get_sql_delete(mod) + get_sql_all(mod)
+get_sql_reset.help_doc = "Prints the DROP TABLE SQL, then the CREATE TABLE SQL, for the given app(s)."
+get_sql_reset.args = APP_ARGS
+
+def get_sql_initial_data(mod):
+ "Returns a list of the initial INSERT SQL statements for the given module."
+ output = []
+ app_label = mod._MODELS[0]._meta.app_label
+ output.append(_get_packages_insert(app_label))
+ app_dir = os.path.normpath(os.path.join(os.path.dirname(mod.__file__), '../sql'))
+ for klass in mod._MODELS:
+ opts = klass._meta
+ # Add custom SQL, if it's available.
+ sql_file_name = os.path.join(app_dir, opts.module_name + '.sql')
+ if os.path.exists(sql_file_name):
+ fp = open(sql_file_name, 'r')
+ output.append(fp.read())
+ fp.close()
+ # Content types.
+ output.append(_get_contenttype_insert(opts))
+ # Permissions.
+ for codename, name in _get_all_permissions(opts):
+ output.append(_get_permission_insert(name, codename, opts))
+ return output
+get_sql_initial_data.help_doc = "Prints the initial INSERT SQL statements for the given app(s)."
+get_sql_initial_data.args = APP_ARGS
+
+def get_sql_sequence_reset(mod):
+ "Returns a list of the SQL statements to reset PostgreSQL sequences for the given module."
+ output = []
+ for klass in mod._MODELS:
+ for f in klass._meta.fields:
+ if isinstance(f, meta.AutoField):
+ output.append("SELECT setval('%s_%s_seq', (SELECT max(%s) FROM %s));" % (klass._meta.db_table, f.name, f.name, klass._meta.db_table))
+ return output
+get_sql_sequence_reset.help_doc = "Prints the SQL statements for resetting PostgreSQL sequences for the given app(s)."
+get_sql_sequence_reset.args = APP_ARGS
+
+def get_sql_indexes(mod):
+ "Returns a list of the CREATE INDEX SQL statements for the given module."
+ output = []
+ for klass in mod._MODELS:
+ for f in klass._meta.fields:
+ if f.db_index:
+ unique = f.unique and "UNIQUE " or ""
+ output.append("CREATE %sINDEX %s_%s ON %s (%s);" % \
+ (unique, klass._meta.db_table, f.name, klass._meta.db_table, f.name))
+ return output
+get_sql_indexes.help_doc = "Prints the CREATE INDEX SQL statements for the given app(s)."
+get_sql_indexes.args = APP_ARGS
+
+def get_sql_all(mod):
+ "Returns a list of CREATE TABLE SQL and initial-data insert for the given module."
+ return get_sql_create(mod) + get_sql_initial_data(mod)
+get_sql_all.help_doc = "Prints the CREATE TABLE and initial-data SQL statements for the given app(s)."
+get_sql_all.args = APP_ARGS
+
+def database_check(mod):
+ "Checks that everything is properly installed in the database for the given module."
+ cursor = db.db.cursor()
+ app_label = mod._MODELS[0]._meta.app_label
+
+ # Check that the package exists in the database.
+ cursor.execute("SELECT 1 FROM packages WHERE label = %s", [app_label])
+ if cursor.rowcount < 1:
+# sys.stderr.write("The '%s' package isn't installed.\n" % app_label)
+ print _get_packages_insert(app_label)
+
+ # Check that the permissions and content types are in the database.
+ perms_seen = {}
+ contenttypes_seen = {}
+ for klass in mod._MODELS:
+ opts = klass._meta
+ perms = _get_all_permissions(opts)
+ perms_seen.update(dict(perms))
+ contenttypes_seen[opts.module_name] = 1
+ for codename, name in perms:
+ cursor.execute("SELECT 1 FROM auth_permissions WHERE package = %s AND codename = %s", (app_label, codename))
+ if cursor.rowcount < 1:
+# sys.stderr.write("The '%s.%s' permission doesn't exist.\n" % (app_label, codename))
+ print _get_permission_insert(name, codename, opts)
+ cursor.execute("SELECT 1 FROM content_types WHERE package = %s AND python_module_name = %s", (app_label, opts.module_name))
+ if cursor.rowcount < 1:
+# sys.stderr.write("The '%s.%s' content type doesn't exist.\n" % (app_label, opts.module_name))
+ print _get_contenttype_insert(opts)
+
+ # Check that there aren't any *extra* permissions in the DB that the model
+ # doesn't know about.
+ cursor.execute("SELECT codename FROM auth_permissions WHERE package = %s", (app_label,))
+ for row in cursor.fetchall():
+ try:
+ perms_seen[row[0]]
+ except KeyError:
+# sys.stderr.write("A permission called '%s.%s' was found in the database but not in the model.\n" % (app_label, row[0]))
+ print "DELETE FROM auth_permissions WHERE package='%s' AND codename = '%s';" % (app_label, row[0])
+
+ # Check that there aren't any *extra* content types in the DB that the
+ # model doesn't know about.
+ cursor.execute("SELECT python_module_name FROM content_types WHERE package = %s", (app_label,))
+ for row in cursor.fetchall():
+ try:
+ contenttypes_seen[row[0]]
+ except KeyError:
+# sys.stderr.write("A content type called '%s.%s' was found in the database but not in the model.\n" % (app_label, row[0]))
+ print "DELETE FROM content_types WHERE package='%s' AND python_module_name = '%s';" % (app_label, row[0])
+database_check.help_doc = "Checks that everything is installed in the database for the given app(s) and prints SQL statements if needed."
+database_check.args = APP_ARGS
+
+def get_admin_index(mod):
+ "Returns admin-index template snippet (in list form) for the given module."
+ output = []
+ app_label = mod._MODELS[0]._meta.app_label
+ output.append('{%% if perms.%s %%}' % app_label)
+ output.append('<div class="module"><h2>%s</h2><table>' % app_label.title())
+ for klass in mod._MODELS:
+ if klass._meta.admin:
+ output.append(MODULE_TEMPLATE % {
+ 'app': app_label,
+ 'mod': klass._meta.module_name,
+ 'name': meta.capfirst(klass._meta.verbose_name_plural),
+ 'addperm': klass._meta.get_add_permission(),
+ 'changeperm': klass._meta.get_change_permission(),
+ })
+ output.append('</table></div>')
+ output.append('{% endif %}')
+ return output
+get_admin_index.help_doc = "Prints the admin-index template snippet for the given app(s)."
+get_admin_index.args = APP_ARGS
+
+def init():
+ "Initializes the database with auth and core."
+ auth = meta.get_app('auth')
+ core = meta.get_app('core')
+ try:
+ cursor = db.db.cursor()
+ for sql in get_sql_create(core) + get_sql_create(auth) + get_sql_initial_data(core) + get_sql_initial_data(auth):
+ cursor.execute(sql)
+ except Exception, e:
+ sys.stderr.write("Error: The database couldn't be initialized. Here's the full exception:\n%s\n" % e)
+ db.db.rollback()
+ sys.exit(1)
+ db.db.commit()
+init.args = ''
+
+def install(mod):
+ "Executes the equivalent of 'get_sql_all' in the current database."
+ sql_list = get_sql_all(mod)
+ try:
+ cursor = db.db.cursor()
+ for sql in sql_list:
+ cursor.execute(sql)
+ except Exception, e:
+ mod_name = mod.__name__[mod.__name__.rindex('.')+1:]
+ sys.stderr.write("""Error: %s couldn't be installed. Possible reasons:
+ * The database isn't running or isn't configured correctly.
+ * At least one of the database tables already exists.
+ * The SQL was invalid.
+Hint: Look at the output of '%s sqlall %s'. That's the SQL this command wasn't able to run.
+The full error: %s\n""" % \
+ (mod_name, __file__, mod_name, e))
+ db.db.rollback()
+ sys.exit(1)
+ db.db.commit()
+install.args = APP_ARGS
+
+def _start_helper(app_or_project, name, directory, other_name=''):
+ other = {'project': 'app', 'app': 'project'}[app_or_project]
+ if not _is_valid_dir_name(name):
+ sys.stderr.write("Error: %r is not a valid %s name. Please use only numbers, letters and underscores.\n" % (name, app_or_project))
+ sys.exit(1)
+ top_dir = os.path.join(directory, name)
+ try:
+ os.mkdir(top_dir)
+ except OSError, e:
+ sys.stderr.write("Error: %s\n" % e)
+ sys.exit(1)
+ template_dir = PROJECT_TEMPLATE_DIR % app_or_project
+ for d, subdirs, files in os.walk(template_dir):
+ relative_dir = d[len(template_dir)+1:].replace('%s_name' % app_or_project, name)
+ if relative_dir:
+ os.mkdir(os.path.join(top_dir, relative_dir))
+ for f in files:
+ fp_old = open(os.path.join(d, f), 'r')
+ fp_new = open(os.path.join(top_dir, relative_dir, f.replace('%s_name' % app_or_project, name)), 'w')
+ fp_new.write(fp_old.read().replace('{{ %s_name }}' % app_or_project, name).replace('{{ %s_name }}' % other, other_name))
+ fp_old.close()
+ fp_new.close()
+
+def startproject(project_name, directory):
+ "Creates a Django project for the given project_name in the given directory."
+ _start_helper('project', project_name, directory)
+startproject.help_doc = "Creates a Django project directory structure for the given project name in the current directory."
+startproject.args = "[projectname]"
+
+def startapp(app_name, directory):
+ "Creates a Django app for the given project_name in the given directory."
+ # Determine the project_name a bit naively -- by looking at the name of
+ # the parent directory.
+ project_dir = os.path.normpath(os.path.join(directory, '../'))
+ project_name = os.path.basename(project_dir)
+ _start_helper('app', app_name, directory, project_name)
+ settings_file = os.path.join(project_dir, 'settings/main.py')
+ if os.path.exists(settings_file):
+ try:
+ settings_contents = open(settings_file, 'r').read()
+ fp = open(settings_file, 'w')
+ except IOError:
+ pass
+ else:
+ settings_contents = re.sub(r'(?s)\b(INSTALLED_APPS\s*=\s*\()(.*?)\)', "\\1\n '%s',\\2)" % app_name, settings_contents)
+ fp.write(settings_contents)
+ fp.close()
+startapp.help_doc = "Creates a Django app directory structure for the given app name in the current directory."
+startapp.args = "[appname]"
+
+def usage():
+ sys.stderr.write("Usage: %s [action]\n" % sys.argv[0])
+
+ available_actions = ACTION_MAPPING.keys()
+ available_actions.sort()
+ sys.stderr.write("Available actions:\n")
+ for a in available_actions:
+ func = ACTION_MAPPING[a]
+ sys.stderr.write(" %s %s-- %s\n" % (a, func.args, getattr(func, 'help_doc', func.__doc__)))
+ sys.exit(1)
+
+ACTION_MAPPING = {
+ 'adminindex': get_admin_index,
+# 'dbcheck': database_check,
+ 'sql': get_sql_create,
+ 'sqlall': get_sql_all,
+ 'sqlclear': get_sql_delete,
+ 'sqlindexes': get_sql_indexes,
+ 'sqlinitialdata': get_sql_initial_data,
+ 'sqlreset': get_sql_reset,
+ 'sqlsequencereset': get_sql_sequence_reset,
+ 'startapp': startapp,
+ 'startproject': startproject,
+ 'init': init,
+ 'install': install,
+}
+
+if __name__ == "__main__":
+ try:
+ action = sys.argv[1]
+ except IndexError:
+ usage()
+ if not ACTION_MAPPING.has_key(action):
+ usage()
+ if action == 'init':
+ init()
+ sys.exit(0)
+ elif action in ('startapp', 'startproject'):
+ try:
+ name = sys.argv[2]
+ except IndexError:
+ usage()
+ ACTION_MAPPING[action](name, os.getcwd())
+ sys.exit(0)
+ elif action == 'dbcheck':
+ mod_list = meta.get_all_installed_modules()
+ else:
+ try:
+ mod_list = [meta.get_app(app_label) for app_label in sys.argv[2:]]
+ except ImportError, e:
+ sys.stderr.write("Error: %s. Are you sure your INSTALLED_APPS setting is correct?\n" % e)
+ sys.exit(1)
+ if not mod_list:
+ usage()
+ if action not in ('adminindex', 'dbcheck', 'install', 'sqlindexes'):
+ print "BEGIN;"
+ for mod in mod_list:
+ output = ACTION_MAPPING[action](mod)
+ if output:
+ print '\n'.join(output)
+ if action not in ('adminindex', 'dbcheck', 'install', 'sqlindexes'):
+ print "COMMIT;"
View
0  django/bin/profiling/__init__.py
No changes.
View
34 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])
View
22 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 <url>.<n>.prof where <url> is
+ the request path with "/" replaced by ".", and <n> is a timestamp with
+ microseconds to prevent overwriting files.
+
+ Use the gather_profile_stats.py script to gather these individual request
+ profiles into aggregated profiles by request path.
+ '''
+ profname = "%s.%.3f.prof" % (req.uri.strip("/").replace('/', '.'), time.time())
+ profname = os.path.join(PROFILE_DATA_DIR, profname)
+ prof = hotshot.Profile(profname)
+ return prof.runcall(CoreHandler(), req)
View
45 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',
+)
View
36 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)
View
0  django/conf/__init__.py
No changes.
View
0  django/conf/app_template/__init__.py
No changes.
View
1  django/conf/app_template/models/__init__.py
@@ -0,0 +1 @@
+__all__ = ['{{ app_name }}']
View
3  django/conf/app_template/models/app_name.py
@@ -0,0 +1,3 @@
+from django.core import meta
+
+# Create your models here.
View
0  django/conf/app_template/urls/__init__.py
No changes.
View
5 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'', ''),
+)
View
0  django/conf/app_template/views/__init__.py
No changes.
View
199 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'
View
0  django/conf/project_template/__init__.py
No changes.
View
0  django/conf/project_template/apps/__init__.py
No changes.
View
0  django/conf/project_template/settings/__init__.py
No changes.
View
31 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 = (
+)
View
42 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
+
View
0  django/conf/urls/__init__.py
No changes.
View
56 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<view>[^/]+)/$', 'django.views.admin.doc.view_detail'),
+ ('^doc/models/$', 'django.views.admin.doc.model_index'),
+ ('^doc/models/(?P<model>[^/]+)/$', 'django.views.admin.doc.model_detail'),
+)
+
+if 'ellington.events' in INSTALLED_APPS:
+ urlpatterns += (
+ ("^events/usersubmittedevents/(?P<object_id>\d+)/$", 'ellington.events.views.admin.user_submitted_event_change_stage'),
+ ("^events/usersubmittedevents/(?P<object_id>\d+)/delete/$", 'ellington.events.views.admin.user_submitted_event_delete_stage'),
+ )
+
+if 'ellington.news' in INSTALLED_APPS:
+ urlpatterns += (
+ ("^stories/preview/$", 'ellington.news.views.admin.story_preview'),
+ ("^stories/js/inlinecontrols/$", 'ellington.news.views.admin.inlinecontrols_js'),
+ ("^stories/js/inlinecontrols/(?P<label>[-\w]+)/$", 'ellington.news.views.admin.inlinecontrols_js_specific'),
+ )
+
+if 'ellington.alerts' in INSTALLED_APPS:
+ urlpatterns += (
+ ("^alerts/send/$", 'ellington.alerts.views.admin.send_alert_form'),
+ ("^alerts/send/do/$", 'ellington.alerts.views.admin.send_alert_action'),
+ )
+
+if 'ellington.media' in INSTALLED_APPS:
+ urlpatterns += (
+ ('^media/photos/caption/(?P<photo_id>\d+)/$', 'ellington.media.views.admin.get_exif_caption'),
+ )
+
+urlpatterns += (
+ # Metasystem admin pages
+ ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/$', 'django.views.admin.main.change_list'),
+ ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/add/$', 'django.views.admin.main.add_stage'),
+ ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/(?P<object_id>\d+)/$', 'django.views.admin.main.change_stage'),
+ ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/(?P<object_id>\d+)/delete/$', 'django.views.admin.main.delete_stage'),
+ ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/(?P<object_id>\d+)/history/$', 'django.views.admin.main.history'),
+ ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/jsvalidation/$', 'django.views.admin.jsvalidation.jsvalidation'),
+)
+urlpatterns = patterns('', *urlpatterns)
View
6 django/conf/urls/admin_password_reset.py
@@ -0,0 +1,6 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('django.views',
+ (r'^/?$', 'registration.passwords.password_reset', {'is_admin_site' : True}),
+ (r'^done/$', 'registration.passwords.password_reset_done'),
+)
View
12 django/conf/urls/comments.py
@@ -0,0 +1,12 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('django.views',
+ (r'^post/$', 'comments.comments.post_comment'),
+ (r'^postfree/$', 'comments.comments.post_free_comment'),
+ (r'^posted/$', 'comments.comments.comment_was_posted'),
+ (r'^karma/vote/(?P<comment_id>\d+)/(?P<vote>up|down)/$', 'comments.karma.vote'),
+ (r'^flag/(?P<comment_id>\d+)/$', 'comments.userflags.flag'),
+ (r'^flag/(?P<comment_id>\d+)/done/$', 'comments.userflags.flag_done'),
+ (r'^delete/(?P<comment_id>\d+)/$', 'comments.userflags.delete'),
+ (r'^delete/(?P<comment_id>\d+)/done/$', 'comments.userflags.delete_done'),
+)
View
17 django/conf/urls/defaults.py
@@ -0,0 +1,17 @@
+from django.core.urlresolvers import RegexURLMultiplePattern, RegexURLPattern
+
+__all__ = ['handler404', 'handler500', 'include', 'patterns']
+
+handler404 = 'django.views.defaults.page_not_found'
+handler500 = 'django.views.defaults.server_error'
+
+include = lambda urlconf_module: [urlconf_module]
+
+def patterns(prefix, *tuples):
+ pattern_list = []
+ for t in tuples:
+ if type(t[1]) == list:
+ pattern_list.append(RegexURLMultiplePattern(t[0], t[1][0]))
+ else:
+ pattern_list.append(RegexURLPattern(t[0], prefix and (prefix + '.' + t[1]) or t[1], *t[2:]))
+ return pattern_list
View
5 django/conf/urls/flatfiles.py
@@ -0,0 +1,5 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('django.views',
+ (r'^(?P<url>.*)$', 'core.flatfiles.flat_file'),
+)
View
19 django/conf/urls/registration.py
@@ -0,0 +1,19 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('',
+ (r'^login/$', 'django.views.auth.login.login'),
+ (r'^logout/$', 'django.views.auth.login.logout'),
+ (r'^login_another/$', 'django.views.auth.login.logout_then_login'),
+
+ (r'^register/$', 'ellington.registration.views.registration.signup'),
+ (r'^register/(?P<challenge_string>\w{32})/$', 'ellington.registration.views.registration.register_form'),
+
+ (r'^profile/$', 'ellington.registration.views.profile.profile'),
+ (r'^profile/welcome/$', 'ellington.registration.views.profile.profile_welcome'),
+ (r'^profile/edit/$', 'ellington.registration.views.profile.edit_profile'),
+
+ (r'^password_reset/$', 'django.views.registration.passwords.password_reset'),
+ (r'^password_reset/done/$', 'django.views.registration.passwords.password_reset_done'),
+ (r'^password_change/$', 'django.views.registration.passwords.password_change'),
+ (r'^password_change/done/$', 'django.views.registration.passwords.password_change_done'),
+)
View
6 django/conf/urls/rss.py
@@ -0,0 +1,6 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('django.views',
+ (r'^(?P<slug>\w+)/$', 'rss.rss.feed'),
+ (r'^(?P<slug>\w+)/(?P<param>[\w/]+)/$', 'rss.rss.feed'),
+)
View
5 django/conf/urls/shortcut.py
@@ -0,0 +1,5 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('django.views',
+ (r'^(?P<content_type_id>\d+)/(?P<object_id>\d+)/$', 'defaults.shortcut'),
+)
View
0  django/core/__init__.py
No changes.
View
255 django/core/cache.py
@@ -0,0 +1,255 @@
+"""
+Caching framework.
+
+This module defines set of cache backends that all conform to a simple API.
+In a nutshell, a cache is a set of values -- which can be any object that
+may be pickled -- identified by string keys. For the complete API, see
+the abstract Cache object, below.
+
+Client code should not access a cache backend directly; instead
+it should use the get_cache() function. This function will look at
+settings.CACHE_BACKEND and use that to create and load a cache object.
+
+The CACHE_BACKEND setting is a quasi-URI; examples are:
+
+ memcached://127.0.0.1:11211/ A memcached backend; the server is running
+ on localhost port 11211.
+
+ pgsql://tablename/ A pgsql backend (the pgsql backend uses
+ the same database/username as the rest of
+ the CMS, so only a table name is needed.)
+
+ file:///var/tmp/django.cache/ A file-based cache at /var/tmp/django.cache
+
+ simple:/// A simple single-process memory cache; you
+ probably don't want to use this except for
+ testing. Note that this cache backend is
+ NOT threadsafe!
+
+All caches may take arguments; these are given in query-string style. Valid
+arguments are:
+
+ timeout
+ Default timeout, in seconds, to use for the cache. Defaults
+ to 5 minutes (300 seconds).
+
+ max_entries
+ For the simple, file, and database backends, the maximum number of
+ entries allowed in the cache before it is cleaned. Defaults to
+ 300.
+
+ cull_percentage
+ The percentage of entries that are culled when max_entries is reached.
+ The actual percentage is 1/cull_percentage, so set cull_percentage=3 to
+ cull 1/3 of the entries when max_entries is reached.
+
+ A value of 0 for cull_percentage means that the entire cache will be
+ dumped when max_entries is reached. This makes culling *much* faster
+ at the expense of more cache misses.
+
+For example:
+
+ memcached://127.0.0.1:11211/?timeout=60
+ pgsql://tablename/?timeout=120&max_entries=500&cull_percentage=4
+
+Invalid arguments are silently ignored, as are invalid values of known
+arguments.
+
+So far, only the memcached and simple backend have been implemented; backends
+using postgres, and file-system storage are planned.
+"""
+
+##############
+# Exceptions #
+##############
+
+class InvalidCacheBackendError(Exception):
+ pass
+
+################################
+# Abstract base implementation #
+################################
+
+class _Cache:
+
+ def __init__(self, params):
+ timeout = params.get('timeout', 300)
+ try:
+ timeout = int(timeout)
+ except (ValueError, TypeError):
+ timeout = 300
+ self.default_timeout = timeout
+
+ def get(self, key, default=None):
+ '''
+ Fetch a given key from the cache. If the key does not exist, return
+ default, which itself defaults to None.
+ '''
+ raise NotImplementedError
+
+ def set(self, key, value, timeout=None):
+ '''
+ Set a value in the cache. If timeout is given, that timeout will be
+ used for the key; otherwise the default cache timeout will be used.
+ '''
+ raise NotImplementedError
+
+ def delete(self, key):
+ '''
+ Delete a key from the cache, failing silently.
+ '''
+ raise NotImplementedError
+
+ def get_many(self, keys):
+ '''
+ Fetch a bunch of keys from the cache. For certain backends (memcached,
+ pgsql) this can be *much* faster when fetching multiple values.
+
+ Returns a dict mapping each key in keys to its value. If the given
+ key is missing, it will be missing from the response dict.
+ '''
+ d = {}
+ for k in keys:
+ val = self.get(k)
+ if val is not None:
+ d[k] = val
+ return d
+
+ def has_key(self, key):
+ '''
+ Returns True if the key is in the cache and has not expired.
+ '''
+ return self.get(key) is not None
+
+###########################
+# memcached cache backend #
+###########################
+
+try:
+ import memcache
+except ImportError:
+ _MemcachedCache = None
+else:
+ class _MemcachedCache(_Cache):
+ """Memcached cache backend."""
+
+ def __init__(self, server, params):
+ _Cache.__init__(self, params)
+ self._cache = memcache.Client([server])
+
+ def get(self, key, default=None):
+ val = self._cache.get(key)
+ if val is None:
+ return default
+ else:
+ return val
+
+ def set(self, key, value, timeout=0):
+ self._cache.set(key, value, timeout)
+
+ def delete(self, key):
+ self._cache.delete(key)
+
+ def get_many(self, keys):
+ return self._cache.get_multi(keys)
+
+##################################
+# Single-process in-memory cache #
+##################################
+
+import time
+
+class _SimpleCache(_Cache):
+ """Simple single-process in-memory cache"""
+
+ def __init__(self, host, params):
+ _Cache.__init__(self, params)
+ self._cache = {}
+ self._expire_info = {}
+
+ max_entries = params.get('max_entries', 300)
+ try:
+ self._max_entries = int(max_entries)
+ except (ValueError, TypeError):
+ self._max_entries = 300
+
+ cull_frequency = params.get('cull_frequency', 3)
+ try:
+ self._cull_frequency = int(cull_frequency)
+ except (ValueError, TypeError):
+ self._cull_frequency = 3
+
+ def get(self, key, default=None):
+ now = time.time()
+ exp = self._expire_info.get(key, now)
+ if exp is not None and exp < now:
+ del self._cache[key]
+ del self._expire_info[key]
+ return default
+ else:
+ return self._cache.get(key, default)
+
+ def set(self, key, value, timeout=None):
+ if len(self._cache) >= self._max_entries:
+ self._cull()
+ if timeout is None:
+ timeout = self.default_timeout
+ self._cache[key] = value
+ self._expire_info[key] = time.time() + timeout
+
+ def delete(self, key):
+ try:
+ del self._cache[key]
+ except KeyError:
+ pass
+ try:
+ del self._expire_info[key]
+ except KeyError:
+ pass
+
+ def has_key(self, key):
+ return self._cache.has_key(key)
+
+ def _cull(self):
+ if self._cull_frequency == 0:
+ self._cache.clear()
+ self._expire_info.clear()
+ else:
+ doomed = [k for (i, k) in enumerate(self._cache) if i % self._cull_frequency == 0]
+ for k in doomed:
+ self.delete(k)
+
+##########################################
+# Read settings and load a cache backend #
+##########################################
+
+from cgi import parse_qsl
+
+_BACKENDS = {
+ 'memcached' : _MemcachedCache,
+ 'simple' : _SimpleCache,
+}
+
+def get_cache(backend_uri):
+ if backend_uri.find(':') == -1:
+ raise InvalidCacheBackendError("Backend URI must start with scheme://")
+ scheme, rest = backend_uri.split(':', 1)
+ if not rest.startswith('//'):
+ raise InvalidCacheBackendError("Backend URI must start with scheme://")
+ if scheme not in _BACKENDS.keys():
+ raise InvalidCacheBackendError("%r is not a valid cache backend" % scheme)
+
+ host = rest[2:]
+ qpos = rest.find('?')
+ if qpos != -1:
+ params = dict(parse_qsl(rest[qpos+1:]))
+ host = rest[:qpos]
+ else:
+ params = {}
+ if host.endswith('/'):
+ host = host[:-1]
+
+ return _BACKENDS[scheme](host, params)
+
+from django.conf.settings import CACHE_BACKEND
+cache = get_cache(CACHE_BACKEND)
View
28 django/core/db/__init__.py
@@ -0,0 +1,28 @@
+"""
+This is the core database connection.
+
+All CMS code assumes database SELECT statements cast the resulting values as such:
+ * booleans are mapped to Python booleans
+ * dates are mapped to Python datetime.date objects
+ * times are mapped to Python datetime.time objects
+ * timestamps are mapped to Python datetime.datetime objects
+
+Right now, we're handling this by using psycopg's custom typecast definitions.
+If we move to a different database module, we should ensure that it either
+performs the appropriate typecasting out of the box, or that it has hooks that
+let us do that.
+"""
+
+from django.conf.settings import DATABASE_ENGINE
+
+dbmod = __import__('django.core.db.backends.%s' % DATABASE_ENGINE, '', '', [''])
+
+DatabaseError = dbmod.DatabaseError
+db = dbmod.DatabaseWrapper()
+dictfetchone = dbmod.dictfetchone
+dictfetchmany = dbmod.dictfetchmany
+dictfetchall = dbmod.dictfetchall
+dictfetchall = dbmod.dictfetchall
+get_last_insert_id = dbmod.get_last_insert_id
+OPERATOR_MAPPING = dbmod.OPERATOR_MAPPING
+DATA_TYPES = dbmod.DATA_TYPES
View
0  django/core/db/backends/__init__.py
No changes.
View
107 django/core/db/backends/mysql.py
@@ -0,0 +1,107 @@
+"""
+MySQL database backend for Django.
+
+Requires MySQLdb: http://sourceforge.net/projects/mysql-python
+"""
+
+from django.core.db import base, typecasts
+import MySQLdb as Database
+from MySQLdb.converters import conversions
+from MySQLdb.constants import FIELD_TYPE
+import types
+
+DatabaseError = Database.DatabaseError
+
+django_conversions = conversions.copy()
+django_conversions.update({
+ types.BooleanType: typecasts.rev_typecast_boolean,
+ FIELD_TYPE.DATETIME: typecasts.typecast_timestamp,
+ FIELD_TYPE.DATE: typecasts.typecast_date,
+ FIELD_TYPE.TIME: typecasts.typecast_time,
+})
+
+class DatabaseWrapper:
+ def __init__(self):
+ self.connection = None
+ self.queries = []
+
+ def cursor(self):
+ from django.conf.settings import DATABASE_USER, DATABASE_NAME, DATABASE_HOST, DATABASE_PASSWORD, DEBUG
+ if self.connection is None:
+ self.connection = Database.connect(user=DATABASE_USER, db=DATABASE_NAME,
+ passwd=DATABASE_PASSWORD, host=DATABASE_HOST, conv=django_conversions)
+ if DEBUG:
+ return base.CursorDebugWrapper(self.connection.cursor(), self)
+ return self.connection.cursor()
+
+ def commit(self):
+ pass
+
+ def rollback(self):
+ pass
+
+ def close(self):
+ if self.connection is not None:
+ self.connection.close()
+ self.connection = None
+
+def dictfetchone(cursor):
+ "Returns a row from the cursor as a dict"
+ raise NotImplementedError
+
+def dictfetchmany(cursor, number):
+ "Returns a certain number of rows from a cursor as a dict"
+ raise NotImplementedError
+
+def dictfetchall(cursor):
+ "Returns all rows from a cursor as a dict"
+ raise NotImplementedError
+
+def get_last_insert_id(cursor, table_name, pk_name):
+ cursor.execute("SELECT LAST_INSERT_ID()")
+ return cursor.fetchone()[0]
+
+OPERATOR_MAPPING = {
+ 'exact': '=',
+ 'iexact': 'LIKE',
+ 'contains': 'LIKE',
+ 'icontains': 'LIKE',
+ 'ne': '!=',
+ 'gt': '>',
+ 'gte': '>=',
+ 'lt': '<',
+ 'lte': '<=',
+ 'startswith': 'LIKE',
+ 'endswith': 'LIKE'
+}
+
+# This dictionary maps Field objects to their associated MySQL column
+# types, as strings. Column-type strings can contain format strings; they'll
+# be interpolated against the values of Field.__dict__ before being output.
+# If a column type is set to None, it won't be included in the output.
+DATA_TYPES = {
+ 'AutoField': 'mediumint(9) auto_increment',
+ 'BooleanField': 'bool',
+ 'CharField': 'varchar(%(maxlength)s)',
+ 'CommaSeparatedIntegerField': 'varchar(%(maxlength)s)',
+ 'DateField': 'date',
+ 'DateTimeField': 'datetime',
+ 'EmailField': 'varchar(75)',
+ 'FileField': 'varchar(100)',
+ 'FloatField': 'numeric(%(max_digits)s, %(decimal_places)s)',
+ 'ImageField': 'varchar(100)',
+ 'IntegerField': 'integer',
+ 'IPAddressField': 'char(15)',
+ 'ManyToManyField': None,
+ 'NullBooleanField': 'bool',
+ 'PhoneNumberField': 'varchar(20)',
+ 'PositiveIntegerField': 'integer UNSIGNED',
+ 'PositiveSmallIntegerField': 'smallint UNSIGNED',
+ 'SlugField': 'varchar(50)',
+ 'SmallIntegerField': 'smallint',
+ 'TextField': 'text',
+ 'TimeField': 'time',
+ 'URLField': 'varchar(200)',
+ 'USStateField': 'varchar(2)',
+ 'XMLField': 'text',
+}
View
109 django/core/db/backends/postgresql.py
@@ -0,0 +1,109 @@
+"""
+PostgreSQL database backend for Django.
+
+Requires psycopg 1: http://initd.org/projects/psycopg1
+"""
+
+from django.core.db import base, typecasts
+import psycopg as Database
+
+DatabaseError = Database.DatabaseError
+
+class DatabaseWrapper:
+ def __init__(self):
+ self.connection = None
+ self.queries = []
+
+ def cursor(self):
+ from django.conf.settings import DATABASE_USER, DATABASE_NAME, DATABASE_HOST, DATABASE_PASSWORD, DEBUG, TIME_ZONE
+ if self.connection is None:
+ # Note that "host=" has to be last, because it might be blank.
+ self.connection = Database.connect("user=%s dbname=%s password=%s host=%s" % \
+ (DATABASE_USER, DATABASE_NAME, DATABASE_PASSWORD, DATABASE_HOST))
+ self.connection.set_isolation_level(1) # make transactions transparent to all cursors
+ cursor = self.connection.cursor()
+ cursor.execute("SET TIME ZONE %s", [TIME_ZONE])
+ if DEBUG:
+ return base.CursorDebugWrapper(cursor, self)
+ return cursor
+
+ def commit(self):
+ return self.connection.commit()
+
+ def rollback(self):
+ if self.connection:
+ return self.connection.rollback()
+
+ def close(self):
+ if self.connection is not None:
+ self.connection.close()
+ self.connection = None
+
+def dictfetchone(cursor):
+ "Returns a row from the cursor as a dict"
+ return cursor.dictfetchone()
+
+def dictfetchmany(cursor, number):
+ "Returns a certain number of rows from a cursor as a dict"
+ return cursor.dictfetchmany(number)
+
+def dictfetchall(cursor):
+ "Returns all rows from a cursor as a dict"
+ return cursor.dictfetchall()
+
+def get_last_insert_id(cursor, table_name, pk_name):
+ cursor.execute("SELECT CURRVAL('%s_%s_seq')" % (table_name, pk_name))
+ return cursor.fetchone()[0]
+
+# Register these custom typecasts, because Django expects dates/times to be
+# in Python's native (standard-library) datetime/time format, whereas psycopg
+# use mx.DateTime by default.
+Database.register_type(Database.new_type((1082,), "DATE", typecasts.typecast_date))
+Database.register_type(Database.new_type((1083,1266), "TIME", typecasts.typecast_time))
+Database.register_type(Database.new_type((1114,1184), "TIMESTAMP", typecasts.typecast_timestamp))
+Database.register_type(Database.new_type((16,), "BOOLEAN", typecasts.typecast_boolean))
+
+OPERATOR_MAPPING = {
+ 'exact': '=',
+ 'iexact': 'ILIKE',
+ 'contains': 'LIKE',
+ 'icontains': 'ILIKE',
+ 'ne': '!=',
+ 'gt': '>',
+ 'gte': '>=',
+ 'lt': '<',
+ 'lte': '<=',
+ 'startswith': 'LIKE',
+ 'endswith': 'LIKE'
+}
+
+# This dictionary maps Field objects to their associated PostgreSQL column
+# types, as strings. Column-type strings can contain format strings; they'll
+# be interpolated against the values of Field.__dict__ before being output.
+# If a column type is set to None, it won't be included in the output.
+DATA_TYPES = {
+ 'AutoField': 'serial',
+ 'BooleanField': 'boolean',
+ 'CharField': 'varchar(%(maxlength)s)',
+ 'CommaSeparatedIntegerField': 'varchar(%(maxlength)s)',
+ 'DateField': 'date',
+ 'DateTimeField': 'timestamp with time zone',
+ 'EmailField': 'varchar(75)',
+ 'FileField': 'varchar(100)',
+ 'FloatField': 'numeric(%(max_digits)s, %(decimal_places)s)',
+ 'ImageField': 'varchar(100)',
+ 'IntegerField': 'integer',
+ 'IPAddressField': 'inet',
+ 'ManyToManyField': None,
+ 'NullBooleanField': 'boolean',
+ 'PhoneNumberField': 'varchar(20)',
+ 'PositiveIntegerField': 'integer CHECK (%(name)s >= 0)',
+ 'PositiveSmallIntegerField': 'smallint CHECK (%(name)s >= 0)',
+ 'SlugField': 'varchar(50)',
+ 'SmallIntegerField': 'smallint',
+ 'TextField': 'text',
+ 'TimeField': 'time',
+ 'URLField': 'varchar(200)',
+ 'USStateField': 'varchar(2)',
+ 'XMLField': 'text',
+}
View
32 django/core/db/base.py
@@ -0,0 +1,32 @@
+from time import time
+
+class CursorDebugWrapper:
+ def __init__(self, cursor, db):
+ self.cursor = cursor
+ self.db = db
+
+ def execute(self, sql, params=[]):
+ start = time()
+ result = self.cursor.execute(sql, params)
+ stop = time()
+ self.db.queries.append({
+ 'sql': sql % tuple(params),
+ 'time': "%.3f" % (stop - start),
+ })
+ return result
+
+ def executemany(self, sql, param_list):
+ start = time()
+ result = self.cursor.executemany(sql, param_list)
+ stop = time()
+ self.db.queries.append({
+ 'sql': 'MANY: ' + sql + ' ' + str(tuple(param_list)),
+ 'time': "%.3f" % (stop - start),
+ })
+ return result
+
+ def __getattr__(self, attr):
+ if self.__dict__.has_key(attr):
+ return self.__dict__[attr]
+ else:
+ return getattr(self.cursor, attr)
View
42 django/core/db/typecasts.py
@@ -0,0 +1,42 @@
+import datetime
+
+###############################################
+# Converters from database (string) to Python #
+###############################################
+
+def typecast_date(s):
+ return s and datetime.date(*map(int, s.split('-'))) # returns None if s is null
+
+def typecast_time(s): # does NOT store time zone information
+ if not s: return None
+ bits = s.split(':')
+ if len(bits[2].split('.')) > 1: # if there is a decimal (e.g. '11:16:36.181305')
+ return datetime.time(int(bits[0]), int(bits[1]), int(bits[2].split('.')[0]),
+ int(bits[2].split('.')[1].split('-')[0]))
+ else: # no decimal was found (e.g. '12:30:00')
+ return datetime.time(int(bits[0]), int(bits[1]), int(bits[2].split('.')[0]), 0)
+
+def typecast_timestamp(s): # does NOT store time zone information
+ if not s: return None
+ d, t = s.split()
+ dates = d.split('-')
+ times = t.split(':')
+ seconds = times[2]
+ if '.' in seconds: # check whether seconds have a fractional part
+ seconds, microseconds = seconds.split('.')
+ else:
+ microseconds = '0'
+ return datetime.datetime(int(dates[0]), int(dates[1]), int(dates[2]),
+ int(times[0]), int(times[1]), int(seconds.split('-')[0]),
+ int(microseconds.split('-')[0]))
+
+def typecast_boolean(s):
+ if s is None: return None
+ return str(s)[0].lower() == 't'
+
+###############################################
+# Converters from Python to database (string) #
+###############################################
+
+def rev_typecast_boolean(obj, d):
+ return obj and '1' or '0'
View
466 django/core/defaultfilters.py
@@ -0,0 +1,466 @@
+"Default variable filters"
+
+import template, re, random
+
+###################
+# STRINGS #
+###################
+
+def addslashes(value, _):
+ "Adds slashes - useful for passing strings to JavaScript, for example."
+ return value.replace('"', '\\"').replace("'", "\\'")
+
+def capfirst(value, _):
+ "Capitalizes the first character of the value"
+ value = str(value)
+ return value and value[0].upper() + value[1:]
+
+def fix_ampersands(value, _):
+ "Replaces ampersands with ``&amp;`` entities"
+ from django.utils.html import fix_ampersands
+ return fix_ampersands(value)
+
+def floatformat(text, _):
+ """
+ Displays a floating point number as 34.2 (with one decimal places) - but
+ only if there's a point to be displayed
+ """
+ if not text:
+ return ''
+ if text - int(text) < 0.1:
+ return int(text)
+ return "%.1f" % text
+
+def linenumbers(value, _):
+ "Displays text with line numbers"
+ from django.utils.html import escape
+ lines = value.split('\n')
+ # Find the maximum width of the line count, for use with zero padding string format command
+ width = str(len(str(len(lines))))
+ for i, line in enumerate(lines):
+ lines[i] = ("%0" + width + "d. %s") % (i + 1, escape(line))
+ return '\n'.join(lines)
+
+def lower(value, _):
+ "Converts a string into all lowercase"
+ return value.lower()
+
+def make_list(value, _):
+ """
+ Returns the value turned into a list. For an integer, it's a list of
+ digits. For a string, it's a list of characters.
+ """
+ return list(str(value))
+
+def slugify(value, _):
+ "Converts to lowercase, removes non-alpha chars and converts spaces to hyphens"
+ value = re.sub('[^\w\s]', '', value).strip().lower()
+ return re.sub('\s+', '-', value)
+
+def stringformat(value, arg):
+ """
+ Formats the variable according to the argument, a string formatting specifier.
+ This specifier uses Python string formating syntax, with the exception that
+ the leading "%" is dropped.
+
+ See http://docs.python.org/lib/typesseq-strings.html for documentation
+ of Python string formatting
+ """
+ try:
+ return ("%" + arg) % value
+ except (ValueError, TypeError):
+ return ""
+
+def title(value, _):
+ "Converts a string into titlecase"
+ return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
+
+def truncatewords(value, arg):
+ """
+ Truncates a string after a certain number of words
+
+ Argument: Number of words to truncate after
+ """
+ from django.utils.text import truncate_words
+ try:
+ length = int(arg)
+ except ValueError: # invalid literal for int()
+ return value # Fail silently.
+ if not isinstance(value, basestring):
+ value = str(value)
+ return truncate_words(value, length)
+
+def upper(value, _):
+ "Converts a string into all uppercase"
+ return value.upper()
+
+def urlencode(value, _):
+ "Escapes a value for use in a URL"
+ import urllib
+ return urllib.quote(value)
+
+def urlize(value, _):
+ "Converts URLs in plain text into clickable links"
+ from django.utils.html import urlize
+ return urlize(value, nofollow=True)
+
+def urlizetrunc(value, limit):
+ """
+ Converts URLs into clickable links, truncating URLs to the given character limit
+
+ Argument: Length to truncate URLs to.
+ """
+ from django.utils.html import urlize
+ return urlize(value, trim_url_limit=int(limit), nofollow=True)
+
+def wordcount(value, _):
+ "Returns the number of words"
+ return len(value.split())
+
+def wordwrap(value, arg):
+ """
+ Wraps words at specified line length
+
+ Argument: number of words to wrap the text at.
+ """
+ from django.utils.text import wrap
+ return wrap(value, int(arg))
+
+def ljust(value, arg):
+ """
+ Left-aligns the value in a field of a given width
+
+ Argument: field size
+ """
+ return str(value).ljust(int(arg))
+
+def rjust(value, arg):
+ """
+ Right-aligns the value in a field of a given width
+
+ Argument: field size
+ """
+ return str(value).rjust(int(arg))
+
+def center(value, arg):
+ "Centers the value in a field of a given width"
+ return str(value).center(int(arg))
+
+def cut(value, arg):
+ "Removes all values of arg from the given string"
+ return value.replace(arg, '')
+
+###################
+# HTML STRINGS #
+###################
+
+def escape(value, _):
+ "Escapes a string's HTML"
+ from django.utils.html import escape
+ return escape(value)
+
+def linebreaks(value, _):
+ "Converts newlines into <p> and <br />s"
+ from django.utils.html import linebreaks
+ return linebreaks(value)
+
+def linebreaksbr(value, _):
+ "Converts newlines into <br />s"
+ return value.replace('\n', '<br />')
+
+def removetags(value, tags):
+ "Removes a space separated list of [X]HTML tags from the output"
+ tags = [re.escape(tag) for tag in tags.split()]
+ tags_re = '(%s)' % '|'.join(tags)
+ starttag_re = re.compile('<%s(>|(\s+[^>]*>))' % tags_re)
+ endtag_re = re.compile('</%s>' % tags_re)
+ value = starttag_re.sub('', value)
+ value = endtag_re.sub('', value)
+ return value
+
+def striptags(value, _):
+ "Strips all [X]HTML tags"
+ from django.utils.html import strip_tags
+ if not isinstance(value, basestring):
+ value = str(value)
+ return strip_tags(value)
+
+###################
+# LISTS #
+###################
+
+def dictsort(value, arg):
+ """
+ Takes a list of dicts, returns that list sorted by the property given in
+ the argument.
+ """
+ decorated = [(template.resolve_variable('var.' + arg, {'var' : item}), item) for item in value]
+ decorated.sort()
+ return [item[1] for item in decorated]
+
+def dictsortreversed(value, arg):
+ """
+ Takes a list of dicts, returns that list sorted in reverse order by the
+ property given in the argument.
+ """
+ decorated = [(template.resolve_variable('var.' + arg, {'var' : item}), item) for item in value]
+ decorated.sort()
+ decorated.reverse()
+ return [item[1] for item in decorated]
+
+def first(value, _):
+ "Returns the first item in a list"
+ try:
+ return value[0]
+ except IndexError:
+ return ''
+
+def join(value, arg):
+ "Joins a list with a string, like Python's ``str.join(list)``"
+ try:
+ return arg.join(map(str, value))
+ except AttributeError: # fail silently but nicely
+ return value
+
+def length(value, _):
+ "Returns the length of the value - useful for lists"
+ return len(value)
+
+def length_is(value, arg):
+ "Returns a boolean of whether the value's length is the argument"
+ return len(value) == int(arg)
+
+def random(value, _):
+ "Returns a random item from the list"
+ return random.choice(value)
+
+def slice_(value, arg):
+ """
+ Returns a slice of the list.
+
+ Uses the same syntax as Python's list slicing; see
+ http://diveintopython.org/native_data_types/lists.html#odbchelper.list.slice
+ for an introduction.
+ """
+ try:
+ start, finish = arg.split(':')
+ except ValueError: # unpack list of wrong size
+ return value # fail silently but nicely
+ try:
+ if start and finish:
+ return value[int(start):int(finish)]
+ if start:
+ return value[int(start):]
+ if finish:
+ return value[:int(finish)]
+ except TypeError:
+ pass
+ return value
+
+def unordered_list(value, _):
+ """
+ Recursively takes a self-nested list and returns an HTML unordered list --
+ WITHOUT opening and closing <ul> tags.
+
+ The list is assumed to be in the proper format. For example, if ``var`` contains
+ ``['States', [['Kansas', [['Lawrence', []], ['Topeka', []]]], ['Illinois', []]]]``,
+ then ``{{ var|unordered_list }}`` would return::
+
+ <li>States
+ <ul>
+ <li>Kansas
+ <ul>
+ <li>Lawrence</li>
+ <li>Topeka</li>
+ </ul>
+ </li>
+ <li>Illinois</li>
+ </ul>
+ </li>
+ """
+ def _helper(value, tabs):
+ indent = '\t' * tabs
+ if value[1]:
+ return '%s<li>%s\n%s<ul>\n%s\n%s</ul>\n%s</li>' % (indent, value[0], indent,
+ '\n'.join([unordered_list(v, tabs+1) for v in value[1]]), indent, indent)
+ else:
+ return '%s<li>%s</li>' % (indent, value[0])
+ return _helper(value, 1)
+
+###################
+# INTEGERS #
+###################
+
+def add(value, arg):
+ "Adds the arg to the value"
+ return int(value) + int(arg)
+
+def get_digit(value, arg):
+ """
+ Given a whole number, returns the requested digit of it, where 1 is the
+ right-most digit, 2 is the second-right-most digit, etc. Returns the
+ original value for invalid input (if input or argument is not an integer,
+ or if argument is less than 1). Otherwise, output is always an integer.
+ """
+ try:
+ arg = int(arg)
+ value = int(value)
+ except ValueError:
+ return value # Fail silently for an invalid argument
+ if arg < 1:
+ return value
+ try:
+ return int(str(value)[-arg])
+ except IndexError:
+ return 0
+
+###################
+# DATES #
+###################
+
+def date(value, arg):
+ "Formats a date according to the given format"
+ from django.utils.dateformat import format
+ return format(value, arg)
+
+def time(value, arg):
+ "Formats a time according to the given format"
+ from django.utils.dateformat import time_format
+ return time_format(value, arg)
+
+def timesince(value, _):
+ 'Formats a date as the time since that date (i.e. "4 days, 6 hours")'
+ from django.utils.timesince import timesince
+ return timesince(value)
+
+###################
+# LOGIC #
+###################
+
+def default(value, arg):
+ "If value is unavailable, use given default"
+ return value or arg
+
+def divisibleby(value, arg):
+ "Returns true if the value is devisible by the argument"
+ return int(value) % int(arg) == 0
+
+def yesno(value, arg):
+ """
+ Given a string mapping values for true, false and (optionally) None,
+ returns one of those strings accoding to the value:
+
+ ========== ====================== ==================================
+ Value Argument Outputs
+ ========== ====================== ==================================
+ ``True`` ``"yeah,no,maybe"`` ``yeah``
+ ``False`` ``"yeah,no,maybe"`` ``no``
+ ``None`` ``"yeah,no,maybe"`` ``maybe``
+ ``None`` ``"yeah,no"`` ``"no"`` (converts None to False
+ if no mapping for None is given.
+ ========== ====================== ==================================
+ """
+ bits = arg.split(',')
+ if len(bits) < 2:
+ return value # Invalid arg.
+ try:
+ yes, no, maybe = bits
+ except ValueError: # unpack list of wrong size (no "maybe" value provided)
+ yes, no, maybe = bits, bits[1]
+ if value is None:
+ return maybe
+ if value:
+ return yes
+ return no
+
+###################
+# MISC #
+###################
+
+def filesizeformat(bytes, _):
+ """
+ Format the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB, 102
+ bytes, etc).
+ """
+ bytes = float(bytes)
+ if bytes < 1024:
+ return "%d byte%s" % (bytes, bytes != 1 and 's' or '')
+ if bytes < 1024 * 1024:
+ return "%.1f KB" % (bytes / 1024)
+ if bytes < 1024 * 1024 * 1024:
+ return "%.1f MB" % (bytes / (1024 * 1024))
+ return "%.1f GB" % (bytes / (1024 * 1024 * 1024))
+
+def pluralize(value, _):
+ "Returns 's' if the value is not 1, for '1 vote' vs. '2 votes'"
+ try:
+ if int(value) != 1:
+ return 's'
+ except ValueError: # invalid string that's not a number
+ pass
+ except TypeError: # value isn't a string or a number; maybe it's a list?
+ try:
+ if len(value) != 1:
+ return 's'
+ except TypeError: # len() of unsized object
+ pass
+ return ''
+
+def phone2numeric(value, _):
+ "Takes a phone number and converts it in to its numerical equivalent"
+ from django.utils.text import phone2numeric
+ return phone2numeric(value)
+
+def pprint(value, _):
+ "A wrapper around pprint.pprint -- for debugging, really"
+ from pprint import pformat
+ return pformat(value)
+
+# Syntax: template.register_filter(name of filter, callback, has_argument)
+template.register_filter('add', add, True)
+template.register_filter('addslashes', addslashes, False)
+template.register_filter('capfirst', capfirst, False)
+template.register_filter('center', center, True)
+template.register_filter('cut', cut, True)
+template.register_filter('date', date, True)
+template.register_filter('default', default, True)
+template.register_filter('dictsort', dictsort, True)
+template.register_filter('dictsortreversed', dictsortreversed, True)
+template.register_filter('divisibleby', divisibleby, True)
+template.register_filter('escape', escape, False)
+template.register_filter('filesizeformat', filesizeformat, False)
+template.register_filter('first', first, False)
+template.register_filter('fix_ampersands', fix_ampersands, False)
+template.register_filter('floatformat', floatformat, False)
+template.register_filter('get_digit', get_digit, True)
+template.register_filter('join', join, True)
+template.register_filter('length', length, False)
+template.register_filter('length_is', length_is, True)
+template.register_filter('linebreaks', linebreaks, False)
+template.register_filter('linebreaksbr', linebreaksbr, False)
+template.register_filter('linenumbers', linenumbers, False)
+template.register_filter('ljust', ljust, True)
+template.register_filter('lower', lower, False)
+template.register_filter('make_list', make_list, False)
+template.register_filter('phone2numeric', phone2numeric, False)
+template.register_filter('pluralize', pluralize, False)
+template.register_filter('pprint', pprint, False)
+template.register_filter('removetags', removetags, True)
+template.register_filter('random', random, False)
+template.register_filter('rjust', rjust, True)
+template.register_filter('slice', slice_, True)
+template.register_filter('slugify', slugify, False)
+template.register_filter('stringformat', stringformat, True)
+template.register_filter('striptags', striptags, False)
+template.register_filter('time', time, True)