MERGED MAGIC-REMOVAL BRANCH TO TRUNK. This change is highly backwards-incompatible. Please read http://code.djangoproject.com/wiki/RemovingTheMagic for upgrade instructions.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@2809 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Adrian Holovaty 2006-05-02 01:31:56 +00:00
parent d5dbeaa9be
commit f69cf70ed8
366 changed files with 17833 additions and 11199 deletions

View File

@ -71,6 +71,7 @@ answer newbie questions, and generally made Django that much better:
lakin.wecker@gmail.com
Stuart Langridge <http://www.kryogenix.org/>
Eugene Lazutkin <http://lazutkin.com/blog/>
Christopher Lenz <http://www.cmlenz.net/>
limodou
Martin Maney <http://www.chipy.org/Martin_Maney>
Manuzhai
@ -79,6 +80,7 @@ answer newbie questions, and generally made Django that much better:
mattycakes@gmail.com
Jason McBrayer <http://www.carcosa.net/jason/>
michael.mcewan@gmail.com
mir@noris.de
mmarshall
Eric Moritz <http://eric.themoritzfamily.com/>
Robin Munn <http://www.geekforgod.com/>
@ -102,7 +104,9 @@ answer newbie questions, and generally made Django that much better:
Aaron Swartz <http://www.aaronsw.com/>
Tom Tobin
Joe Topjian <http://joe.terrarum.net/geek/code/python/django/>
Malcolm Tredinnick
Amit Upadhyay
Geert Vanderkelen
Milton Waddams
Rachel Willmer <http://www.willmer.com/kb/>
wojtek

View File

@ -1 +1 @@
VERSION = (0, 9, 1, 'SVN')
VERSION = (0, 95, 'post-magic-removal')

View File

@ -1,17 +1,17 @@
"Daily cleanup file"
from django.core.db import db
from django.db import backend, connection, transaction
DOCUMENTATION_DIRECTORY = '/home/html/documentation/'
def clean_up():
# Clean up old database records
cursor = db.cursor()
cursor = connection.cursor()
cursor.execute("DELETE FROM %s WHERE %s < NOW()" % \
(db.quote_name('core_sessions'), db.quote_name('expire_date')))
(backend.quote_name('core_sessions'), backend.quote_name('expire_date')))
cursor.execute("DELETE FROM %s WHERE %s < NOW() - INTERVAL '1 week'" % \
(db.quote_name('registration_challenges'), db.quote_name('request_date')))
db.commit()
(backend.quote_name('registration_challenges'), backend.quote_name('request_date')))
transaction.commit_unless_managed()
if __name__ == "__main__":
clean_up()

View File

@ -0,0 +1,73 @@
"""
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
ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE"
class Settings:
def __init__(self, settings_module):
# update this dict from global settings (but only for ALL_CAPS settings)
for setting in dir(global_settings):
if setting == setting.upper():
setattr(self, setting, getattr(global_settings, setting))
# store the settings module in case someone later cares
self.SETTINGS_MODULE = settings_module
try:
mod = __import__(self.SETTINGS_MODULE, '', '', [''])
except ImportError, e:
raise EnvironmentError, "Could not import settings '%s' (is it on sys.path?): %s" % (self.SETTINGS_MODULE, e)
# Settings that should be converted into tuples if they're mistakenly entered
# as strings.
tuple_settings = ("INSTALLED_APPS", "TEMPLATE_DIRS")
for setting in dir(mod):
if setting == setting.upper():
setting_value = getattr(mod, setting)
if setting in tuple_settings and type(setting_value) == str:
setting_value = (setting_value,) # In case the user forgot the comma.
setattr(self, setting, setting_value)
# Expand entries in INSTALLED_APPS like "django.contrib.*" to a list
# of all those apps.
new_installed_apps = []
for app in self.INSTALLED_APPS:
if app.endswith('.*'):
appdir = os.path.dirname(__import__(app[:-2], '', '', ['']).__file__)
for d in os.listdir(appdir):
if d.isalpha() and os.path.isdir(os.path.join(appdir, d)):
new_installed_apps.append('%s.%s' % (app[:-2], d))
else:
new_installed_apps.append(app)
self.INSTALLED_APPS = new_installed_apps
# move the time zone info into os.environ
os.environ['TZ'] = self.TIME_ZONE
# try to load DJANGO_SETTINGS_MODULE
try:
settings_module = os.environ[ENVIRONMENT_VARIABLE]
if not settings_module: # If it's set but is an empty string.
raise KeyError
except KeyError:
raise EnvironmentError, "Environment variable %s is undefined." % ENVIRONMENT_VARIABLE
# instantiate the configuration object
settings = Settings(settings_module)
# install the translation machinery so that it is available
from django.utils import translation
translation.install()

View File

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

View File

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

View File

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

View File

@ -79,7 +79,7 @@ SERVER_EMAIL = 'root@localhost'
SEND_BROKEN_LINK_EMAILS = False
# Database connection info.
DATABASE_ENGINE = 'postgresql' # 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'.
DATABASE_ENGINE = '' # 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'.
DATABASE_NAME = '' # Or path to database file if using sqlite3.
DATABASE_USER = '' # Not used with sqlite3.
DATABASE_PASSWORD = '' # Not used with sqlite3.
@ -102,19 +102,16 @@ INSTALLED_APPS = ()
# List of locations of the template source files, in search order.
TEMPLATE_DIRS = ()
# Extension on all templates.
TEMPLATE_FILE_EXTENSION = '.html'
# List of callables that know how to import templates from various sources.
# See the comments in django/core/template/loader.py for interface
# documentation.
TEMPLATE_LOADERS = (
'django.core.template.loaders.filesystem.load_template_source',
'django.core.template.loaders.app_directories.load_template_source',
# 'django.core.template.loaders.eggs.load_template_source',
'django.template.loaders.filesystem.load_template_source',
'django.template.loaders.app_directories.load_template_source',
# 'django.template.loaders.eggs.load_template_source',
)
# List of processors used by DjangoContext to populate the context.
# List of processors used by RequestContext to populate the context.
# Each one should be a callable that takes the request object as its
# only parameter and returns a dictionary to add to the context.
TEMPLATE_CONTEXT_PROCESSORS = (
@ -205,6 +202,10 @@ TIME_FORMAT = 'P'
# http://psyco.sourceforge.net/
ENABLE_PSYCO = False
# Do you want to manage transactions manually?
# Hint: you really don't!
TRANSACTIONS_MANAGED = False
##############
# MIDDLEWARE #
##############
@ -213,7 +214,8 @@ ENABLE_PSYCO = False
# 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.sessions.SessionMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
# "django.middleware.http.ConditionalGetMiddleware",
# "django.middleware.gzip.GZipMiddleware",
"django.middleware.common.CommonMiddleware",

View File

@ -9,7 +9,7 @@ ADMINS = (
MANAGERS = ADMINS
DATABASE_ENGINE = 'postgresql' # 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'.
DATABASE_ENGINE = '' # 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'.
DATABASE_NAME = '' # Or path to database file if using sqlite3.
DATABASE_USER = '' # Not used with sqlite3.
DATABASE_PASSWORD = '' # Not used with sqlite3.
@ -45,14 +45,15 @@ SECRET_KEY = ''
# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
'django.core.template.loaders.filesystem.load_template_source',
'django.core.template.loaders.app_directories.load_template_source',
# 'django.core.template.loaders.eggs.load_template_source',
'django.template.loaders.filesystem.load_template_source',
'django.template.loaders.app_directories.load_template_source',
# 'django.template.loaders.eggs.load_template_source',
)
MIDDLEWARE_CLASSES = (
"django.middleware.common.CommonMiddleware",
"django.middleware.sessions.SessionMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.middleware.doc.XViewMiddleware",
)
@ -64,4 +65,8 @@ TEMPLATE_DIRS = (
)
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
)

View File

@ -5,5 +5,5 @@ urlpatterns = patterns('',
# (r'^{{ project_name }}/', include('{{ project_name }}.apps.foo.urls.foo')),
# Uncomment this for admin:
# (r'^admin/', include('django.contrib.admin.urls.admin')),
# (r'^admin/', include('django.contrib.admin.urls')),
)

View File

@ -1,77 +0,0 @@
"""
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
ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE"
# 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:
me.SETTINGS_MODULE = os.environ[ENVIRONMENT_VARIABLE]
if not me.SETTINGS_MODULE: # If it's set but is an empty string.
raise KeyError
except KeyError:
raise EnvironmentError, "Environment variable %s is undefined." % ENVIRONMENT_VARIABLE
try:
mod = __import__(me.SETTINGS_MODULE, '', '', [''])
except ImportError, e:
raise EnvironmentError, "Could not import %s '%s' (is it on sys.path?): %s" % (ENVIRONMENT_VARIABLE, me.SETTINGS_MODULE, e)
# Settings that should be converted into tuples if they're mistakenly entered
# as strings.
tuple_settings = ("INSTALLED_APPS", "TEMPLATE_DIRS")
for setting in dir(mod):
if setting == setting.upper():
setting_value = getattr(mod, setting)
if setting in tuple_settings and type(setting_value) == str:
setting_value = (setting_value,) # In case the user forgot the comma.
setattr(me, setting, setting_value)
# Expand entries in INSTALLED_APPS like "django.contrib.*" to a list
# of all those apps.
new_installed_apps = []
for app in me.INSTALLED_APPS:
if app.endswith('.*'):
appdir = os.path.dirname(__import__(app[:-2], '', '', ['']).__file__)
for d in os.listdir(appdir):
if d.isalpha() and os.path.isdir(os.path.join(appdir, d)):
new_installed_apps.append('%s.%s' % (app[:-2], d))
else:
new_installed_apps.append(app)
me.INSTALLED_APPS = new_installed_apps
# save DJANGO_SETTINGS_MODULE in case anyone in the future cares
me.SETTINGS_MODULE = os.environ.get(ENVIRONMENT_VARIABLE, '')
# 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
# as the last step, install the translation machinery and
# remove the module again to not clutter the namespace.
from django.utils import translation
translation.install()
del translation

View File

@ -1,9 +1,9 @@
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'^login/$', 'django.contrib.auth.view.login'),
(r'^logout/$', 'django.contrib.auth.views.logout'),
(r'^login_another/$', 'django.contrib.auth.views.logout_then_login'),
(r'^register/$', 'ellington.registration.views.registration.signup'),
(r'^register/(?P<challenge_string>\w{32})/$', 'ellington.registration.views.registration.register_form'),
@ -12,8 +12,8 @@ urlpatterns = patterns('',
(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'),
(r'^password_reset/$', 'django.contrib.auth.views.password_reset'),
(r'^password_reset/done/$', 'django.contrib.auth.views.password_reset_done'),
(r'^password_change/$', 'django.contrib.auth.views.password_change'),
(r'^password_change/done/$', 'django.contrib.auth.views.password_change_done'),
)

View File

@ -6,7 +6,7 @@ Each filter subclass knows how to display a filter for a field that passes a
certain test -- e.g. being a DateField or ForeignKey.
"""
from django.core import meta
from django.db import models
import datetime
class FilterSpec(object):
@ -50,13 +50,13 @@ class FilterSpec(object):
class RelatedFilterSpec(FilterSpec):
def __init__(self, f, request, params):
super(RelatedFilterSpec, self).__init__(f, request, params)
if isinstance(f, meta.ManyToManyField):
self.lookup_title = f.rel.to.verbose_name
if isinstance(f, models.ManyToManyField):
self.lookup_title = f.rel.to._meta.verbose_name
else:
self.lookup_title = f.verbose_name
self.lookup_kwarg = '%s__%s__exact' % (f.name, f.rel.to.pk.name)
self.lookup_kwarg = '%s__%s__exact' % (f.name, f.rel.to._meta.pk.name)
self.lookup_val = request.GET.get(self.lookup_kwarg, None)
self.lookup_choices = f.rel.to.get_model_module().get_list()
self.lookup_choices = f.rel.to._default_manager.all()
def has_output(self):
return len(self.lookup_choices) > 1
@ -69,7 +69,7 @@ class RelatedFilterSpec(FilterSpec):
'query_string': cl.get_query_string({}, [self.lookup_kwarg]),
'display': _('All')}
for val in self.lookup_choices:
pk_val = getattr(val, self.field.rel.to.pk.attname)
pk_val = getattr(val, self.field.rel.to._meta.pk.attname)
yield {'selected': self.lookup_val == str(pk_val),
'query_string': cl.get_query_string( {self.lookup_kwarg: pk_val}),
'display': val}
@ -103,7 +103,7 @@ class DateFieldFilterSpec(FilterSpec):
today = datetime.date.today()
one_week_ago = today - datetime.timedelta(days=7)
today_str = isinstance(self.field, meta.DateTimeField) and today.strftime('%Y-%m-%d 23:59:59') or today.strftime('%Y-%m-%d')
today_str = isinstance(self.field, models.DateTimeField) and today.strftime('%Y-%m-%d 23:59:59') or today.strftime('%Y-%m-%d')
self.links = (
(_('Any date'), {}),
@ -126,7 +126,7 @@ class DateFieldFilterSpec(FilterSpec):
'query_string': cl.get_query_string( param_dict, self.field_generic),
'display': title}
FilterSpec.register(lambda f: isinstance(f, meta.DateField), DateFieldFilterSpec)
FilterSpec.register(lambda f: isinstance(f, models.DateField), DateFieldFilterSpec)
class BooleanFieldFilterSpec(FilterSpec):
def __init__(self, f, request, params):
@ -144,9 +144,9 @@ class BooleanFieldFilterSpec(FilterSpec):
yield {'selected': self.lookup_val == v and not self.lookup_val2,
'query_string': cl.get_query_string( {self.lookup_kwarg: v}, [self.lookup_kwarg2]),
'display': k}
if isinstance(self.field, meta.NullBooleanField):
if isinstance(self.field, models.NullBooleanField):
yield {'selected': self.lookup_val2 == 'True',
'query_string': cl.get_query_string( {self.lookup_kwarg2: 'True'}, [self.lookup_kwarg]),
'display': _('Unknown')}
FilterSpec.register(lambda f: isinstance(f, meta.BooleanField) or isinstance(f, meta.NullBooleanField), BooleanFieldFilterSpec)
FilterSpec.register(lambda f: isinstance(f, models.BooleanField) or isinstance(f, models.NullBooleanField), BooleanFieldFilterSpec)

View File

@ -1,3 +1,14 @@
@import url(global.css);
@import url(changelists.css);
/*
DJANGO Admin
by Wilson Miner wilson@lawrence.com
*/
/* Block IE 5 */
@import "null?\"\{";
/* Import other styles */
@import url('global.css');
@import url('layout.css');
/* Import patch for IE 6 Windows */
/*\*/ @import "patch-iewin.css"; /**/

View File

@ -1,16 +1,13 @@
/*
DJANGO Admin Changelist Styles
by Wilson Miner wilson@lawrence.com
Copyright (c) 2005 Lawrence Journal-World
*/
@import url('base.css');
/* CHANGELISTS */
#changelist { position:relative; width:100%; }
#changelist table { width:100%; }
.change-list .filtered table { border-right:1px solid #ddd; }
.change-list .filtered { min-height:400px; _height:400px; }
.change-list .filtered { background:white url(../img/admin/changelist-bg.gif) top right repeat-y !important; }
.change-list .filtered table, .change-list .filtered .paginator, .filtered #toolbar, .filtered div.xfull { margin-right:160px !important; width:auto !important; }
.change-list .filtered table tbody th { padding-right:10px; }
.change-list .filtered table tbody th { padding-right:1em; }
#changelist .toplinks { border-bottom:1px solid #ccc !important; }
#changelist .paginator { color:#666; border-top:1px solid #eee; border-bottom:1px solid #eee; background:white url(../img/admin/nav-bg.gif) 0 180% repeat-x; overflow:hidden; }
.change-list .filtered .paginator { border-right:1px solid #ddd; }
@ -42,3 +39,12 @@
.change-list ul.toplinks li { float: left; width: 9em; padding:3px 6px; font-weight: bold; list-style-type:none; }
.change-list ul.toplinks .date-back a { color:#999; }
.change-list ul.toplinks .date-back a:hover { color:#036; }
/* PAGINATOR */
.paginator { font-size:11px; padding-top:10px; padding-bottom:10px; line-height:22px; margin:0; border-top:1px solid #ddd; }
.paginator a:link, .paginator a:visited { padding:2px 6px; border:solid 1px #ccc; background:white; text-decoration:none; }
.paginator a.showall { padding:0 !important; border:none !important; }
.paginator a.showall:hover { color:#036 !important; background:transparent !important; }
.paginator .end { border-width:2px !important; margin-right:6px; }
.paginator .this-page { padding:2px 6px; font-weight:bold; font-size:13px; vertical-align:top; }
.paginator a:hover { color:white; background:#5b80b2; border-color:#036; }

View File

@ -0,0 +1,10 @@
@import url('base.css');
/* DASHBOARD */
.dashboard .module table th { width:100%; }
.dashboard .module table td { white-space:nowrap; }
.dashboard .module table td a { display:block; padding-right:.6em; }
/* RECENT ACTIONS MODULE */
.module ul.actionlist { margin-left:0; }
ul.actionlist li { list-style-type:none; }

View File

@ -0,0 +1,60 @@
@import url('base.css');
@import url('widgets.css');
/* FORM ROWS */
.form-row { overflow:hidden; padding:8px 12px; font-size:11px; border-bottom:1px solid #eee; }
.form-row img, .form-row input { vertical-align:middle; }
form .form-row p { padding-left:0; font-size:11px; }
/* FORM LABELS */
form h4 { margin:0 !important; padding:0 !important; border:none !important; }
label { font-weight:normal !important; color:#666; font-size:12px; }
label.inline { margin-left:20px; }
.required label, label.required { font-weight:bold !important; color:#333 !important; }
/* RADIO BUTTONS */
form ul.radiolist li { list-style-type:none; }
form ul.radiolist label { float:none; display:inline; }
form ul.inline { margin-left:0; padding:0; }
form ul.inline li { float:left; padding-right:7px; }
/* ALIGNED FIELDSETS */
.aligned label { display:block; padding:0 1em 3px 0; float:left; width:8em; }
.aligned label.inline { display:inline; float:none; }
.colMS .aligned .vLargeTextField, .colMS .aligned .vXMLLargeTextField { width:350px; }
form .aligned p, form .aligned ul { margin-left:7em; padding-left:30px; }
form .aligned table p { margin-left:0; padding-left:0; }
form .aligned p.help { padding-left:38px; }
.aligned .vCheckboxLabel { float:none !important; display:inline; padding-left:4px; }
.colM .aligned .vLargeTextField, colM .aligned .vXMLLargeTextField { width:610px; }
.checkbox-row p.help { margin-left:0; padding-left:0 !important; }
/* WIDE FIELDSETS */
.wide label { width:15em !important; }
form .wide p { margin-left:15em; }
form .wide p.help { padding-left:38px; }
.colM fieldset.wide .vLargeTextField, .colM fieldset.wide .vXMLLargeTextField { width:450px; }
/* COLLAPSED FIELDSETS */
fieldset.collapsed * { display:none; }
fieldset.collapsed h2, fieldset.collapsed { display:block !important; }
fieldset.collapsed h2 { background-image:url(../img/admin/nav-bg.gif); background-position:bottom left; color:#999; }
fieldset.collapsed .collapse-toggle { padding:3px 5px !important; background:transparent; display:inline !important;}
/* MONOSPACE TEXTAREAS */
fieldset.monospace textarea { font-family:"Bitstream Vera Sans Mono",Monaco,"Courier New",Courier,monospace; }
/* SUBMIT ROW */
.submit-row { padding:5px 7px; text-align:right; background:white url(../img/admin/nav-bg.gif) 0 100% repeat-x; border:1px solid #ccc; margin:5px 0; }
.submit-row input { margin:0 0 0 5px; }
.submit-row p { margin-top:0.3em; }
.submit-row .deletelink { background:url(../img/admin/icon_deletelink.gif) 0 50% no-repeat; padding-left:14px; }
/* CUSTOM FORM FIELDS */
.vSelectMultipleField { vertical-align:top !important; }
.vCheckboxField { border:none; }
.vDateField, .vTimeField { margin-right:2px; }
.vURLField { width:30em; }
.vLargeTextField, .vXMLLargeTextField { width:48em; }
.flatpages-flatpage #id_content { height:40.2em; }
.module table .vPositiveSmallIntegerField { width:2.2em; }

View File

@ -1,19 +1,14 @@
/*
DJANGO Admin Global Styles
by Wilson Miner wilson@lawrence.com
Copyright (c) 2005 Lawrence Journal-World
*/
body { margin:0; padding:0; font-family:"Lucida Grande","Bitstream Vera Sans",Verdana,Arial,sans-serif; color:#333; background:#fff; }
body { margin:0; padding:0; font-size:12px; font-family:"Lucida Grande","Bitstream Vera Sans",Verdana,Arial,sans-serif; color:#333; background:#fff; }
/* LINKS */
a:link, a:visited { color: #5b80b2; text-decoration:none; }
a:hover { color: #036; }
a img { border:none; }
/* GLOBAL DEFAULTS */
p, ol, ul, dl { margin:.2em 0 .8em 0; font-size:12px; }
/* GLOBAL DEFAULTS */
p, ol, ul, dl { margin:.2em 0 .8em 0; }
p { padding:0; line-height:140%; }
h1,h2,h3,h4,h5 { font-weight:bold; }
h1 { font-size:18px; color:#666; padding:0 6px 0 0; margin:0 0 .2em 0; }
h2 { font-size:16px; margin:1em 0 .5em 0; }
@ -21,6 +16,7 @@ h2.subhead { font-weight:normal;margin-top:0; }
h3 { font-size:14px; margin:.8em 0 .3em 0; color:#666; font-weight:bold; }
h4 { font-size:12px; margin:1em 0 .8em 0; padding-bottom:3px; }
h5 { font-size:10px; margin:1.5em 0 .5em 0; color:#666; text-transform:uppercase; letter-spacing:1px; }
ul li { list-style-type:square; padding:1px 0; }
ul.plainlist { margin-left:0 !important; }
ul.plainlist li { list-style-type:none; }
@ -28,274 +24,17 @@ li ul { margin-bottom:0; }
li, dt, dd { font-size:11px; line-height:14px; }
dt { font-weight:bold; margin-top:4px; }
dd { margin-left:0; }
form { margin:0; padding:0; }
fieldset { margin:0; padding:0; }
blockquote { font-size:11px; color:#777; margin-left:2px; padding-left:10px; border-left:5px solid #ddd; }
code, pre { font-family:"Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace; background:inherit; color:#666; font-size:11px; }
pre.literal-block { margin:10px; background:#eee; padding:6px 8px; }
code strong { color:#930; }
hr { clear:both; color:#eee; background-color:#eee; height:1px; border:none; margin:0; padding:0; font-size:1px; line-height:1px; }
/* PAGE STRUCTURE */
#container { position:relative; width:100%; min-width:760px; }
#content { margin:10px 15px; }
#header { width:100%; }
#content-main { float:left; width:100%; }
#content-related { float:right; width:220px; position:relative; margin-right:-230px; }
#footer { clear:both; padding:10px; }
/* COLUMN TYPES */
.colMS { margin-right:245px !important; }
.colSM { margin-left:245px !important; }
.colSM #content-related { float:left; margin-right:0; margin-left:-230px; }
.colSM #content-main { float:right; }
.popup .colM { width:95%; }
.subcol { float:left; width:46%; margin-right:15px; }
.dashboard #content { width:500px; }
/* HEADER */
#header { background:#417690; color:#ffc; min-height:2.4em; overflow:hidden; }
#header a:link, #header a:visited { color:white; }
#header a:hover { text-decoration:underline; }
#branding h1 { padding:0.5em 10px 0 10px; font-size:18px; margin:0; font-weight:normal; color:#f4f379; }
#branding h2 { padding:0 10px 0.8em 10px; font-size:14px; margin:0; font-weight:normal; color:#ffc; }
#user-tools { position:absolute; top:0; right:0; padding:1.2em 10px; font-size:11px; text-align:right; }
/* SIDEBAR */
#content-related h3 { font-size:12px; color:#666; margin-bottom:3px; }
#content-related h4 { font-size:11px; }
/* TABLES */
table { border-collapse:collapse; border-color:#ccc; }
td, th { font-size:11px; line-height:13px; border-bottom:1px solid #eee; vertical-align:top; padding:5px; font-family:"Lucida Grande", Verdana, Arial, sans-serif; }
th { text-align:left; font-size:12px; }
thead th { font-weight:bold; color:#666; padding:2px 5px; font-size:11px; background:#e1e1e1 url(../img/admin/nav-bg.gif) top left repeat-x; border-left:1px solid #ddd; border-bottom:1px solid #ddd; }
thead th:first-child { border-left:none !important; }
.superwide table th, .superwide table td, .superwide table input, .superwide table select { font-size:10px; }
.module table { border-collapse: collapse; }
thead th.optional { font-weight:normal !important; }
#home-page table.module tr:hover { background:#EDF3FE; }
fieldset table { border-right:1px solid #eee; }
tr.row-label td { font-size:9px; padding-top:2px; padding-bottom:0; border-bottom:none; color:#666; margin-top:-1px; }
tr.alt { background:#f6f6f6; }
.row1 { background:#EDF3FE; }
.row2 { background:white; }
table#change-history { width:100%; }
table#change-history tbody th { width:16em; }
/* TABLE SORTING */
thead th a:link, thead th a:visited { color:#666; display:block; }
table thead th.sorted { background-position:bottom left !important; }
table thead th.sorted a { padding-right:13px; }
table thead th.ascending a { background:url(../img/admin/arrow-down.gif) right .4em no-repeat; }
table thead th.descending a { background:url(../img/admin/arrow-up.gif) right .4em no-repeat; }
/* MODULES */
.module { border:1px solid #ccc; margin-bottom:5px; background:white; }
.module p, .module ul, .module h3, .module h4, .module dl, .module pre { padding-left:10px; padding-right:10px; }
.module blockquote { margin-left:12px; }
.module ul, .module ol { margin-left:1.5em; }
.module h2, .module caption { margin:0; padding:2px 5px 3px 5px; font-size:11px; text-align:left; background:#7CA0C7 url(../img/admin/default-bg.gif) left top repeat-x; color:white; font-weight:bold; }
.module caption { border:1px solid #ccc; border-bottom:none; }
.module h3 { margin-top:.6em; }
#content-related .module h2 { background:#eee url(../img/admin/nav-bg.gif) bottom left repeat-x; color:#666; }
#content-main .verbose .actionlist { float:right; font-size:10px; width:17em; position:relative; top:-1.6em; margin:0 8px; }
/* DASHBOARD */
.dashboard .module table th { width:100%; }
.dashboard .module table td { white-space:nowrap; }
.dashboard .module table td a { display:block; padding-right:.6em; }
/* RECENT ACTIONS MODULE */
.module ul.actionlist { margin-left:0; }
ul.actionlist li { list-style-type:none; }
/* FORM DEFAULTS */
input, textarea, select { margin:2px 0; padding:2px 3px; vertical-align:middle; border:1px solid #ccc; font-family:"Lucida Grande", Verdana, Arial, sans-serif; font-weight:normal; font-size:11px; }
textarea { vertical-align:top !important; }
input[type=checkbox], input[type=radio] { border:none; }
/* FORM BUTTONS */
input[type=submit], input[type=button], .submit-row input { background:white url(../img/admin/nav-bg.gif) bottom repeat-x; padding:3px; color:black; }
input[type=submit]:active, input[type=button]:active { background-image:url(../img/admin/nav-bg-reverse.gif); background-position:top; }
input[type=submit].default, .submit-row input.default { border:2px solid #5b80b2; background:#7CA0C7 url(../img/admin/default-bg.gif) bottom repeat-x; font-weight:bold; color:white; }
input[type=submit].default:active { background-image:url(../img/admin/default-bg-reverse.gif); background-position:top; }
.submit-row { padding:5px 7px; text-align:right; background:#ffc; border:1px solid #ccc; margin:5px 0; }
.submit-row input { margin:0 0 0 5px; }
.submit-row .float-left { padding-top:.1em; }
/* FORM ROWS */
.form-row { clear:both; padding:8px 12px; font-size:11px; }
html>body .form-row { border-bottom:1px solid #eee; }
.form-row:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
.form-row img, .form-row input { vertical-align:middle; }
form .form-row p { padding-left:0; font-size:11px; }
/* FORM LABELS */
form h4 { margin:0 !important; padding:0 !important; border:none !important; }
label { font-weight:normal !important; color:#666; font-size:12px; }
label.inline { margin-left:20px; }
.required label, label.required { font-weight:bold !important; color:#333 !important; }
/* RADIO BUTTONS */
form ul.radiolist li { list-style-type:none; }
form ul.radiolist label { float:none; display:inline; }
form ul.inline { margin-left:0; padding:0; }
form ul.inline li { float:left; padding-right:7px; }
/* ALIGNED FIELDSETS */
.aligned label { display:block; padding:0 1em 3px 0; float:left; text-align:left; width:8em; }
.aligned label.inline { display:inline; float:none; }
.colMS .aligned .vLargeTextField, .colMS .aligned .vXMLLargeTextField { width:350px; }
form .aligned p, form .aligned ul { margin-left:7em; padding-left:30px; }
form .aligned table p { margin-left:0; padding-left:0; }
form .aligned p.help { padding-left:38px; }
.aligned .vCheckboxLabel { float:none !important; display:inline; }
.colM .aligned .vLargeTextField, colM .aligned .vXMLLargeTextField { width:610px; }
.checkbox-row p.help { margin-left:0; padding-left:0 !important; }
/* WIDE FIELDSETS */
.wide label { width:15em !important; }
form .wide p { margin-left:15em; }
form .wide p.help { padding-left:38px; }
.colM fieldset.wide .vLargeTextField, .colM fieldset.wide .vXMLLargeTextField { width:450px; }
/* COLLAPSED FIELDSETS */
fieldset.collapsed * { display:none; }
fieldset.collapsed h2, fieldset.collapsed { display:block !important; }
fieldset.collapsed .collapse-toggle { display: inline !important; }
fieldset.collapse h2 a.collapse-toggle { color:#ffc; }
fieldset.collapse h2 a.collapse-toggle:hover { text-decoration:underline; }
.hidden { display:none; }
/* MONOSPACE TEXTAREAS */
fieldset.monospace textarea { font-family:"Bitstream Vera Sans Mono",Monaco,"Courier New",Courier,monospace; }
/* MESSAGES & ERRORS */
ul.messagelist { padding:0 0 5px 0; margin:0; }
ul.messagelist li { font-size:12px; display:block; padding:4px 5px 4px 25px; margin:0 0 3px 0; border-bottom:1px solid #ddd; color:#666; background:#ffc url(../img/admin/icon_success.gif) 5px .3em no-repeat; }
.errornote { font-size:12px !important; display:block; padding:4px 5px 4px 25px; margin:0 0 3px 0; border:1px solid red; color:red;background:#ffc url(../img/admin/icon_error.gif) 5px .3em no-repeat; }
ul.errorlist { margin:0 !important; padding:0 !important; }
.errorlist li { font-size:12px !important; display:block; padding:4px 5px 4px 25px; margin:0 0 3px 0; border:1px solid red; color:white; background:red url(../img/admin/icon_alert.gif) 5px .3em no-repeat; }
td ul.errorlist { margin:0 !important; padding:0 !important; }
td ul.errorlist li { margin:0 !important; }
.error { background:#ffc; }
.error input, .error select { border:1px solid red; }
div.system-message { background: #ffc; margin: 10px; padding: 6px 8px; font-size: .8em; }
div.system-message p.system-message-title { padding:4px 5px 4px 25px; margin:0; color:red; background:#ffc url(../img/admin/icon_error.gif) 5px .3em no-repeat; }
/* ACTION ICONS */
.addlink { padding-left:12px; background:url(../img/admin/icon_addlink.gif) 0 .2em no-repeat; }
.changelink { padding-left:12px; background:url(../img/admin/icon_changelink.gif) 0 .2em no-repeat; }
.deletelink { padding-left:12px; background:url(../img/admin/icon_deletelink.gif) 0 .2em no-repeat; }
a.deletelink:link, a.deletelink:visited { color:#CC3434; }
a.deletelink:hover { color:#993333; }
/* OBJECT TOOLS */
.object-tools { font-size:10px; font-weight:bold; font-family:Arial,Helvetica,sans-serif; padding-left:0; margin-bottom:5px; float:right; position:relative; margin-top:-2.4em; margin-bottom:-2em; }
.form-row .object-tools { margin-top:0; margin-bottom:0; }
.object-tools li { display:block; float:left; background:url(../img/admin/tool-left.gif) 0 0 no-repeat; padding:0 0 0 8px; margin-left:2px; height:16px; }
.object-tools li:hover { background:url(../img/admin/tool-left_over.gif) 0 0 no-repeat; }
.object-tools a:link, .object-tools a:visited { display:block; float:left; color:white; padding:.1em 14px .1em 8px; height:14px; background:#999 url(../img/admin/tool-right.gif) 100% 0 no-repeat; }
.object-tools a:hover, .object-tools li:hover a { background:#5b80b2 url(../img/admin/tool-right_over.gif) 100% 0 no-repeat; }
.object-tools a.viewsitelink, .object-tools a.golink { background:#999 url(../img/admin/tooltag-arrowright.gif) top right no-repeat; padding-right:28px; }
.object-tools a.viewsitelink:hover, .object-tools a.golink:hover { background:#5b80b2 url(../img/admin/tooltag-arrowright_over.gif) top right no-repeat; }
.object-tools a.addlink { background:#999 url(../img/admin/tooltag-add.gif) top right no-repeat; padding-right:28px; }
.object-tools a.addlink:hover { background:#5b80b2 url(../img/admin/tooltag-add_over.gif) top right no-repeat; }
/* INLINE CONTROLS */
#inline-controls { font-weight:bold; font-size:12px; }
#inline-specific-controls { margin-left:6px; padding:0 8px; border-left:6px solid #ccc; }
/* BREADCRUMBS */
p.breadcrumbs { font-size:11px; color:#ccc;text-align:left; } /* old breadcrumbs style */
div.breadcrumbs { background:white url(../img/admin/nav-bg-reverse.gif) 0 -10px repeat-x; padding:2px 8px 3px 8px; font-size:11px; color:#999; border-top:1px solid white; border-bottom:1px solid #ccc; text-align:left; }
/* SELECTOR (FILTER INTERFACE) */
.selector { width:580px; float:left; }
.selector select { width:270px; height:170px; }
.selector-available, .selector-chosen { float:left; width:270px; text-align:center; margin-bottom:5px; }
.selector-available h2, .selector-chosen h2 { border:1px solid #ccc; }
.selector .selector-available h2 { background:white url(../img/admin/nav-bg.gif) bottom left repeat-x; color:#666; }
.selector .selector-filter { background:white; border:1px solid #ccc; border-width:0 1px; padding:3px; color:#999; font-size:10px; margin:0; text-align:left; }
.selector .selector-chosen .selector-filter { padding:4px 5px; }
.selector .selector-available input { width:230px; }
.selector ul.selector-chooser { float:left; width:22px; height:50px; background:url(../img/admin/chooser-bg.gif) top center no-repeat; margin:13% 3px 0 3px; padding:0; }
.selector-chooser li { margin:0; padding:3px; list-style-type:none; }
.selector select { margin-bottom:5px; margin-top:0; }
.selector-add, .selector-remove { width:16px; height:16px; display:block; text-indent:-3000px; }
.selector-add { background:url(../img/admin/selector-add.gif) top center no-repeat; margin-bottom:2px; }
.selector-remove { background:url(../img/admin/selector-remove.gif) top center no-repeat; }
a.selector-chooseall, a.selector-clearall { display:block; width:6em; text-align:left; margin-left:auto; margin-right:auto; font-weight:bold; color:#666; padding:3px 0 3px 18px; }
a.selector-chooseall:hover, a.selector-clearall:hover { color:#036; }
a.selector-chooseall { width:7em; background:url(../img/admin/selector-addall.gif) left center no-repeat; }
a.selector-clearall { background:url(../img/admin/selector-removeall.gif) left center no-repeat; }
/* Stacked selectors for long items */
.stacked { float:left; width:500px; }
.stacked select { width:480px; height:100px; }
.stacked .selector-available, .stacked .selector-chosen { width:480px; }
.stacked .selector-available { margin-bottom:0; }
.stacked .selector-available input { width:442px; }
.stacked ul.selector-chooser { height:22px; width:50px; margin:0 0 3px 40%; background:url(../img/admin/chooser_stacked-bg.gif) top center no-repeat; }
.stacked .selector-chooser li { float:left; padding:3px 3px 3px 5px; }
.stacked .selector-chooseall, .stacked .selector-clearall { display:none; }
.stacked .selector-add { background-image:url(../img/admin/selector_stacked-add.gif); }
.stacked .selector-remove { background-image:url(../img/admin/selector_stacked-remove.gif); }
/* DATE AND TIME */
p.datetime { line-height:20px; margin:0; padding:0; color:#666; font-size:11px; font-weight:bold; }
.datetime span { font-size:11px; font-weight:normal; color:#ccc; white-space:nowrap; }
.vDateField { margin-left:4px; }
table p.datetime { font-size:10px; margin-left:0; padding-left:0; }
/* FILE UPLOADS */
p.file-upload { line-height:20px; margin:0; padding:0; color:#666; font-size:11px; font-weight:bold; }
.file-upload a { font-weight:normal; }
.file-upload .deletelink { margin-left:5px; }
/* CALENDARS & CLOCKS */
.calendarbox, .clockbox { margin:5px auto; font-size:11px; width: 16em; text-align: center; background:white; position:relative; }
.clockbox { width:9em; }
.calendar { margin:0; padding: 0; }
.calendar table { margin: 0; padding: 0; border-collapse:collapse; background:white; width:99%; }
.calendar caption, .calendarbox h2 { margin: 0; font-size:11px; text-align:center; border-top:none; }
.calendar th { font-size:10px; color:#666; padding:2px 3px; text-align:center; background:#e1e1e1 url(../img/admin/nav-bg.gif) 0 50% repeat-x; border-bottom:1px solid #ddd; }
.calendar td { font-size:11px; text-align: center; padding: 0; border-top:1px solid #eee; border-bottom:none; }
.calendar td.selected a { background: #C9DBED; }
.calendar td.nonday { background:#efefef; }
.calendar td.today a { background:#ffc; }
.calendar td a, .timelist a { display: block; font-weight:bold; padding:4px; text-decoration: none; color:#444; }
.calendar td a:hover, .timelist a:hover { background: #5b80b2; color:white; }
.calendar td a:active, .timelist a:active { background: #036; color:white; }
.calendarnav { font-size:10px; text-align: center; color:#ccc; margin:0; padding:1px 3px; }
.calendarnav a:link, #calendarnav a:visited, #calendarnav a:hover { color: #999; }
.calendar-shortcuts { background:white; font-size:10px; line-height:11px; border-top:1px solid #eee; padding:3px 0 4px; color:#ccc; }
.calendarbox .calendarnav-previous, .calendarbox .calendarnav-next { display:block; position:absolute; font-weight:bold; font-size:12px; background:#C9DBED url(../img/admin/default-bg.gif) bottom left repeat-x; padding:1px 4px 2px 4px; color:white; }
.calendarnav-previous:hover, .calendarnav-next:hover { background:#036; }
.calendarnav-previous { top:0; left:0; }
.calendarnav-next { top:0; right:0; }
.calendar-cancel { margin:0 !important; padding:0; font-size:10px; background:#e1e1e1 url(../img/admin/nav-bg.gif) 0 50% repeat-x; border-top:1px solid #ddd; }
.calendar-cancel a { padding:2px; color:#999; }
ul.timelist, .timelist li { list-style-type:none; margin:0; padding:0; }
.timelist a { padding:2px; }
/* ORDERING WIDGET */
ul#orderthese { position:absolute; top:8em; right:0; width:240px; padding:0; margin:0; list-style-type:none; }
ul#orderthese li { list-style-type:none; display:block; padding:0; margin:6px 0; width:214px; background:#f6f6f6; white-space:nowrap; overflow:hidden; }
ul#orderthese li span { display:block; border:1px solid #e7e7e7; background:transparent url(../img/admin/nav-bg-grabber.gif) top left repeat-y; font-size:10px !important; padding:4px 6px 4px 12px; }
ul#orderthese span:hover { background-color:#efefef; }
/* PAGINATOR */
.paginator { font-size:11px; padding-top:10px; padding-bottom:10px; line-height:22px; margin:0; border-top:1px solid #ddd; }
.paginator a:link, .paginator a:visited { padding:2px 6px; border:solid 1px #ccc; background:white; text-decoration:none; }
.paginator a.showall { padding:0 !important; border:none !important; }
.paginator a.showall:hover { color:#036 !important; background:transparent !important; }
.paginator .end { border-width:2px !important; margin-right:6px; }
.paginator .this-page { padding:2px 6px; font-weight:bold; font-size:13px; vertical-align:top; }
.paginator a:hover { color:white; background:#5b80b2; border-color:#036; }
/* TEXT STYLES & MODIFIERS */
/* TEXT STYLES & MODIFIERS */
.small { font-size:11px; }
.tiny { font-size:10px; }
p.tiny { margin-top:-2px; }
@ -313,13 +52,90 @@ p img, h1 img, h2 img, h3 img, h4 img, td img { vertical-align:middle; }
.example { margin:10px 0; padding:5px 10px; background:#efefef; }
.nowrap { white-space:nowrap; }
/* CUSTOM FORM FIELDS */
.vSelectMultipleField { vertical-align:top !important; }
.vCheckboxField { border:none; }
.vDateField, .vTimeField { margin-right:2px; }
.vFileUploadField { border:none; }
.vURLField { width:380px; }
.vLargeTextField, .vXMLLargeTextField { width:480px; }
.colM .vLargeTextField, .colM .vXMLLargeTextField { width:720px; }
body.core-flatfile #id_content { height: 400px; }
.module table .vPositiveSmallIntegerField { width: 22px; }
/* TABLES */
table { border-collapse:collapse; border-color:#ccc; }
td, th { font-size:11px; line-height:13px; border-bottom:1px solid #eee; vertical-align:top; padding:5px; font-family:"Lucida Grande", Verdana, Arial, sans-serif; }
th { text-align:left; font-size:12px; font-weight:bold; }
thead th,
tfoot td { color:#666; padding:2px 5px; font-size:11px; background:#e1e1e1 url(../img/admin/nav-bg.gif) top left repeat-x; border-left:1px solid #ddd; border-bottom:1px solid #ddd; }
tfoot td { border-bottom:none; border-top:1px solid #ddd; }
thead th:first-child,
tfoot td:first-child { border-left:none !important; }
thead th.optional { font-weight:normal !important; }
fieldset table { border-right:1px solid #eee; }
tr.row-label td { font-size:9px; padding-top:2px; padding-bottom:0; border-bottom:none; color:#666; margin-top:-1px; }
tr.alt { background:#f6f6f6; }
.row1 { background:#EDF3FE; }
.row2 { background:white; }
/* SORTABLE TABLES */
thead th a:link, thead th a:visited { color:#666; display:block; }
table thead th.sorted { background-position:bottom left !important; }
table thead th.sorted a { padding-right:13px; }
table thead th.ascending a { background:url(../img/admin/arrow-down.gif) right .4em no-repeat; }
table thead th.descending a { background:url(../img/admin/arrow-up.gif) right .4em no-repeat; }
/* ORDERABLE TABLES */
table.orderable tbody tr td:hover { cursor:move; }
table.orderable tbody tr td:first-child { padding-left:14px; background-image:url(../img/admin/nav-bg-grabber.gif); background-repeat:repeat-y; }
table.orderable-initalized .order-cell, body>tr>td.order-cell { display:none; }
/* FORM DEFAULTS */
input, textarea, select { margin:2px 0; padding:2px 3px; vertical-align:middle; font-family:"Lucida Grande", Verdana, Arial, sans-serif; font-weight:normal; font-size:11px; }
textarea { vertical-align:top !important; }
input[type=text], input[type=password], textarea, select, .vTextField { border:1px solid #ccc; }
/* FORM BUTTONS */
input[type=submit], input[type=button], .submit-row input { background:white url(../img/admin/nav-bg.gif) bottom repeat-x; padding:3px; color:black; }
input[type=submit]:active, input[type=button]:active { background-image:url(../img/admin/nav-bg-reverse.gif); background-position:top; }
input[type=submit].default, .submit-row input.default { border:2px solid #5b80b2; background:#7CA0C7 url(../img/admin/default-bg.gif) bottom repeat-x; font-weight:bold; color:white; }
input[type=submit].default:active { background-image:url(../img/admin/default-bg-reverse.gif); background-position:top; }
/* MODULES */
.module { border:1px solid #ccc; margin-bottom:5px; background:white; }
.module p, .module ul, .module h3, .module h4, .module dl, .module pre { padding-left:10px; padding-right:10px; }
.module blockquote { margin-left:12px; }
.module ul, .module ol { margin-left:1.5em; }
.module h3 { margin-top:.6em; }
.module h2, .module caption { margin:0; padding:2px 5px 3px 5px; font-size:11px; text-align:left; font-weight:bold; background:#7CA0C7 url(../img/admin/default-bg.gif) top left repeat-x; color:white; }
.module table { border-collapse: collapse; }
/* MESSAGES & ERRORS */
ul.messagelist { padding:0 0 5px 0; margin:0; }
ul.messagelist li { font-size:12px; display:block; padding:4px 5px 4px 25px; margin:0 0 3px 0; border-bottom:1px solid #ddd; color:#666; background:#ffc url(../img/admin/icon_success.gif) 5px .3em no-repeat; }
.errornote { font-size:12px !important; display:block; padding:4px 5px 4px 25px; margin:0 0 3px 0; border:1px solid red; color:red;background:#ffc url(../img/admin/icon_error.gif) 5px .3em no-repeat; }
ul.errorlist { margin:0 !important; padding:0 !important; }
.errorlist li { font-size:12px !important; display:block; padding:4px 5px 4px 25px; margin:0 0 3px 0; border:1px solid red; color:white; background:red url(../img/admin/icon_alert.gif) 5px .3em no-repeat; }
td ul.errorlist { margin:0 !important; padding:0 !important; }
td ul.errorlist li { margin:0 !important; }
.error { background:#ffc; }
.error input, .error select { border:1px solid red; }
div.system-message { background: #ffc; margin: 10px; padding: 6px 8px; font-size: .8em; }
div.system-message p.system-message-title { padding:4px 5px 4px 25px; margin:0; color:red; background:#ffc url(../img/admin/icon_error.gif) 5px .3em no-repeat; }
.description { font-size:12px; padding:5px 0 0 12px; }
/* BREADCRUMBS */
div.breadcrumbs { background:white url(../img/admin/nav-bg-reverse.gif) 0 -10px repeat-x; padding:2px 8px 3px 8px; font-size:11px; color:#999; border-top:1px solid white; border-bottom:1px solid #ccc; text-align:left; }
/* ACTION ICONS */
.addlink { padding-left:12px; background:url(../img/admin/icon_addlink.gif) 0 .2em no-repeat; }
.changelink { padding-left:12px; background:url(../img/admin/icon_changelink.gif) 0 .2em no-repeat; }
.deletelink { padding-left:12px; background:url(../img/admin/icon_deletelink.gif) 0 .25em no-repeat; }
a.deletelink:link, a.deletelink:visited { color:#CC3434; }
a.deletelink:hover { color:#993333; }
/* OBJECT TOOLS */
.object-tools { font-size:10px; font-weight:bold; font-family:Arial,Helvetica,sans-serif; padding-left:0; float:right; position:relative; margin-top:-2.4em; margin-bottom:-2em; }
.form-row .object-tools { margin-top:5px; margin-bottom:5px; float:none; height:2em; padding-left:3.5em; }
.object-tools li { display:block; float:left; background:url(../img/admin/tool-left.gif) 0 0 no-repeat; padding:0 0 0 8px; margin-left:2px; height:16px; }
.object-tools li:hover { background:url(../img/admin/tool-left_over.gif) 0 0 no-repeat; }
.object-tools a:link, .object-tools a:visited { display:block; float:left; color:white; padding:.1em 14px .1em 8px; height:14px; background:#999 url(../img/admin/tool-right.gif) 100% 0 no-repeat; }
.object-tools a:hover, .object-tools li:hover a { background:#5b80b2 url(../img/admin/tool-right_over.gif) 100% 0 no-repeat; }
.object-tools a.viewsitelink, .object-tools a.golink { background:#999 url(../img/admin/tooltag-arrowright.gif) top right no-repeat; padding-right:28px; }
.object-tools a.viewsitelink:hover, .object-tools a.golink:hover { background:#5b80b2 url(../img/admin/tooltag-arrowright_over.gif) top right no-repeat; }
.object-tools a.addlink { background:#999 url(../img/admin/tooltag-add.gif) top right no-repeat; padding-right:28px; }
.object-tools a.addlink:hover { background:#5b80b2 url(../img/admin/tooltag-add_over.gif) top right no-repeat; }
/* OBJECT HISTORY */
table#change-history { width:100%; }
table#change-history tbody th { width:16em; }

View File

@ -0,0 +1,29 @@
/* PAGE STRUCTURE */
#container { position:relative; width:100%; min-width:760px; }
#content { margin:10px 15px; }
#header { width:100%; }
#content-main { float:left; width:100%; }
#content-related { float:right; width:220px; position:relative; margin-right:-230px; }
#footer { clear:both; padding:10px; }
/* COLUMN TYPES */
.colMS { margin-right:245px !important; }
.colSM { margin-left:245px !important; }
.colSM #content-related { float:left; margin-right:0; margin-left:-230px; }
.colSM #content-main { float:right; }
.popup .colM { width:95%; }
.subcol { float:left; width:46%; margin-right:15px; }
.dashboard #content { width:500px; }
/* HEADER */
#header { background:#417690; color:#ffc; overflow:hidden; }
#header a:link, #header a:visited { color:white; }
#header a:hover { text-decoration:underline; }
#branding h1 { padding:0 10px; font-size:18px; margin:8px 0; font-weight:normal; color:#f4f379; }
#branding h2 { padding:0 10px; font-size:14px; margin:-8px 0 8px 0; font-weight:normal; color:#ffc; }
#user-tools { position:absolute; top:0; right:0; padding:1.2em 10px; font-size:11px; text-align:right; }
/* SIDEBAR */
#content-related h3 { font-size:12px; color:#666; margin-bottom:3px; }
#content-related h4 { font-size:11px; }
#content-related .module h2 { background:#eee url(../img/admin/nav-bg.gif) bottom left repeat-x; color:#666; }

View File

@ -0,0 +1,13 @@
@import url('base.css');
@import url('layout.css');
/* LOGIN FORM */
body.login { background:#eee; }
.login #container { background:white; border:1px solid #ccc; width:28em; min-width:300px; margin-left:auto; margin-right:auto; margin-top:100px; }
.login #content-main { width:100%; }
.login form { margin-top:1em; }
.login .form-row { padding:4px 0; float:left; width:100%; }
.login .form-row label { float:left; width:7em; padding-right:0.5em; line-height:2em; text-align:right; font-size:1em; color:#333; }
.login .form-row #id_username, .login .form-row #id_password { width:16em; }
.login span.help { font-size:10px; display:block; }
.login .submit-row { clear:both; padding:1em 0 0 7.4em; }

View File

@ -1,7 +1,6 @@
* html #container { position:static; } /* keep header from flowing off the page */
* html .colMS #content-related { margin-right:0; margin-left:10px; position:static; } /* put the right sidebars back on the page */
* html .colSM #content-related { margin-right:10px; margin-left:-115px; position:static; } /* put the left sidebars back on the page */
* html .form-row { height:1%; }
* html .dashboard #content { width:768px; } /* proper fixed width for dashboard in IE6 */
* html .dashboard #content-main { width:535px; } /* proper fixed width for dashboard in IE6 */
* html #content { width /**/: 768px; } /* fixed width for IE5 */
* html #content-main { width /**/: 535px; } /* fixed width for IE5 */
* html .dashboard #content-main { width:535px; } /* proper fixed width for dashboard in IE6 */

View File

@ -0,0 +1,101 @@
/* SELECTOR (FILTER INTERFACE) */
.selector { width:580px; float:left; }
.selector select { width:270px; height:17.2em; }
.selector-available, .selector-chosen { float:left; width:270px; text-align:center; margin-bottom:5px; }
.selector-available h2, .selector-chosen h2 { border:1px solid #ccc; }
.selector .selector-available h2 { background:white url(../img/admin/nav-bg.gif) bottom left repeat-x; color:#666; }
.selector .selector-filter { background:white; border:1px solid #ccc; border-width:0 1px; padding:3px; color:#999; font-size:10px; margin:0; text-align:left; }
.selector .selector-chosen .selector-filter { padding:4px 5px; }
.selector .selector-available input { width:230px; }
.selector ul.selector-chooser { float:left; width:22px; height:50px; background:url(../img/admin/chooser-bg.gif) top center no-repeat; margin:8em 3px 0 3px; padding:0; }
.selector-chooser li { margin:0; padding:3px; list-style-type:none; }
.selector select { margin-bottom:5px; margin-top:0; }
.selector-add, .selector-remove { width:16px; height:16px; display:block; text-indent:-3000px; }
.selector-add { background:url(../img/admin/selector-add.gif) top center no-repeat; margin-bottom:2px; }
.selector-remove { background:url(../img/admin/selector-remove.gif) top center no-repeat; }
a.selector-chooseall, a.selector-clearall { display:block; width:6em; text-align:left; margin-left:auto; margin-right:auto; font-weight:bold; color:#666; padding:3px 0 3px 18px; }
a.selector-chooseall:hover, a.selector-clearall:hover { color:#036; }
a.selector-chooseall { width:7em; background:url(../img/admin/selector-addall.gif) left center no-repeat; }
a.selector-clearall { background:url(../img/admin/selector-removeall.gif) left center no-repeat; }
/* STACKED SELECTORS */
.stacked { float:left; width:500px; }
.stacked select { width:480px; height:10.1em; }
.stacked .selector-available, .stacked .selector-chosen { width:480px; }
.stacked .selector-available { margin-bottom:0; }
.stacked .selector-available input { width:442px; }
.stacked ul.selector-chooser { height:22px; width:50px; margin:0 0 3px 40%; background:url(../img/admin/chooser_stacked-bg.gif) top center no-repeat; }
.stacked .selector-chooser li { float:left; padding:3px 3px 3px 5px; }
.stacked .selector-chooseall, .stacked .selector-clearall { display:none; }
.stacked .selector-add { background-image:url(../img/admin/selector_stacked-add.gif); }
.stacked .selector-remove { background-image:url(../img/admin/selector_stacked-remove.gif); }
/* DATE AND TIME */
p.datetime { line-height:20px; margin:0; padding:0; color:#666; font-size:11px; font-weight:bold; }
.datetime span { font-size:11px; color:#ccc; font-weight:normal; white-space:nowrap; }
.vDateField { margin-left:4px; }
table p.datetime { font-size:10px; margin-left:0; padding-left:0; }
/* FILE UPLOADS */
p.file-upload { line-height:20px; margin:0; padding:0; color:#666; font-size:11px; font-weight:bold; }
.file-upload a { font-weight:normal; }
.file-upload .deletelink { margin-left:5px; }
/* CALENDARS & CLOCKS */
.calendarbox, .clockbox { margin:5px auto; font-size:11px; width:16em; text-align:center; background:white; position:relative; }
.clockbox { width:9em; }
.calendar { margin:0; padding: 0; }
.calendar table { margin:0; padding:0; border-collapse:collapse; background:white; width:99%; }
.calendar caption, .calendarbox h2 { margin: 0; font-size:11px; text-align:center; border-top:none; }
.calendar th { font-size:10px; color:#666; padding:2px 3px; text-align:center; background:#e1e1e1 url(../img/admin/nav-bg.gif) 0 50% repeat-x; border-bottom:1px solid #ddd; }
.calendar td { font-size:11px; text-align: center; padding: 0; border-top:1px solid #eee; border-bottom:none; }
.calendar td.selected a { background: #C9DBED; }
.calendar td.nonday { background:#efefef; }
.calendar td.today a { background:#ffc; }
.calendar td a, .timelist a { display: block; font-weight:bold; padding:4px; text-decoration: none; color:#444; }
.calendar td a:hover, .timelist a:hover { background: #5b80b2; color:white; }
.calendar td a:active, .timelist a:active { background: #036; color:white; }
.calendarnav { font-size:10px; text-align: center; color:#ccc; margin:0; padding:1px 3px; }
.calendarnav a:link, #calendarnav a:visited, #calendarnav a:hover { color: #999; }
.calendar-shortcuts { background:white; font-size:10px; line-height:11px; border-top:1px solid #eee; padding:3px 0 4px; color:#ccc; }
.calendarbox .calendarnav-previous, .calendarbox .calendarnav-next { display:block; position:absolute; font-weight:bold; font-size:12px; background:#C9DBED url(../img/admin/default-bg.gif) bottom left repeat-x; padding:1px 4px 2px 4px; color:white; }
.calendarnav-previous:hover, .calendarnav-next:hover { background:#036; }
.calendarnav-previous { top:0; left:0; }
.calendarnav-next { top:0; right:0; }
.calendar-cancel { margin:0 !important; padding:0; font-size:10px; background:#e1e1e1 url(../img/admin/nav-bg.gif) 0 50% repeat-x; border-top:1px solid #ddd; }
.calendar-cancel a { padding:2px; color:#999; }
ul.timelist, .timelist li { list-style-type:none; margin:0; padding:0; }
.timelist a { padding:2px; }
/* INLINE ORDERER */
ul.orderer { position:relative; padding:0 !important; margin:0 !important; list-style-type:none; }
ul.orderer li { list-style-type:none; display:block; padding:0; margin:0; border:1px solid #bbb; border-width:0 1px 1px 0; white-space:nowrap; overflow:hidden; background:#e2e2e2 url(../img/admin/nav-bg-grabber.gif) repeat-y; }
ul.orderer li:hover { cursor:move; background-color:#ddd; }
ul.orderer li a.selector { margin-left:12px; overflow:hidden; width:83%; font-size:10px !important; padding:0.6em 0; }
ul.orderer li a:link, ul.orderer li a:visited { color:#333; }
ul.orderer li .inline-deletelink { position:absolute; right:4px; margin-top:0.6em; }
ul.orderer li.selected { background-color:#f8f8f8; border-right-color:#f8f8f8; }
ul.orderer li.deleted { background:#bbb url(../img/admin/deleted-overlay.gif); }
ul.orderer li.deleted a:link, ul.orderer li.deleted a:visited { color:#888; }
ul.orderer li.deleted .inline-deletelink { background-image:url(../img/admin/inline-restore.png); }
ul.orderer li.deleted:hover, ul.orderer li.deleted a.selector:hover { cursor:default; }
/* EDIT INLINE */
.inline-deletelink { display:block; text-indent:-9999px; background:transparent url(../img/admin/inline-delete.png) no-repeat; width:15px; height:15px; margin:0.4em 0; border: 0px none; }
.inline-deletelink:hover { background-position:-15px 0; cursor:pointer; }
.editinline button.addlink { border: 0px none; color: #5b80b2; font-size: 100%; cursor: pointer; }
.editinline button.addlink:hover { color: #036; cursor: pointer; }
.editinline table .help { text-align:right; float:right; padding-left:2em; }
.editinline tfoot .addlink { white-space:nowrap; }
.editinline table thead th:last-child { border-left:none; }
.editinline tr.deleted { background:#ddd url(../img/admin/deleted-overlay.gif); }
.editinline tr.deleted .inline-deletelink { background-image:url(../img/admin/inline-restore.png); }
.editinline tr.deleted td:hover { cursor:default; }
.editinline tr.deleted td:first-child { background-image:none !important; }
/* EDIT INLINE - STACKED */
.editinline-stacked { min-width:758px; }
.editinline-stacked .inline-object { margin-left:210px; background:white; }
.editinline-stacked .inline-source { float:left; width:200px; background:#f8f8f8; }
.editinline-stacked .inline-splitter { float:left; width:9px; background:#f8f8f8 url(../img/admin/inline-splitter-bg.gif) 50% 50% no-repeat; border-right:1px solid #ccc; }
.editinline-stacked .controls { clear:both; background:#e1e1e1 url(../img/admin/nav-bg.gif) top left repeat-x; padding:3px 4px; font-size:11px; border-top:1px solid #ddd; }

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 477 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 781 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 447 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 B

View File

@ -39,7 +39,7 @@ function dismissAddAnotherPopup(win, newId, newRepr) {
if (elem.nodeName == 'SELECT') {
var o = new Option(newRepr, newId);
elem.options[elem.options.length] = o;
elem.selectedIndex = elem.length - 1;
o.selected = true;
} else if (elem.nodeName == 'INPUT') {
elem.value = newId;
}

View File

@ -0,0 +1,51 @@
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import User
from django.utils.translation import gettext_lazy as _
ADDITION = 1
CHANGE = 2
DELETION = 3
class LogEntryManager(models.Manager):
def log_action(self, user_id, content_type_id, object_id, object_repr, action_flag, change_message=''):
e = self.model(None, None, user_id, content_type_id, object_id, object_repr[:200], action_flag, change_message)
e.save()
class LogEntry(models.Model):
action_time = models.DateTimeField(_('action time'), auto_now=True)
user = models.ForeignKey(User)
content_type = models.ForeignKey(ContentType, blank=True, null=True)
object_id = models.TextField(_('object id'), blank=True, null=True)
object_repr = models.CharField(_('object repr'), maxlength=200)
action_flag = models.PositiveSmallIntegerField(_('action flag'))
change_message = models.TextField(_('change message'), blank=True)
objects = LogEntryManager()
class Meta:
verbose_name = _('log entry')
verbose_name_plural = _('log entries')
db_table = 'django_admin_log'
ordering = ('-action_time',)
def __repr__(self):
return str(self.action_time)
def is_addition(self):
return self.action_flag == ADDITION
def is_change(self):
return self.action_flag == CHANGE
def is_deletion(self):
return self.action_flag == DELETION
def get_edited_object(self):
"Returns the edited object represented by this log entry"
return self.content_type.get_object_for_this_type(pk=self.object_id)
def get_admin_url(self):
"""
Returns the admin URL to edit the object represented by this log entry.
This is relative to the Django admin index page.
"""
return "%s/%s/%s/" % (self.content_type.app_label, self.content_type.model, self.object_id)

View File

@ -1 +0,0 @@
__all__ = ['admin']

View File

@ -1,50 +0,0 @@
from django.core import meta
from django.models import auth, core
from django.utils.translation import gettext_lazy as _
class LogEntry(meta.Model):
action_time = meta.DateTimeField(_('action time'), auto_now=True)
user = meta.ForeignKey(auth.User)
content_type = meta.ForeignKey(core.ContentType, blank=True, null=True)
object_id = meta.TextField(_('object id'), blank=True, null=True)
object_repr = meta.CharField(_('object repr'), maxlength=200)
action_flag = meta.PositiveSmallIntegerField(_('action flag'))
change_message = meta.TextField(_('change message'), blank=True)
class META:
module_name = 'log'
verbose_name = _('log entry')
verbose_name_plural = _('log entries')
db_table = 'django_admin_log'
ordering = ('-action_time',)
module_constants = {
'ADDITION': 1,
'CHANGE': 2,
'DELETION': 3,
}
def __repr__(self):
return str(self.action_time)
def is_addition(self):
return self.action_flag == ADDITION
def is_change(self):
return self.action_flag == CHANGE
def is_deletion(self):
return self.action_flag == DELETION
def get_edited_object(self):
"Returns the edited object represented by this log entry"
return self.get_content_type().get_object_for_this_type(pk=self.object_id)
def get_admin_url(self):
"""
Returns the admin URL to edit the object represented by this log entry.
This is relative to the Django admin index page.
"""
return "%s/%s/%s/" % (self.get_content_type().get_package(), self.get_content_type().python_module_name, self.object_id)
def _module_log_action(user_id, content_type_id, object_id, object_repr, action_flag, change_message=''):
e = LogEntry(None, None, user_id, content_type_id, object_id, object_repr[:200], action_flag, change_message)
e.save()

View File

@ -1,4 +1,4 @@
{% extends "admin/base_site" %}
{% extends "admin/base_site.html" %}
{% load i18n %}
{% block title %}{% trans 'Page not found' %}{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends "admin/base_site" %}
{% extends "admin/base_site.html" %}
{% load i18n %}
{% block breadcrumbs %}<div class="breadcrumbs"><a href="/">{% trans "Home" %}</a> &rsaquo; {% trans "Server error" %}</div>{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends "admin/base" %}
{% extends "admin/base.html" %}
{% load i18n %}
{% block title %}{{ title }} | {% trans 'Django site admin' %}{% endblock %}

View File

@ -1,36 +1,39 @@
{% extends "admin/base_site" %}
{% extends "admin/base_site.html" %}
{% load i18n admin_modify adminmedia %}
{% block extrahead %}{{ block.super }}
<script type="text/javascript" src="../../../jsi18n/"></script>
{% for js in bound_manipulator.javascript_imports %}{% include_admin_script js %}{% endfor %}
{% for js in javascript_imports %}{% include_admin_script js %}{% endfor %}
{% endblock %}
{% block coltype %}{{ bound_manipulator.coltype }}{% endblock %}
{% block bodyclass %}{{ app_label }}-{{ bound_manipulator.object_name.lower }} change-form{% endblock %}
{% block stylesheet %}{% admin_media_prefix %}css/forms.css{% endblock %}
{% block coltype %}{% if ordered_objects %}colMS{% else %}colM{% endif %}{% endblock %}
{% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %}
{% block userlinks %}<a href="../../../doc/">{% trans 'Documentation' %}</a> / <a href="../../../password_change/">{% trans 'Change password' %}</a> / <a href="../../../logout/">{% trans 'Log out' %}</a>{% endblock %}
{% block breadcrumbs %}{% if not is_popup %}
<div class="breadcrumbs">
<a href="../../../">{% trans "Home" %}</a> &rsaquo;
<a href="../">{{ bound_manipulator.verbose_name_plural|capfirst }}</a> &rsaquo;
{% if add %}{% trans "Add" %} {{ bound_manipulator.verbose_name }}{% else %}{{ bound_manipulator.original|striptags|truncatewords:"18" }}{% endif %}
<a href="../">{{ opts.verbose_name_plural|capfirst }}</a> &rsaquo;
{% if add %}{% trans "Add" %} {{ opts.verbose_name }}{% else %}{{ original|striptags|truncatewords:"18" }}{% endif %}
</div>
{% endif %}{% endblock %}
{% block content %}<div id="content-main">
{% if change %}{% if not is_popup %}
<ul class="object-tools"><li><a href="history/" class="historylink">{% trans "History" %}</a></li>
{% if bound_manipulator.has_absolute_url %}<li><a href="/r/{{ bound_manipulator.content_type_id }}/{{ object_id }}/" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif%}
{% if has_absolute_url %}<li><a href="../../../r/{{ content_type_id }}/{{ object_id }}/" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif%}
</ul>
{% endif %}{% endif %}
<form {{ bound_manipulator.form_enc_attrib }} action="{{ form_url }}" method="post">{% block form_top %}{% endblock %}
<form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="post">{% block form_top %}{% endblock %}
<div>
{% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %}
{% if bound_manipulator.save_on_top %}{% submit_row bound_manipulator %}{% endif %}
{% if opts.admin.save_on_top %}{% submit_row %}{% endif %}
{% if form.error_dict %}
<p class="errornote">
{% blocktrans count form.error_dict.items|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %}
</p>
{% endif %}
{% for bound_field_set in bound_manipulator.bound_field_sets %}
{% for bound_field_set in bound_field_sets %}
<fieldset class="module aligned {{ bound_field_set.classes }}">
{% if bound_field_set.name %}<h2>{{ bound_field_set.name }}</h2>{% endif %}
{% if bound_field_set.description %}<div class="description">{{ bound_field_set.description }}</div>{% endif %}
{% for bound_field_line in bound_field_set %}
{% admin_field_line bound_field_line %}
{% for bound_field in bound_field_line %}
@ -41,7 +44,7 @@
{% endfor %}
{% block after_field_sets %}{% endblock %}
{% if change %}
{% if bound_manipulator.ordered_objects %}
{% if ordered_objects %}
<fieldset class="module"><h2>{% trans "Ordering" %}</h2>
<div class="form-row{% if form.order_.errors %} error{% endif %} ">
{% if form.order_.errors %}{{ form.order_.html_error_list }}{% endif %}
@ -49,27 +52,17 @@
</div></fieldset>
{% endif %}
{% endif %}
{% for related_object in bound_manipulator.inline_related_objects %}{% edit_inline related_object %}{% endfor %}
{% for related_object in inline_related_objects %}{% edit_inline related_object %}{% endfor %}
{% block after_related_objects %}{% endblock %}
{% submit_row bound_manipulator %}
{% submit_row %}
{% if add %}
<script type="text/javascript">document.getElementById("{{ bound_manipulator.first_form_field_id }}").focus();</script>
<script type="text/javascript">document.getElementById("{{ first_form_field_id }}").focus();</script>
{% endif %}
{% if bound_manipulator.auto_populated_fields %}
{% if auto_populated_fields %}
<script type="text/javascript">
{% auto_populated_field_script bound_manipulator.auto_populated_fields change %}
{% auto_populated_field_script auto_populated_fields change %}
</script>
{% endif %}
{% if change %}
{% if bound_manipulator.ordered_objects %}
{% if form.order_objects %}<ul id="orderthese">
{% for object in form.order_objects %}
<li id="p{% object_pk bound_manipulator object %}">
<span id="handlep{% object_pk bound_manipulator object %}">{{ object|truncatewords:"5" }}</span>
</li>
{% endfor %}
</ul>{% endif %}
{% endif %}
{% endif %}
</div>
</form></div>
{% endblock %}

View File

@ -1,8 +1,9 @@
{% extends "admin/base_site" %}
{% extends "admin/base_site.html" %}
{% load adminmedia admin_list i18n %}
{% block stylesheet %}{% admin_media_prefix %}css/changelists.css{% endblock %}
{% block bodyclass %}change-list{% endblock %}
{% block userlinks %}<a href="../../doc/">{% trans 'Documentation' %}</a> / <a href="../../password_change/">{% trans 'Change password' %}</a> / <a href="../../logout/">{% trans 'Log out' %}</a>{% endblock %}
{% if not is_popup %}{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans "Home" %}</a> &rsaquo; {{ cl.opts.verbose_name_plural|capfirst }} </div>{% endblock %}{% endif %}
{% if not is_popup %}{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans "Home" %}</a> &rsaquo; {{ cl.opts.verbose_name_plural|capfirst }}</div>{% endblock %}{% endif %}
{% block coltype %}flex{% endblock %}
{% block content %}
<div id="content-main">

View File

@ -1,6 +1,14 @@
{% extends "admin/base_site" %}
{% extends "admin/base_site.html" %}
{% load i18n %}
{% block userlinks %}<a href="../../../../doc/">{% trans 'Documentation' %}</a> / <a href="../../../../password_change/">{% trans 'Change password' %}</a> / <a href="../../../../logout/">{% trans 'Log out' %}</a>{% endblock %}
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="../../../../">{% trans "Home" %}</a> &rsaquo;
<a href="../../">{{ opts.verbose_name_plural|capfirst }}</a> &rsaquo;
<a href="../">{{ object|striptags|truncatewords:"18" }}</a> &rsaquo;
{% trans 'Delete' %}
</div>
{% endblock %}
{% block content %}
{% if perms_lacking %}
<p>{% blocktrans %}Deleting the {{ object_name }} '{{ object }}' would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:{% endblocktrans %}</p>
@ -13,8 +21,10 @@
<p>{% blocktrans %}Are you sure you want to delete the {{ object_name }} "{{ object }}"? All of the following related items will be deleted:{% endblocktrans %}</p>
<ul>{{ deleted_objects|unordered_list }}</ul>
<form action="" method="post">
<div>
<input type="hidden" name="post" value="yes" />
<input type="submit" value="{% trans "Yes, I'm sure" %}" />
</div>
</form>
{% endif %}
{% endblock %}

View File

@ -13,4 +13,4 @@
{% endif %}
{% endfor %}
{% endfor %}
</fieldset>
</fieldset>

View File

@ -9,14 +9,6 @@
{% if not bound_field.has_label_first %}
{% field_label bound_field %}
{% endif %}
{% if change %}
{% if bound_field.field.primary_key %}
{{ bound_field.original_value }}
{% endif %}
{% if bound_field.raw_id_admin %}
{% if bound_field.existing_display %}&nbsp;<strong>{{ bound_field.existing_display|truncatewords:"14" }}</strong>{% endif %}
{% endif %}
{% endif %}
{% if bound_field.field.help_text %}<p class="help">{{ bound_field.field.help_text }}</p>{% endif %}
{% endfor %}
</div>

View File

@ -1,6 +1,7 @@
{% extends "admin/base_site" %}
{% extends "admin/base_site.html" %}
{% load i18n %}
{% block stylesheet %}{% load adminmedia %}{% admin_media_prefix %}css/dashboard.css{% endblock %}
{% block coltype %}colMS{% endblock %}
{% block bodyclass %}dashboard{% endblock %}
{% block breadcrumbs %}{% endblock %}
@ -13,14 +14,14 @@
{% if app_list %}
{% for app in app_list %}
<div class="module">
<h2>{{ app.name }}</h2>
<table>
<table summary="{% blocktrans with app.name as name %}Models available in the {{ name }} application.{% endblocktrans %}">
<caption>{{ app.name }}</caption>
{% for model in app.models %}
<tr>
{% if model.perms.change %}
<th><a href="{{ model.admin_url }}">{{ model.name }}</a></th>
<th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th>
{% else %}
<th>{{ model.name }}</th>
<th scope="row">{{ model.name }}</th>
{% endif %}
{% if model.perms.add %}
@ -57,7 +58,7 @@
{% else %}
<ul class="actionlist">
{% for entry in admin_log %}
<li class="{% if entry.is_addition %}addlink{% endif %}{% if entry.is_change %}changelink{% endif %}{% if entry.is_deletion %}deletelink{% endif %}">{% if not entry.is_deletion %}<a href="{{ entry.get_admin_url }}">{% endif %}{{ entry.object_repr|escape }}{% if not entry.is_deletion %}</a>{% endif %}<br /><span class="mini quiet">{{ entry.get_content_type.name|capfirst }}</span></li>
<li class="{% if entry.is_addition %}addlink{% endif %}{% if entry.is_change %}changelink{% endif %}{% if entry.is_deletion %}deletelink{% endif %}">{% if not entry.is_deletion %}<a href="{{ entry.get_admin_url }}">{% endif %}{{ entry.object_repr|escape }}{% if not entry.is_deletion %}</a>{% endif %}<br /><span class="mini quiet">{{ entry.content_type.name|capfirst }}</span></li>
{% endfor %}
</ul>
{% endif %}

View File

@ -1,6 +1,9 @@
{% extends "admin/base_site" %}
{% extends "admin/base_site.html" %}
{% load i18n %}
{% block stylesheet %}{% load adminmedia %}{% admin_media_prefix %}css/login.css{% endblock %}
{% block bodyclass %}login{% endblock %}
{% block content_title %}{% endblock %}
{% block breadcrumbs %}{% endblock %}
{% block content %}
@ -9,20 +12,18 @@
<p class="errornote">{{ error_message }}</p>
{% endif %}
<div id="content-main">
<form action="{{ app_path }}" method="post">
<p class="aligned">
<label for="id_username">{% trans 'Username:' %}</label> <input type="text" name="username" id="id_username" />
</p>
<p class="aligned">
<label for="id_password">{% trans 'Password:' %}</label> <input type="password" name="password" id="id_password" />
<input type="hidden" name="this_is_the_login_form" value="1" />
<input type="hidden" name="post_data" value="{{ post_data }}" />{% comment %} <span class="help">{% trans 'Have you <a href="/password_reset/">forgotten your password</a>?' %}</span>{% endcomment %}
</p>
<div class="aligned ">
<label>&nbsp;</label><input type="submit" value="{% trans 'Log in' %}" />
</div>
<form action="{{ app_path }}" method="post" id="login-form">
<div class="form-row">
<label for="id_username">{% trans 'Username:' %}</label> <input type="text" name="username" id="id_username" />
</div>
<div class="form-row">
<label for="id_password">{% trans 'Password:' %}</label> <input type="password" name="password" id="id_password" />
<input type="hidden" name="this_is_the_login_form" value="1" />
<input type="hidden" name="post_data" value="{{ post_data }}" /> {% comment %}<span class="help">{% trans 'Have you <a href="/password_reset/">forgotten your password</a>?' %}</span>{% endcomment %}
</div>
<div class="submit-row">
<label>&nbsp;</label><input type="submit" value="{% trans 'Log in' %}" />
</div>
</form>
<script type="text/javascript">

View File

@ -1,4 +1,4 @@
{% extends "admin/base_site" %}
{% extends "admin/base_site.html" %}
{% load i18n %}
{% block userlinks %}<a href="../../../../doc/">{% trans 'Documentation' %}</a> / <a href="../../../../password_change/">{% trans 'Change password' %}</a> / <a href="../../../../logout/">{% trans 'Log out' %}</a>{% endblock %}
{% block breadcrumbs %}
@ -15,16 +15,16 @@
<table id="change-history">
<thead>
<tr>
<th>{% trans 'Date/time' %}</th>
<th>{% trans 'User' %}</th>
<th>{% trans 'Action' %}</th>
<th scope="col">{% trans 'Date/time' %}</th>
<th scope="col">{% trans 'User' %}</th>
<th scope="col">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
{% for action in action_list %}
<tr>
<th>{{ action.action_time|date:_("DATE_WITH_TIME_FULL") }}</th>
<td>{{ action.get_user.username }}{% if action.get_user.first_name %} ({{ action.get_user.first_name }} {{ action.get_user.last_name }}){% endif %}</td>
<th scope="row">{{ action.action_time|date:_("DATE_WITH_TIME_FULL") }}</th>
<td>{{ action.user.username }}{% if action.user.first_name %} ({{ action.user.first_name }} {{ action.user.last_name }}){% endif %}</td>
<td>{{ action.change_message}}</td>
</tr>
{% endfor %}

View File

@ -3,7 +3,7 @@
{% if cl.lookup_opts.admin.search_fields %}
<div id="toolbar"><form id="changelist-search" action="" method="get">
<div><!-- DIV needed for valid HTML -->
<label><img src="{% admin_media_prefix %}img/admin/icon_searchbox.png" alt="Search" /></label>
<label for="searchbar"><img src="{% admin_media_prefix %}img/admin/icon_searchbox.png" alt="Search" /></label>
<input type="text" size="40" name="{{ search_var }}" value="{{ cl.query|escape }}" id="searchbar" />
<input type="submit" value="{% trans 'Go' %}" />
{% if show_result_count %}

View File

@ -1,4 +1,4 @@
{% extends "admin/base_site" %}
{% extends "admin/base_site.html" %}
{% block content %}

View File

@ -1,4 +1,4 @@
{% extends "admin/base_site" %}
{% extends "admin/base_site.html" %}
{% block breadcrumbs %}{% load i18n %}<div class="breadcrumbs"><a href="../../">{% trans "Home" %}</a> &rsaquo; <a href="../">{% trans "Documentation" %}</a> &rsaquo; {% trans "Bookmarklets" %}</div>{% endblock %}
{% block userlinks %}<a href="../../password_change/">{% trans 'Change password' %}</a> / <a href="../../logout/">{% trans 'Log out' %}</a>{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends "admin/base_site" %}
{% extends "admin/base_site.html" %}
{% load i18n %}
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">Home</a> &rsaquo; Documentation</div>{% endblock %}
{% block userlinks %}<a href="../password_change/">{% trans 'Change password' %}</a> / <a href="../logout/">{% trans 'Log out' %}</a>{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends "admin/base_site" %}
{% extends "admin/base_site.html" %}
{% load i18n %}
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">Home</a> &rsaquo; Documentation</div>{% endblock %}
{% block userlinks %}<a href="../password_change/">{% trans 'Change password' %}</a> / <a href="../logout/">{% trans 'Log out' %}</a>{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends "admin/base_site" %}
{% extends "admin/base_site.html" %}
{% load i18n %}
{% block userlinks %}<a href="../../../password_change/">{% trans 'Change password' %}</a> / <a href="../../../logout/">{% trans 'Log out' %}</a>{% endblock %}
{% block extrahead %}
@ -41,6 +41,6 @@
</table>
</div>
<p class="small"><a href="../">&lsaquo; Back to Models Documentation</p>
<p class="small"><a href="../">&lsaquo; Back to Models Documentation</a></p>
</div>
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends "admin/base_site" %}
{% extends "admin/base_site.html" %}
{% load i18n %}
{% block coltype %}colSM{% endblock %}
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">Home</a> &rsaquo; <a href="../">Documentation</a> &rsaquo; Models</div>{% endblock %}
@ -8,18 +8,19 @@
{% block content %}
<h1>Models Documentation</h1>
<h1>Model documentation</h1>
{% regroup models by app_label as grouped_models %}
<div id="content-main">
{% regroup models|dictsort:"module" by module as grouped_models %}
{% for group in grouped_models %}
<div class="module">
<h2 id='{{ group.grouper }}'>{{ group.grouper }}</h2>
<h2 id="{{ group.grouper }}">{{ group.grouper|capfirst }}</h2>
<table class="xfull">
{% for model in group.list %}
<tr>
<th><a href="{{ model.name }}/">{{ model.class }}</a></th>
<th><a href="{{ model.app_label }}.{{ model.object_name.lower }}/">{{ model.object_name }}</a></th>
</tr>
{% endfor %}
</table>
@ -32,11 +33,11 @@
{% block sidebar %}
<div id="content-related" class="sidebar">
<div class="module">
<h2>Model Groups Quick List</h2>
<h2>Model groups</h2>
<ul>
{% regroup models|dictsort:"module" by module as grouped_models %}
{% regroup models by app_label as grouped_models %}
{% for group in grouped_models %}
<li><a href="#{{ group.grouper }}">{{ group.grouper }}</a></li>
<li><a href="#{{ group.grouper }}">{{ group.grouper|capfirst }}</a></li>
{% endfor %}
</ul>
</div>

View File

@ -1,4 +1,4 @@
{% extends "admin/base_site" %}
{% extends "admin/base_site.html" %}
{% load i18n %}
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../../">Home</a> &rsaquo; <a href="../../">Documentation</a> &rsaquo; Templates &rsaquo; {{ name }}</div>{% endblock %}
{% block userlinks %}<a href="../../../password_change/">{% trans 'Change password' %}</a> / <a href="../../../logout/">{% trans 'Log out' %}</a>{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends "admin/base_site" %}
{% extends "admin/base_site.html" %}
{% load i18n %}
{% block coltype %}colSM{% endblock %}
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">Home</a> &rsaquo; <a href="../">Documentation</a> &rsaquo; filters</div>{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends "admin/base_site" %}
{% extends "admin/base_site.html" %}
{% load i18n %}
{% block coltype %}colSM{% endblock %}
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">Home</a> &rsaquo; <a href="../">Documentation</a> &rsaquo; Tags</div>{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends "admin/base_site" %}
{% extends "admin/base_site.html" %}
{% load i18n %}
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../../">Home</a> &rsaquo; <a href="../../">Documentation</a> &rsaquo; <a href="../">Views</a> &rsaquo; {{ name }}</div>{% endblock %}
{% block userlinks %}<a href="../../../password_change/">{% trans 'Change password' %}</a> / <a href="../../../logout/">{% trans 'Log out' %}</a>{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends "admin/base_site" %}
{% extends "admin/base_site.html" %}
{% load i18n %}
{% block coltype %}colSM{% endblock %}
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">Home</a> &rsaquo; <a href="../">Documentation</a> &rsaquo; Views</div>{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends "admin/base_site" %}
{% extends "admin/base_site.html" %}
{% load i18n %}
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a></div>{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends "admin/base_site" %}
{% extends "admin/base_site.html" %}
{% load i18n %}
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password change' %}</div>{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends "admin/base_site" %}
{% extends "admin/base_site.html" %}
{% load i18n %}
{% block userlinks %}<a href="../doc/">{% trans 'Documentation' %}</a> / {% trans 'Change password' %} / <a href="../logout/">{% trans 'Log out' %}</a>{% endblock %}
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password change' %}</div>{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends "admin/base_site" %}
{% extends "admin/base_site.html" %}
{% load i18n %}
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password reset' %}</div>{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends "admin/base_site" %}
{% extends "admin/base_site.html" %}
{% load i18n %}
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password reset' %}</div>{% endblock %}

View File

@ -1,12 +1,20 @@
{% load admin_modify adminmedia %}
{% output_all bound_field.form_fields %}
{% if bound_field.raw_id_admin %}
{% if bound_field.field.rel.limit_choices_to %}
<a href="../../../{{ bound_field.field.rel.to.app_label }}/{{ bound_field.field.rel.to.module_name }}/?{% for limit_choice in bound_field.field.rel.limit_choices_to.items %}{% if not forloop.first %}{{"&"|escape}}{% endif %}{{ limit_choice|join:"=" }}{% endfor %}" class="related-lookup" id="lookup_{{ bound_field.element_id }}" onclick="return showRelatedObjectLookupPopup(this);"> <img src="{% admin_media_prefix %}img/admin/selector-search.gif" width="16" height="16" alt="Lookup"></a>
{% else %}
<a href="../../../{{ bound_field.field.rel.to.app_label }}/{{ bound_field.field.rel.to.module_name }}/" class="related-lookup" id="lookup_{{ bound_field.element_id }}" onclick="return showRelatedObjectLookupPopup(this);"> <img src="{% admin_media_prefix %}img/admin/selector-search.gif" width="16" height="16" alt="Lookup"></a>
{% endif %}
{% if bound_field.field.rel.limit_choices_to %}
<a href="{{ bound_field.related_url }}?{% for limit_choice in bound_field.field.rel.limit_choices_to.items %}{% if not forloop.first %}&amp;{% endif %}{{ limit_choice|join:"=" }}{% endfor %}" class="related-lookup" id="lookup_{{ bound_field.element_id }}" onclick="return showRelatedObjectLookupPopup(this);"> <img src="{% admin_media_prefix %}img/admin/selector-search.gif" width="16" height="16" alt="Lookup"></a>
{% else %}
<a href="{{ bound_field.related_url }}" class="related-lookup" id="lookup_{{ bound_field.element_id }}" onclick="return showRelatedObjectLookupPopup(this);"> <img src="{% admin_media_prefix %}img/admin/selector-search.gif" width="16" height="16" alt="Lookup"></a>
{% endif %}
{% else %}
{% if bound_field.needs_add_label %}
<a href="../../../{{ bound_field.field.rel.to.app_label }}/{{ bound_field.field.rel.to.module_name }}/add/" class="add-another" id="add_{{ bound_field.element_id }}" onclick="return showAddAnotherPopup(this);"> <img src="{% admin_media_prefix %}img/admin/icon_addlink.gif" width="10" height="10" alt="Add Another"/></a>
<a href="{{ bound_field.related_url }}add/" class="add-another" id="add_{{ bound_field.element_id }}" onclick="return showAddAnotherPopup(this);"> <img src="{% admin_media_prefix %}img/admin/icon_addlink.gif" width="10" height="10" alt="Add Another"/></a>
{% endif %}{% endif %}
{% if change %}
{% if bound_field.field.primary_key %}
{{ bound_field.original_value }}
{% endif %}
{% if bound_field.raw_id_admin %}
{% if bound_field.existing_display %}&nbsp;<strong>{{ bound_field.existing_display|truncatewords:"14" }}</strong>{% endif %}
{% endif %}
{% endif %}

View File

@ -1 +1 @@
{% include "widget/foreign" %}
{% include "widget/foreign.html" %}

View File

@ -1 +1,2 @@
{% include "widget/foreign" %}
{% if add %}{% include "widget/foreign.html" %}{% endif %}
{% if change %}{% if bound_field.existing_display %}&nbsp;<strong>{{ bound_field.existing_display|truncatewords:"14" }}</strong>{% endif %}{% endif %}

View File

@ -1,14 +1,15 @@
from django.contrib.admin.views.main import MAX_SHOW_ALL_ALLOWED, DEFAULT_RESULTS_PER_PAGE, ALL_VAR
from django import template
from django.conf import settings
from django.contrib.admin.views.main import MAX_SHOW_ALL_ALLOWED, ALL_VAR
from django.contrib.admin.views.main import ORDER_VAR, ORDER_TYPE_VAR, PAGE_VAR, SEARCH_VAR
from django.contrib.admin.views.main import IS_POPUP_VAR, EMPTY_CHANGELIST_VALUE, MONTHS
from django.core import meta, template
from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from django.utils import dateformat
from django.utils.html import escape
from django.utils.text import capfirst
from django.utils.translation import get_date_formats
from django.conf.settings import ADMIN_MEDIA_PREFIX
from django.core.template import Library
from django.template import Library
register = Library()
@ -16,11 +17,11 @@ DOT = '.'
def paginator_number(cl,i):
if i == DOT:
return '... '
return '... '
elif i == cl.page_num:
return '<span class="this-page">%d</span> ' % (i+1)
return '<span class="this-page">%d</span> ' % (i+1)
else:
return '<a href="%s"%s>%d</a> ' % (cl.get_query_string({PAGE_VAR: i}), (i == cl.paginator.pages-1 and ' class="end"' or ''), i+1)
return '<a href="%s"%s>%d</a> ' % (cl.get_query_string({PAGE_VAR: i}), (i == cl.paginator.pages-1 and ' class="end"' or ''), i+1)
paginator_number = register.simple_tag(paginator_number)
def pagination(cl):
@ -64,7 +65,7 @@ def pagination(cl):
'ALL_VAR': ALL_VAR,
'1': 1,
}
pagination = register.inclusion_tag('admin/pagination')(pagination)
pagination = register.inclusion_tag('admin/pagination.html')(pagination)
def result_headers(cl):
lookup_opts = cl.lookup_opts
@ -72,22 +73,22 @@ def result_headers(cl):
for i, field_name in enumerate(lookup_opts.admin.list_display):
try:
f = lookup_opts.get_field(field_name)
except meta.FieldDoesNotExist:
except models.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__':
# back to the method name. And __str__ is a special-case.
if field_name == '__str__':
header = lookup_opts.verbose_name
else:
func = getattr(cl.mod.Klass, field_name) # Let AttributeErrors propagate.
attr = getattr(cl.model, field_name) # Let AttributeErrors propagate.
try:
header = func.short_description
header = attr.short_description
except AttributeError:
header = func.__name__.replace('_', ' ')
header = field_name.replace('_', ' ')
# Non-field list_display values don't get ordering capability.
yield {"text": header}
else:
if isinstance(f.rel, meta.ManyToOneRel) and f.null:
if isinstance(f.rel, models.ManyToOneRel) and f.null:
yield {"text": f.verbose_name}
else:
th_classes = []
@ -108,34 +109,37 @@ def items_for_result(cl, result):
row_class = ''
try:
f = cl.lookup_opts.get_field(field_name)
except meta.FieldDoesNotExist:
# For non-field list_display values, the value is a method
# name. Execute the method.
except models.FieldDoesNotExist:
# For non-field list_display values, the value is either a method
# or a property.
try:
func = getattr(result, field_name)
result_repr = str(func())
attr = getattr(result, field_name)
allow_tags = getattr(attr, 'allow_tags', False)
if callable(attr):
attr = attr()
result_repr = str(attr)
except AttributeError, ObjectDoesNotExist:
result_repr = EMPTY_CHANGELIST_VALUE
else:
# Strip HTML tags in the resulting text, except if the
# function has an "allow_tags" attribute set to True.
if not getattr(func, 'allow_tags', False):
if not allow_tags:
result_repr = escape(result_repr)
else:
field_val = getattr(result, f.attname)
if isinstance(f.rel, meta.ManyToOneRel):
if isinstance(f.rel, models.ManyToOneRel):
if field_val is not None:
result_repr = getattr(result, 'get_%s' % f.name)()
result_repr = getattr(result, f.name)
else:
result_repr = EMPTY_CHANGELIST_VALUE
# Dates and times are special: They're formatted in a certain way.
elif isinstance(f, meta.DateField) or isinstance(f, meta.TimeField):
elif isinstance(f, models.DateField) or isinstance(f, models.TimeField):
if field_val:
(date_format, datetime_format, time_format) = get_date_formats()
if isinstance(f, meta.DateTimeField):
if isinstance(f, models.DateTimeField):
result_repr = capfirst(dateformat.format(field_val, datetime_format))
elif isinstance(f, meta.TimeField):
elif isinstance(f, models.TimeField):
result_repr = capfirst(dateformat.time_format(field_val, time_format))
else:
result_repr = capfirst(dateformat.format(field_val, date_format))
@ -143,15 +147,15 @@ def items_for_result(cl, result):
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):
elif isinstance(f, models.BooleanField) or isinstance(f, models.NullBooleanField):
BOOLEAN_MAPPING = {True: 'yes', False: 'no', None: 'unknown'}
result_repr = '<img src="%simg/admin/icon-%s.gif" alt="%s" />' % (ADMIN_MEDIA_PREFIX, BOOLEAN_MAPPING[field_val], field_val)
result_repr = '<img src="%simg/admin/icon-%s.gif" alt="%s" />' % (settings.ADMIN_MEDIA_PREFIX, BOOLEAN_MAPPING[field_val], field_val)
# ImageFields are special: Use a thumbnail.
elif isinstance(f, meta.ImageField):
elif isinstance(f, models.ImageField):
from django.parts.media.photos import get_thumbnail_url
result_repr = '<img src="%s" alt="%s" title="%s" />' % (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):
elif isinstance(f, models.FloatField):
if field_val is not None:
result_repr = ('%%.%sf' % f.decimal_places) % field_val
else:
@ -163,7 +167,7 @@ def items_for_result(cl, result):
else:
result_repr = escape(str(field_val))
if result_repr == '':
result_repr = '&nbsp;'
result_repr = '&nbsp;'
if first: # First column is a special case
first = False
url = cl.url_for_result(result)
@ -181,28 +185,20 @@ def result_list(cl):
return {'cl': cl,
'result_headers': list(result_headers(cl)),
'results': list(results(cl))}
result_list = register.inclusion_tag("admin/change_list_results")(result_list)
result_list = register.inclusion_tag("admin/change_list_results.html")(result_list)
def date_hierarchy(cl):
lookup_opts, params, lookup_params, lookup_mod = \
cl.lookup_opts, cl.params, cl.lookup_params, cl.lookup_mod
if lookup_opts.admin.date_hierarchy:
field_name = lookup_opts.admin.date_hierarchy
if cl.lookup_opts.admin.date_hierarchy:
field_name = cl.lookup_opts.admin.date_hierarchy
year_field = '%s__year' % field_name
month_field = '%s__month' % field_name
day_field = '%s__day' % field_name
field_generic = '%s__' % field_name
year_lookup = params.get(year_field)
month_lookup = params.get(month_field)
day_lookup = params.get(day_field)
year_lookup = cl.params.get(year_field)
month_lookup = cl.params.get(month_field)
day_lookup = cl.params.get(day_field)
def link(d):
return cl.get_query_string(d, [field_generic])
def get_dates(unit, params):
return getattr(lookup_mod, 'get_%s_list' % field_name)(unit, **params)
link = lambda d: cl.get_query_string(d, [field_generic])
if year_lookup and month_lookup and day_lookup:
month_name = MONTHS[int(month_lookup)]
@ -215,9 +211,7 @@ def date_hierarchy(cl):
'choices': [{'title': "%s %s" % (month_name, day_lookup)}]
}
elif year_lookup and month_lookup:
date_lookup_params = lookup_params.copy()
date_lookup_params.update({year_field: year_lookup, month_field: month_lookup})
days = get_dates('day', date_lookup_params)
days = cl.query_set.filter(**{year_field: year_lookup, month_field: month_lookup}).dates(field_name, 'day')
return {
'show': True,
'back': {
@ -230,9 +224,7 @@ def date_hierarchy(cl):
} for day in days]
}
elif year_lookup:
date_lookup_params = lookup_params.copy()
date_lookup_params.update({year_field: year_lookup})
months = get_dates('month', date_lookup_params)
months = cl.query_set.filter(**{year_field: year_lookup}).dates(field_name, 'month')
return {
'show' : True,
'back': {
@ -240,20 +232,20 @@ def date_hierarchy(cl):
'title': _('All dates')
},
'choices': [{
'link': link( {year_field: year_lookup, month_field: month.month}),
'title': "%s %s" % (month.strftime('%B') , month.year)
'link': link({year_field: year_lookup, month_field: month.month}),
'title': "%s %s" % (month.strftime('%B'), month.year)
} for month in months]
}
else:
years = get_dates('year', lookup_params)
years = cl.query_set.dates(field_name, 'year')
return {
'show': True,
'choices': [{
'link': link({year_field: year.year}),
'title': year.year
} for year in years ]
} for year in years]
}
date_hierarchy = register.inclusion_tag('admin/date_hierarchy')(date_hierarchy)
date_hierarchy = register.inclusion_tag('admin/date_hierarchy.html')(date_hierarchy)
def search_form(cl):
return {
@ -261,12 +253,12 @@ def search_form(cl):
'show_result_count': cl.result_count != cl.full_result_count and not cl.opts.one_to_one_field,
'search_var': SEARCH_VAR
}
search_form = register.inclusion_tag('admin/search_form')(search_form)
search_form = register.inclusion_tag('admin/search_form.html')(search_form)
def filter(cl, spec):
return {'title': spec.title(), 'choices' : list(spec.choices(cl))}
filter = register.inclusion_tag('admin/filter')(filter)
filter = register.inclusion_tag('admin/filter.html')(filter)
def filters(cl):
return {'cl': cl}
filters = register.inclusion_tag('admin/filters')(filters)
filters = register.inclusion_tag('admin/filters.html')(filters)

View File

@ -1,11 +1,13 @@
from django.core import template, template_loader, meta
from django import template
from django.contrib.admin.views.main import AdminBoundField
from django.template import loader
from django.utils.html import escape
from django.utils.text import capfirst
from django.utils.functional import curry
from django.contrib.admin.views.main import AdminBoundField
from django.core.meta.fields import BoundField, Field
from django.core.meta import BoundRelatedObject, TABULAR, STACKED
from django.conf.settings import ADMIN_MEDIA_PREFIX
from django.db import models
from django.db.models.fields import Field
from django.db.models.related import BoundRelatedObject
from django.conf import settings
import re
register = template.Library()
@ -16,30 +18,28 @@ def class_name_to_underscored(name):
return '_'.join([s.lower() for s in word_re.findall(name)[:-1]])
def include_admin_script(script_path):
return '<script type="text/javascript" src="%s%s"></script>' % (ADMIN_MEDIA_PREFIX, script_path)
return '<script type="text/javascript" src="%s%s"></script>' % (settings.ADMIN_MEDIA_PREFIX, script_path)
include_admin_script = register.simple_tag(include_admin_script)
def submit_row(context, bound_manipulator):
def submit_row(context):
opts = context['opts']
change = context['change']
add = context['add']
show_delete = context['show_delete']
has_delete_permission = context['has_delete_permission']
is_popup = context['is_popup']
return {
'onclick_attrib': (bound_manipulator.ordered_objects and change
'onclick_attrib': (opts.get_ordered_objects() and change
and 'onclick="submitOrderForm();"' or ''),
'show_delete_link': (not is_popup and has_delete_permission
and (change or show_delete)),
'show_save_as_new': not is_popup and change and bound_manipulator.save_as,
'show_save_and_add_another': not is_popup and (not bound_manipulator.save_as or add),
'show_save_and_continue': not is_popup,
'show_delete_link': (not is_popup and context['has_delete_permission']
and (change or context['show_delete'])),
'show_save_as_new': not is_popup and change and opts.admin.save_as,
'show_save_and_add_another': not is_popup and (not opts.admin.save_as or context['add']),
'show_save_and_continue': not is_popup and context['has_change_permission'],
'show_save': True
}
submit_row = register.inclusion_tag('admin/submit_line', takes_context=True)(submit_row)
submit_row = register.inclusion_tag('admin/submit_line.html', takes_context=True)(submit_row)
def field_label(bound_field):
class_names = []
if isinstance(bound_field.field, meta.BooleanField):
if isinstance(bound_field.field, models.BooleanField):
class_names.append("vCheckboxLabel")
colon = ""
else:
@ -64,16 +64,15 @@ class FieldWidgetNode(template.Node):
if not cls.nodelists.has_key(klass):
try:
field_class_name = klass.__name__
template_name = "widget/%s" % \
class_name_to_underscored(field_class_name)
nodelist = template_loader.get_template(template_name).nodelist
template_name = "widget/%s.html" % class_name_to_underscored(field_class_name)
nodelist = loader.get_template(template_name).nodelist
except template.TemplateDoesNotExist:
super_klass = bool(klass.__bases__) and klass.__bases__[0] or None
if super_klass and super_klass != Field:
nodelist = cls.get_nodelist(super_klass)
else:
if not cls.default:
cls.default = template_loader.get_template("widget/default").nodelist
cls.default = loader.get_template("widget/default.html").nodelist
nodelist = cls.default
cls.nodelists[klass] = nodelist
@ -97,21 +96,22 @@ class FieldWrapper(object):
self.field = field
def needs_header(self):
return not isinstance(self.field, meta.AutoField)
return not isinstance(self.field, models.AutoField)
def header_class_attribute(self):
return self.field.blank and ' class="optional"' or ''
def use_raw_id_admin(self):
return isinstance(self.field.rel, (meta.ManyToOneRel, meta.ManyToManyRel)) \
return isinstance(self.field.rel, (models.ManyToOneRel, models.ManyToManyRel)) \
and self.field.rel.raw_id_admin
class FormFieldCollectionWrapper(object):
def __init__(self, field_mapping, fields):
def __init__(self, field_mapping, fields, index):
self.field_mapping = field_mapping
self.fields = fields
self.bound_fields = [AdminBoundField(field, self.field_mapping, field_mapping['original'])
for field in self.fields]
self.index = index
class TabularBoundRelatedObject(BoundRelatedObject):
def __init__(self, related_object, field_mapping, original):
@ -120,29 +120,25 @@ class TabularBoundRelatedObject(BoundRelatedObject):
fields = self.relation.editable_fields()
self.form_field_collection_wrappers = [FormFieldCollectionWrapper(field_mapping, fields)
for field_mapping in self.field_mappings]
self.form_field_collection_wrappers = [FormFieldCollectionWrapper(field_mapping, fields, i)
for (i,field_mapping) in self.field_mappings.items() ]
self.original_row_needed = max([fw.use_raw_id_admin() for fw in self.field_wrapper_list])
self.show_url = original and hasattr(self.relation.opts, 'get_absolute_url')
def template_name(self):
return "admin/edit_inline_tabular"
return "admin/edit_inline_tabular.html"
class StackedBoundRelatedObject(BoundRelatedObject):
def __init__(self, related_object, field_mapping, original):
super(StackedBoundRelatedObject, self).__init__(related_object, field_mapping, original)
fields = self.relation.editable_fields()
self.form_field_collection_wrappers = [FormFieldCollectionWrapper(field_mapping ,fields)
for field_mapping in self.field_mappings]
self.field_mappings.fill()
self.form_field_collection_wrappers = [FormFieldCollectionWrapper(field_mapping ,fields, i)
for (i,field_mapping) in self.field_mappings.items()]
self.show_url = original and hasattr(self.relation.opts, 'get_absolute_url')
def template_name(self):
return "admin/edit_inline_stacked"
bound_related_object_overrides = {
TABULAR: TabularBoundRelatedObject,
STACKED: StackedBoundRelatedObject,
}
return "admin/edit_inline_stacked.html"
class EditInlineNode(template.Node):
def __init__(self, rel_var):
@ -150,21 +146,16 @@ class EditInlineNode(template.Node):
def render(self, context):
relation = template.resolve_variable(self.rel_var, context)
context.push()
klass = relation.field.rel.edit_inline
bound_related_object_class = bound_related_object_overrides.get(klass, klass)
if relation.field.rel.edit_inline == models.TABULAR:
bound_related_object_class = TabularBoundRelatedObject
else:
bound_related_object_class = StackedBoundRelatedObject
original = context.get('original', None)
bound_related_object = relation.bind(context['form'], original, bound_related_object_class)
context['bound_related_object'] = bound_related_object
t = template_loader.get_template(bound_related_object.template_name())
t = loader.get_template(bound_related_object.template_name())
output = t.render(context)
context.pop()
return output
@ -191,30 +182,30 @@ auto_populated_field_script = register.simple_tag(auto_populated_field_script)
def filter_interface_script_maybe(bound_field):
f = bound_field.field
if f.rel and isinstance(f.rel, meta.ManyToManyRel) and f.rel.filter_interface:
return '<script type="text/javascript">addEvent(window, "load", function(e) {' \
if f.rel and isinstance(f.rel, models.ManyToManyRel) and f.rel.filter_interface:
return '<script type="text/javascript">addEvent(window, "load", function(e) {' \
' SelectFilter.init("id_%s", "%s", %s, "%s"); });</script>\n' % (
f.name, f.verbose_name, f.rel.filter_interface-1, ADMIN_MEDIA_PREFIX)
f.name, f.verbose_name, f.rel.filter_interface-1, settings.ADMIN_MEDIA_PREFIX)
else:
return ''
filter_interface_script_maybe = register.simple_tag(filter_interface_script_maybe)
def do_one_arg_tag(node_factory, parser,token):
tokens = token.contents.split()
if len(tokens) != 2:
raise template.TemplateSyntaxError("%s takes 1 argument" % tokens[0])
return node_factory(tokens[1])
def field_widget(parser, token):
bits = token.contents.split()
if len(bits) != 2:
raise template.TemplateSyntaxError, "%s takes 1 argument" % bits[0]
return FieldWidgetNode(bits[1])
field_widget = register.tag(field_widget)
def register_one_arg_tag(node):
tag_name = class_name_to_underscored(node.__name__)
parse_func = curry(do_one_arg_tag, node)
register.tag(tag_name, parse_func)
register_one_arg_tag(FieldWidgetNode)
register_one_arg_tag(EditInlineNode)
def edit_inline(parser, token):
bits = token.contents.split()
if len(bits) != 2:
raise template.TemplateSyntaxError, "%s takes 1 argument" % bits[0]
return EditInlineNode(bits[1])
edit_inline = register.tag(edit_inline)
def admin_field_line(context, argument_val):
if (isinstance(argument_val, BoundField)):
if isinstance(argument_val, AdminBoundField):
bound_fields = [argument_val]
else:
bound_fields = [bf for bf in argument_val]
@ -229,7 +220,7 @@ def admin_field_line(context, argument_val):
break
# Assumes BooleanFields won't be stacked next to each other!
if isinstance(bound_fields[0].field, meta.BooleanField):
if isinstance(bound_fields[0].field, models.BooleanField):
class_names.append('checkbox-row')
return {
@ -238,8 +229,4 @@ def admin_field_line(context, argument_val):
'bound_fields': bound_fields,
'class_names': " ".join(class_names),
}
admin_field_line = register.inclusion_tag('admin/field_line', takes_context=True)(admin_field_line)
def object_pk(bound_manip, ordered_obj):
return bound_manip.get_ordered_object_pk(ordered_obj)
object_pk = register.simple_tag(object_pk)
admin_field_line = register.inclusion_tag('admin/field_line.html', takes_context=True)(admin_field_line)

View File

@ -1,4 +1,5 @@
from django.core import template
from django import template
from django.db.models import get_models
register = template.Library()
@ -7,20 +8,24 @@ class AdminApplistNode(template.Node):
self.varname = varname
def render(self, context):
from django.core import meta
from django.db import models
from django.utils.text import capfirst
app_list = []
user = context['user']
for app in meta.get_installed_model_modules():
app_label = app.__name__[app.__name__.rindex('.')+1:]
for app in models.get_apps():
# Determine the app_label.
app_models = get_models(app)
if not app_models:
continue
app_label = app_models[0]._meta.app_label
has_module_perms = user.has_module_perms(app_label)
if has_module_perms:
model_list = []
for m in app._MODELS:
for m in app_models:
if m._meta.admin:
module_name = m._meta.module_name
perms = {
'add': user.has_perm("%s.%s" % (app_label, m._meta.get_add_permission())),
'change': user.has_perm("%s.%s" % (app_label, m._meta.get_change_permission())),
@ -32,7 +37,7 @@ class AdminApplistNode(template.Node):
if True in perms.values():
model_list.append({
'name': capfirst(m._meta.verbose_name_plural),
'admin_url': '%s/%s/' % (app_label, m._meta.module_name),
'admin_url': '%s/%s/' % (app_label, m.__name__.lower()),
'perms': perms,
})

View File

@ -1,10 +1,11 @@
from django.core.template import Library
from django.template import Library
register = Library()
def admin_media_prefix():
try:
from django.conf.settings import ADMIN_MEDIA_PREFIX
from django.conf import settings
except ImportError:
return ''
return ADMIN_MEDIA_PREFIX
return settings.ADMIN_MEDIA_PREFIX
admin_media_prefix = register.simple_tag(admin_media_prefix)

View File

@ -1,5 +1,5 @@
from django.models.admin import log
from django.core import template
from django import template
from django.contrib.admin.models import LogEntry
register = template.Library()
@ -13,7 +13,7 @@ class AdminLogNode(template.Node):
def render(self, context):
if self.user is not None and not self.user.isdigit():
self.user = context[self.user].id
context[self.varname] = log.get_list(user__id__exact=self.user, limit=self.limit, select_related=True)
context[self.varname] = LogEntry.objects.filter(user__id__exact=self.user).select_related()[:self.limit]
return ''
class DoGetAdminLog:

View File

@ -0,0 +1,31 @@
from django.conf.urls.defaults import *
urlpatterns = patterns('',
('^$', 'django.contrib.admin.views.main.index'),
('^r/(\d+)/(\d+)/$', 'django.views.defaults.shortcut'),
('^jsi18n/$', 'django.views.i18n.javascript_catalog', {'packages': 'django.conf'}),
('^logout/$', 'django.contrib.auth.views.logout'),
('^password_change/$', 'django.contrib.auth.views.password_change'),
('^password_change/done/$', 'django.contrib.auth.views.password_change_done'),
('^template_validator/$', 'django.contrib.admin.views.template.template_validator'),
# Documentation
('^doc/$', 'django.contrib.admin.views.doc.doc_index'),
('^doc/bookmarklets/$', 'django.contrib.admin.views.doc.bookmarklets'),
('^doc/tags/$', 'django.contrib.admin.views.doc.template_tag_index'),
('^doc/filters/$', 'django.contrib.admin.views.doc.template_filter_index'),
('^doc/views/$', 'django.contrib.admin.views.doc.view_index'),
('^doc/views/jump/$', 'django.contrib.admin.views.doc.jump_to_view'),
('^doc/views/(?P<view>[^/]+)/$', 'django.contrib.admin.views.doc.view_detail'),
('^doc/models/$', 'django.contrib.admin.views.doc.model_index'),
('^doc/models/(?P<app_label>[^\.]+)\.(?P<model_name>[^/]+)/$', 'django.contrib.admin.views.doc.model_detail'),
# ('^doc/templates/$', 'django.views.admin.doc.template_index'),
('^doc/templates/(?P<template>.*)/$', 'django.contrib.admin.views.doc.template_detail'),
# Add/change/delete/history
('^([^/]+)/([^/]+)/$', 'django.contrib.admin.views.main.change_list'),
('^([^/]+)/([^/]+)/add/$', 'django.contrib.admin.views.main.add_stage'),
('^([^/]+)/([^/]+)/(.+)/history/$', 'django.contrib.admin.views.main.history'),
('^([^/]+)/([^/]+)/(.+)/delete/$', 'django.contrib.admin.views.main.delete_stage'),
('^([^/]+)/([^/]+)/(.+)/$', 'django.contrib.admin.views.main.change_stage'),
)

View File

@ -1,58 +0,0 @@
from django.conf.urls.defaults import *
from django.conf.settings import INSTALLED_APPS
urlpatterns = (
('^$', 'django.contrib.admin.views.main.index'),
('^jsi18n/$', 'django.views.i18n.javascript_catalog', {'packages': 'django.conf'}),
('^logout/$', 'django.views.auth.login.logout'),
('^password_change/$', 'django.views.registration.passwords.password_change'),
('^password_change/done/$', 'django.views.registration.passwords.password_change_done'),
('^template_validator/$', 'django.contrib.admin.views.template.template_validator'),
# Documentation
('^doc/$', 'django.contrib.admin.views.doc.doc_index'),
('^doc/bookmarklets/$', 'django.contrib.admin.views.doc.bookmarklets'),
('^doc/tags/$', 'django.contrib.admin.views.doc.template_tag_index'),
('^doc/filters/$', 'django.contrib.admin.views.doc.template_filter_index'),
('^doc/views/$', 'django.contrib.admin.views.doc.view_index'),
('^doc/views/jump/$', 'django.contrib.admin.views.doc.jump_to_view'),
('^doc/views/(?P<view>[^/]+)/$', 'django.contrib.admin.views.doc.view_detail'),
('^doc/models/$', 'django.contrib.admin.views.doc.model_index'),
('^doc/models/(?P<model>[^/]+)/$', 'django.contrib.admin.views.doc.model_detail'),
# ('^doc/templates/$', 'django.views.admin.doc.template_index'),
('^doc/templates/(?P<template>.*)/$', 'django.contrib.admin.views.doc.template_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.contrib.admin.views.main.change_list'),
('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/add/$', 'django.contrib.admin.views.main.add_stage'),
('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/(?P<object_id>.+)/history/$', 'django.contrib.admin.views.main.history'),
('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/(?P<object_id>.+)/delete/$', 'django.contrib.admin.views.main.delete_stage'),
('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/(?P<object_id>.+)/$', 'django.contrib.admin.views.main.change_stage'),
)
urlpatterns = patterns('', *urlpatterns)

View File

@ -82,18 +82,18 @@ ROLES = {
def create_reference_role(rolename, urlbase):
def _role(name, rawtext, text, lineno, inliner, options={}, content=[]):
node = docutils.nodes.reference(rawtext, text, refuri=(urlbase % (inliner.document.settings.link_base, text)), **options)
node = docutils.nodes.reference(rawtext, text, refuri=(urlbase % (inliner.document.settings.link_base, text.lower())), **options)
return [node], []
docutils.parsers.rst.roles.register_canonical_role(rolename, _role)
def default_reference_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
context = inliner.document.settings.default_reference_context
node = docutils.nodes.reference(rawtext, text, refuri=(ROLES[context] % (inliner.document.settings.link_base, text)), **options)
node = docutils.nodes.reference(rawtext, text, refuri=(ROLES[context] % (inliner.document.settings.link_base, text.lower())), **options)
return [node], []
if docutils_is_available:
docutils.parsers.rst.roles.register_canonical_role('cmsreference', default_reference_role)
docutils.parsers.rst.roles.DEFAULT_INTERPRETED_ROLE = 'cmsreference'
for (name, urlbase) in ROLES.items():
for name, urlbase in ROLES.items():
create_reference_role(name, urlbase)

View File

@ -1,7 +1,7 @@
from django.core.extensions import DjangoContext, render_to_response
from django.conf.settings import SECRET_KEY
from django.models.auth import users
from django.utils import httpwrappers
from django import http, template
from django.conf import settings
from django.contrib.auth.models import User, SESSION_KEY
from django.shortcuts import render_to_response
from django.utils.translation import gettext_lazy
import base64, datetime, md5
import cPickle as pickle
@ -19,22 +19,22 @@ def _display_login_form(request, error_message=''):
post_data = _encode_post_data(request.POST)
else:
post_data = _encode_post_data({})
return render_to_response('admin/login', {
return render_to_response('admin/login.html', {
'title': _('Log in'),
'app_path': request.path,
'post_data': post_data,
'error_message': error_message
}, context_instance=DjangoContext(request))
}, context_instance=template.RequestContext(request))
def _encode_post_data(post_data):
pickled = pickle.dumps(post_data)
pickled_md5 = md5.new(pickled + SECRET_KEY).hexdigest()
pickled_md5 = md5.new(pickled + settings.SECRET_KEY).hexdigest()
return base64.encodestring(pickled + pickled_md5)
def _decode_post_data(encoded_data):
encoded_data = base64.decodestring(encoded_data)
pickled, tamper_check = encoded_data[:-32], encoded_data[-32:]
if md5.new(pickled + SECRET_KEY).hexdigest() != tamper_check:
if md5.new(pickled + settings.SECRET_KEY).hexdigest() != tamper_check:
from django.core.exceptions import SuspiciousOperation
raise SuspiciousOperation, "User may have tampered with session cookie."
return pickle.loads(pickled)
@ -53,7 +53,7 @@ def staff_member_required(view_func):
request.POST = _decode_post_data(request.POST['post_data'])
return view_func(request, *args, **kwargs)
assert hasattr(request, 'session'), "The Django admin requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.middleware.sessions.SessionMiddleware'."
assert hasattr(request, 'session'), "The Django admin requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'."
# If this isn't already the login page, display it.
if not request.POST.has_key(LOGIN_FORM_KEY):
@ -71,14 +71,14 @@ def staff_member_required(view_func):
# Check the password.
username = request.POST.get('username', '')
try:
user = users.get_object(username__exact=username, is_staff__exact=True)
except users.UserDoesNotExist:
user = User.objects.get(username=username, is_staff=True)
except User.DoesNotExist:
message = ERROR_MESSAGE
if '@' in username:
# Mistakenly entered e-mail address instead of username? Look it up.
try:
user = users.get_object(email__exact=username)
except users.UserDoesNotExist:
user = User.objects.get(email=username)
except User.DoesNotExist:
message = _("Usernames cannot contain the '@' character.")
else:
message = _("Your e-mail address is not your username. Try '%s' instead.") % user.username
@ -87,7 +87,7 @@ def staff_member_required(view_func):
# The user data is correct; log in the user in and continue.
else:
if user.check_password(request.POST.get('password', '')):
request.session[users.SESSION_KEY] = user.id
request.session[SESSION_KEY] = user.id
user.last_login = datetime.datetime.now()
user.save()
if request.POST.has_key('post_data'):
@ -99,7 +99,7 @@ def staff_member_required(view_func):
return view_func(request, *args, **kwargs)
else:
request.session.delete_test_cookie()
return httpwrappers.HttpResponseRedirect(request.path)
return http.HttpResponseRedirect(request.path)
else:
return _display_login_form(request, ERROR_MESSAGE)

View File

@ -1,12 +1,14 @@
from django.core import meta
from django import templatetags
from django import template, templatetags
from django.template import RequestContext
from django.conf import settings
from django.contrib.admin.views.decorators import staff_member_required
from django.models.core import sites
from django.core.extensions import DjangoContext, render_to_response
from django.core.exceptions import Http404, ViewDoesNotExist
from django.core import template, urlresolvers
from django.db import models
from django.shortcuts import render_to_response
from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
from django.http import Http404, get_host
from django.core import urlresolvers
from django.contrib.admin import utils
from django.contrib.sites.models import Site
import inspect, os, re
# Exclude methods starting with these strings from documentation
@ -15,15 +17,15 @@ MODEL_METHODS_EXCLUDE = ('_', 'add_', 'delete', 'save', 'set_')
def doc_index(request):
if not utils.docutils_is_available:
return missing_docutils_page(request)
return render_to_response('admin_doc/index', context_instance=DjangoContext(request))
return render_to_response('admin_doc/index.html', context_instance=RequestContext(request))
doc_index = staff_member_required(doc_index)
def bookmarklets(request):
# Hack! This couples this view to the URL it lives at.
admin_root = request.path[:-len('doc/bookmarklets/')]
return render_to_response('admin_doc/bookmarklets', {
'admin_url': "%s://%s%s" % (os.environ.get('HTTPS') == 'on' and 'https' or 'http', request.META['HTTP_HOST'], admin_root),
}, context_instance=DjangoContext(request))
return render_to_response('admin_doc/bookmarklets.html', {
'admin_url': "%s://%s%s" % (os.environ.get('HTTPS') == 'on' and 'https' or 'http', get_host(request), admin_root),
}, context_instance=RequestContext(request))
bookmarklets = staff_member_required(bookmarklets)
def template_tag_index(request):
@ -54,7 +56,7 @@ def template_tag_index(request):
'library': tag_library,
})
return render_to_response('admin_doc/template_tag_index', {'tags': tags}, context_instance=DjangoContext(request))
return render_to_response('admin_doc/template_tag_index.html', {'tags': tags}, context_instance=RequestContext(request))
template_tag_index = staff_member_required(template_tag_index)
def template_filter_index(request):
@ -84,16 +86,20 @@ def template_filter_index(request):
'meta': metadata,
'library': tag_library,
})
return render_to_response('admin_doc/template_filter_index', {'filters': filters}, context_instance=DjangoContext(request))
return render_to_response('admin_doc/template_filter_index.html', {'filters': filters}, context_instance=RequestContext(request))
template_filter_index = staff_member_required(template_filter_index)
def view_index(request):
if not utils.docutils_is_available:
return missing_docutils_page(request)
if settings.ADMIN_FOR:
settings_modules = [__import__(m, '', '', ['']) for m in settings.ADMIN_FOR]
else:
settings_modules = [settings]
views = []
for site_settings_module in settings.ADMIN_FOR:
settings_mod = __import__(site_settings_module, '', '', [''])
for settings_mod in settings_modules:
urlconf = __import__(settings_mod.ROOT_URLCONF, '', '', [''])
view_functions = extract_views_from_urlpatterns(urlconf.urlpatterns)
for (func, regex) in view_functions:
@ -101,10 +107,10 @@ def view_index(request):
'name': func.__name__,
'module': func.__module__,
'site_id': settings_mod.SITE_ID,
'site': sites.get_object(pk=settings_mod.SITE_ID),
'site': Site.objects.get(pk=settings_mod.SITE_ID),
'url': simplify_regex(regex),
})
return render_to_response('admin_doc/view_index', {'views': views}, context_instance=DjangoContext(request))
return render_to_response('admin_doc/view_index.html', {'views': views}, context_instance=RequestContext(request))
view_index = staff_member_required(view_index)
def view_detail(request, view):
@ -123,51 +129,63 @@ def view_detail(request, view):
body = utils.parse_rst(body, 'view', 'view:' + view)
for key in metadata:
metadata[key] = utils.parse_rst(metadata[key], 'model', 'view:' + view)
return render_to_response('admin_doc/view_detail', {
return render_to_response('admin_doc/view_detail.html', {
'name': view,
'summary': title,
'body': body,
'meta': metadata,
}, context_instance=DjangoContext(request))
}, context_instance=RequestContext(request))
view_detail = staff_member_required(view_detail)
def model_index(request):
if not utils.docutils_is_available:
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,
})
return render_to_response('admin_doc/model_index', {'models': models}, context_instance=DjangoContext(request))
m_list = [m._meta for m in models.get_models()]
return render_to_response('admin_doc/model_index.html', {'models': m_list}, context_instance=RequestContext(request))
model_index = staff_member_required(model_index)
def model_detail(request, model):
def model_detail(request, app_label, model_name):
if not utils.docutils_is_available:
return missing_docutils_page(request)
# Get the model class.
try:
model = meta.get_app(model)
except ImportError:
raise Http404
opts = model.Klass._meta
app_mod = models.get_app(app_label)
except ImproperlyConfigured:
raise Http404, "App %r not found" % app_label
model = None
for m in models.get_models(app_mod):
if m._meta.object_name.lower() == model_name:
model = m
break
if model is None:
raise Http404, "Model %r not found in app %r" % (model_name, app_label)
# Gather fields/field descriptions
opts = model._meta
# Gather fields/field descriptions.
fields = []
for field in opts.fields:
# ForeignKey is a special case since the field will actually be a
# descriptor that returns the other object
if isinstance(field, models.ForeignKey):
data_type = related_object_name = field.rel.to.__name__
app_label = field.rel.to._meta.app_label
verbose = utils.parse_rst(("the related `%s.%s` object" % (app_label, data_type)), 'model', 'model:' + data_type)
else:
data_type = get_readable_field_data_type(field)
verbose = field.verbose_name
fields.append({
'name': field.name,
'data_type': get_readable_field_data_type(field),
'verbose': field.verbose_name,
'data_type': data_type,
'verbose': verbose,
'help': field.help_text,
})
for func_name, func in model.Klass.__dict__.items():
if callable(func) and len(inspect.getargspec(func)[0]) == 0:
# Gather model methods.
for func_name, func in model.__dict__.items():
if (inspect.isfunction(func) and len(inspect.getargspec(func)[0]) == 1):
try:
for exclude in MODEL_METHODS_EXCLUDE:
if func_name.startswith(exclude):
@ -182,12 +200,26 @@ def model_detail(request, model):
'data_type': get_return_data_type(func_name),
'verbose': verbose,
})
return render_to_response('admin_doc/model_detail', {
'name': '%s.%s' % (opts.app_label, opts.module_name),
'summary': "Fields on %s objects" % opts.verbose_name,
# Gather related objects
for rel in opts.get_all_related_objects():
verbose = "related `%s.%s` objects" % (rel.opts.app_label, rel.opts.object_name)
accessor = rel.get_accessor_name()
fields.append({
'name' : "%s.all" % accessor,
'verbose' : utils.parse_rst("all " + verbose , 'model', 'model:' + opts.module_name),
})
fields.append({
'name' : "%s.count" % accessor,
'verbose' : utils.parse_rst("number of " + verbose , 'model', 'model:' + opts.module_name),
})
return render_to_response('admin_doc/model_detail.html', {
'name': '%s.%s' % (opts.app_label, opts.object_name),
'summary': "Fields on %s objects" % opts.object_name,
'description': model.__doc__,
'fields': fields,
}, context_instance=DjangoContext(request))
}, context_instance=RequestContext(request))
model_detail = staff_member_required(model_detail)
def template_detail(request, template):
@ -201,13 +233,13 @@ def template_detail(request, template):
'exists': os.path.exists(template_file),
'contents': lambda: os.path.exists(template_file) and open(template_file).read() or '',
'site_id': settings_mod.SITE_ID,
'site': sites.get_object(pk=settings_mod.SITE_ID),
'site': Site.objects.get(pk=settings_mod.SITE_ID),
'order': list(settings_mod.TEMPLATE_DIRS).index(dir),
})
return render_to_response('admin_doc/template_detail', {
return render_to_response('admin_doc/template_detail.html', {
'name': template,
'templates': templates,
}, context_instance=DjangoContext(request))
}, context_instance=RequestContext(request))
template_detail = staff_member_required(template_detail)
####################
@ -216,7 +248,7 @@ template_detail = staff_member_required(template_detail)
def missing_docutils_page(request):
"""Display an error message for people without docutils"""
return render_to_response('admin_doc/missing_docutils')
return render_to_response('admin_doc/missing_docutils.html')
def load_all_installed_template_libraries():
# Load/register all template tag libraries from installed apps.
@ -271,9 +303,6 @@ DATA_TYPE_MAPPING = {
}
def get_readable_field_data_type(field):
# ForeignKey is a special case. Use the field type of the relation.
if field.get_internal_type() == 'ForeignKey':
field = field.rel.get_related_field()
return DATA_TYPE_MAPPING[field.get_internal_type()] % field.__dict__
def extract_views_from_urlpatterns(urlpatterns, base=''):
@ -295,15 +324,23 @@ def extract_views_from_urlpatterns(urlpatterns, base=''):
raise TypeError, "%s does not appear to be a urlpattern object" % p
return views
# Clean up urlpattern regexes into something somewhat readable by Mere Humans:
# turns something like "^(?P<sport_slug>\w+)/athletes/(?P<athlete_slug>\w+)/$"
# into "<sport_slug>/athletes/<athlete_slug>/"
named_group_matcher = re.compile(r'\(\?P(<\w+>).+?\)')
non_named_group_matcher = re.compile(r'\(.*?\)')
def simplify_regex(pattern):
"""
Clean up urlpattern regexes into something somewhat readable by Mere Humans:
turns something like "^(?P<sport_slug>\w+)/athletes/(?P<athlete_slug>\w+)/$"
into "<sport_slug>/athletes/<athlete_slug>/"
"""
# handle named groups first
pattern = named_group_matcher.sub(lambda m: m.group(1), pattern)
pattern = pattern.replace('^', '').replace('$', '').replace('?', '').replace('//', '/')
# handle non-named groups
pattern = non_named_group_matcher.sub("<var>", pattern)
# clean up any outstanding regex-y characters.
pattern = pattern.replace('^', '').replace('$', '').replace('?', '').replace('//', '/').replace('\\', '')
if not pattern.startswith('/'):
pattern = '/' + pattern
return pattern

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,9 @@
from django.contrib.admin.views.decorators import staff_member_required
from django.core import formfields, validators
from django.core import template
from django.core.template import loader
from django.core.extensions import DjangoContext, render_to_response
from django.models.core import sites
from django.core import validators
from django import template, forms
from django.template import loader
from django.shortcuts import render_to_response
from django.contrib.sites.models import Site
from django.conf import settings
def template_validator(request):
@ -23,19 +23,19 @@ def template_validator(request):
errors = manipulator.get_validation_errors(new_data)
if not errors:
request.user.add_message('The template is valid.')
return render_to_response('admin/template_validator', {
return render_to_response('admin/template_validator.html', {
'title': 'Template validator',
'form': formfields.FormWrapper(manipulator, new_data, errors),
}, context_instance=DjangoContext(request))
'form': forms.FormWrapper(manipulator, new_data, errors),
}, context_instance=template.RequestContext(request))
template_validator = staff_member_required(template_validator)
class TemplateValidator(formfields.Manipulator):
class TemplateValidator(forms.Manipulator):
def __init__(self, settings_modules):
self.settings_modules = settings_modules
site_list = sites.get_in_bulk(settings_modules.keys()).values()
site_list = Site.objects.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]),
forms.SelectField('site', is_required=True, choices=[(s.id, s.name) for s in site_list]),
forms.LargeTextField('template', is_required=True, rows=25, validator_list=[self.isValidTemplate]),
)
def isValidTemplate(self, field_data, all_data):

View File

@ -0,0 +1,2 @@
LOGIN_URL = '/accounts/login/'
REDIRECT_FIELD_NAME = 'next'

View File

@ -0,0 +1,84 @@
"""
Helper function for creating superusers in the authentication system.
"""
from django.core import validators
from django.contrib.auth.models import User
import getpass
import os
import sys
def createsuperuser(username=None, email=None, password=None):
"""
Helper function for creating a superuser from the command line. All
arguments are optional and will be prompted-for if invalid or not given.
"""
try:
import pwd
except ImportError:
default_username = ''
else:
# Determine the current system user's username, to use as a default.
default_username = pwd.getpwuid(os.getuid())[0].replace(' ', '').lower()
# Determine whether the default username is taken, so we don't display
# it as an option.
if default_username:
try:
User.objects.get(username=default_username)
except User.DoesNotExist:
pass
else:
default_username = ''
try:
while 1:
if not username:
input_msg = 'Username'
if default_username:
input_msg += ' (Leave blank to use %r)' % default_username
username = raw_input(input_msg + ': ')
if default_username and username == '':
username = default_username
if not username.isalnum():
sys.stderr.write("Error: That username is invalid. Use only letters, digits and underscores.\n")
username = None
try:
User.objects.get(username=username)
except User.DoesNotExist:
break
else:
sys.stderr.write("Error: That username is already taken.\n")
username = None
while 1:
if not email:
email = raw_input('E-mail address: ')
try:
validators.isValidEmail(email, None)
except validators.ValidationError:
sys.stderr.write("Error: That e-mail address is invalid.\n")
email = None
else:
break
while 1:
if not password:
password = getpass.getpass()
password2 = getpass.getpass('Password (again): ')
if password != password2:
sys.stderr.write("Error: Your passwords didn't match.\n")
password = None
continue
if password.strip() == '':
sys.stderr.write("Error: Blank passwords aren't allowed.\n")
password = None
continue
break
except KeyboardInterrupt:
sys.stderr.write("\nOperation cancelled.\n")
sys.exit(1)
u = User.objects.create_user(username, email, password)
u.is_staff = True
u.is_active = True
u.is_superuser = True
u.save()
print "Superuser created successfully."

View File

@ -1,6 +1,7 @@
from django.views.auth import login
from django.contrib.auth import LOGIN_URL, REDIRECT_FIELD_NAME
from django.http import HttpResponseRedirect
def user_passes_test(test_func, login_url=login.LOGIN_URL):
def user_passes_test(test_func, login_url=LOGIN_URL):
"""
Decorator for views that checks that the user passes the given test,
redirecting to the log-in page if necessary. The test should be a callable
@ -10,7 +11,8 @@ def user_passes_test(test_func, login_url=login.LOGIN_URL):
def _checklogin(request, *args, **kwargs):
if test_func(request.user):
return view_func(request, *args, **kwargs)
return login.redirect_to_login(request.path, login_url)
return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, request.path))
return _checklogin
return _dec

View File

@ -0,0 +1,108 @@
from django.contrib.auth.models import User
from django.contrib.sites.models import Site
from django.template import Context, loader
from django.core import validators
from django import forms
class AuthenticationForm(forms.Manipulator):
"""
Base class for authenticating users. Extend this to get a form that accepts
username/password logins.
"""
def __init__(self, request=None):
"""
If request is passed in, the manipulator will validate that cookies are
enabled. Note that the request (a HttpRequest object) must have set a
cookie with the key TEST_COOKIE_NAME and value TEST_COOKIE_VALUE before
running this validator.
"""
self.request = request
self.fields = [
forms.TextField(field_name="username", length=15, maxlength=30, is_required=True,
validator_list=[self.isValidUser, self.hasCookiesEnabled]),
forms.PasswordField(field_name="password", length=15, maxlength=30, is_required=True,
validator_list=[self.isValidPasswordForUser]),
]
self.user_cache = None
def hasCookiesEnabled(self, field_data, all_data):
if self.request and not self.request.session.test_cookie_worked():
raise validators.ValidationError, _("Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in.")
def isValidUser(self, field_data, all_data):
try:
self.user_cache = User.objects.get(username=field_data)
except User.DoesNotExist:
raise validators.ValidationError, _("Please enter a correct username and password. Note that both fields are case-sensitive.")
def isValidPasswordForUser(self, field_data, all_data):
if self.user_cache is not None and not self.user_cache.check_password(field_data):
self.user_cache = None
raise validators.ValidationError, _("Please enter a correct username and password. Note that both fields are case-sensitive.")
def get_user_id(self):
if self.user_cache:
return self.user_cache.id
return None
def get_user(self):
return self.user_cache
class PasswordResetForm(forms.Manipulator):
"A form that lets a user request a password reset"
def __init__(self):
self.fields = (
forms.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 = User.objects.get(email__iexact=new_data)
except User.DoesNotExist:
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
new_pass = User.objects.make_random_password()
self.user_cache.set_password(new_pass)
self.user_cache.save()
if not domain_override:
current_site = Site.objects.get_current()
site_name = current_site.name
domain = current_site.domain
else:
site_name = domain = domain_override
t = loader.get_template('registration/password_reset_email.html')
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(Context(c)), None, [self.user_cache.email])
class PasswordChangeForm(forms.Manipulator):
"A form that lets a user change his password."
def __init__(self, user):
self.user = user
self.fields = (
forms.PasswordField(field_name="old_password", length=30, maxlength=30, is_required=True,
validator_list=[self.isValidOldPassword]),
forms.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.")]),
forms.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()

View File

@ -10,7 +10,7 @@ def authenhandler(req, **kwargs):
# that so that the following import works
os.environ.update(req.subprocess_env)
from django.models.auth import users
from django.contrib.auth.models import User
# check for PythonOptions
_str_to_bool = lambda s: s.lower() in ('1', 'true', 'on', 'yes')
@ -21,14 +21,14 @@ def authenhandler(req, **kwargs):
superuser_only = _str_to_bool(options.get('DjangoRequireSuperuserStatus', "off"))
# check that the username is valid
kwargs = {'username__exact': req.user, 'is_active__exact': True}
kwargs = {'username': req.user, 'is_active': True}
if staff_only:
kwargs['is_staff__exact'] = True
kwargs['is_staff'] = True
if superuser_only:
kwargs['is_superuser__exact'] = True
kwargs['is_superuser'] = True
try:
user = users.get_object(**kwargs)
except users.UserDoesNotExist:
user = User.objects.get(**kwargs)
except User.DoesNotExist:
return apache.HTTP_UNAUTHORIZED
# check the password and any permission given

View File

@ -0,0 +1,53 @@
"""
Creates permissions for all installed apps that need permissions.
"""
from django.dispatch import dispatcher
from django.db.models import get_models, signals
from django.contrib.auth import models as auth_app
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 = []
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 create_permissions(app, created_models):
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import Permission
app_models = get_models(app)
if not app_models:
return
for klass in app_models:
if not klass._meta.admin:
continue
ctype = ContentType.objects.get_for_model(klass)
for codename, name in _get_all_permissions(klass._meta):
try:
Permission.objects.get(name=name, codename=codename, content_type__pk=ctype.id)
except Permission.DoesNotExist:
p = Permission(name=name, codename=codename, content_type=ctype)
p.save()
print "Adding permission '%s'" % p
def create_superuser(app, created_models):
from django.contrib.auth.models import User
from django.contrib.auth.create_superuser import createsuperuser as do_create
if User in created_models:
msg = "\nYou just installed Django's auth system, which means you don't have " \
"any superusers defined.\nWould you like to create one now? (yes/no): "
confirm = raw_input(msg)
while 1:
if confirm not in ('yes', 'no'):
confirm = raw_input('Please enter either "yes" or "no": ')
continue
if confirm == 'yes':
do_create()
break
dispatcher.connect(create_permissions, signal=signals.post_syncdb)
dispatcher.connect(create_superuser, sender=auth_app, signal=signals.post_syncdb)

View File

@ -0,0 +1,19 @@
class LazyUser(object):
def __init__(self):
self._user = None
def __get__(self, request, obj_type=None):
if self._user is None:
from django.contrib.auth.models import User, AnonymousUser, SESSION_KEY
try:
user_id = request.session[SESSION_KEY]
self._user = User.objects.get(pk=user_id)
except (KeyError, User.DoesNotExist):
self._user = AnonymousUser()
return self._user
class AuthenticationMiddleware:
def process_request(self, request):
assert hasattr(request, 'session'), "The Django authentication middleware requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'."
request.__class__.user = LazyUser()
return None

View File

@ -0,0 +1,264 @@
from django.core import validators
from django.db import backend, connection, models
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import gettext_lazy as _
import datetime
SESSION_KEY = '_auth_user_id'
class SiteProfileNotAvailable(Exception):
pass
class Permission(models.Model):
name = models.CharField(_('name'), maxlength=50)
content_type = models.ForeignKey(ContentType)
codename = models.CharField(_('codename'), maxlength=100)
class Meta:
verbose_name = _('permission')
verbose_name_plural = _('permissions')
unique_together = (('content_type', 'codename'),)
ordering = ('content_type', 'codename')
def __str__(self):
return "%r | %s" % (self.content_type, self.name)
class Group(models.Model):
name = models.CharField(_('name'), maxlength=80, unique=True)
permissions = models.ManyToManyField(Permission, verbose_name=_('permissions'), blank=True, filter_interface=models.HORIZONTAL)
class Meta:
verbose_name = _('group')
verbose_name_plural = _('groups')
ordering = ('name',)
class Admin:
search_fields = ('name',)
def __str__(self):
return self.name
class UserManager(models.Manager):
def create_user(self, username, email, password):
"Creates and saves a User with the given username, e-mail and password."
now = datetime.datetime.now()
user = self.model(None, username, '', '', email.strip().lower(), 'placeholder', False, True, False, now, now)
user.set_password(password)
user.save()
return user
def make_random_password(self, length=10, allowed_chars='abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'):
"Generates a random password with the given length and given allowed_chars"
# Note that default value of allowed_chars does not have "I" or letters
# that look like it -- just to avoid confusion.
from random import choice
return ''.join([choice(allowed_chars) for i in range(length)])
class User(models.Model):
username = models.CharField(_('username'), maxlength=30, unique=True, validator_list=[validators.isAlphaNumeric])
first_name = models.CharField(_('first name'), maxlength=30, blank=True)
last_name = models.CharField(_('last name'), maxlength=30, blank=True)
email = models.EmailField(_('e-mail address'), blank=True)
password = models.CharField(_('password'), maxlength=128, help_text=_("Use '[algo]$[salt]$[hexdigest]'"))
is_staff = models.BooleanField(_('staff status'), help_text=_("Designates whether the user can log into this admin site."))
is_active = models.BooleanField(_('active'), default=True)
is_superuser = models.BooleanField(_('superuser status'))
last_login = models.DateTimeField(_('last login'), default=models.LazyDate())
date_joined = models.DateTimeField(_('date joined'), default=models.LazyDate())
groups = models.ManyToManyField(Group, verbose_name=_('groups'), blank=True,
help_text=_("In addition to the permissions manually assigned, this user will also get all permissions granted to each group he/she is in."))
user_permissions = models.ManyToManyField(Permission, verbose_name=_('user permissions'), blank=True, filter_interface=models.HORIZONTAL)
objects = UserManager()
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
ordering = ('username',)
class Admin:
fields = (
(None, {'fields': ('username', 'password')}),
(_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}),
(_('Permissions'), {'fields': ('is_staff', 'is_active', 'is_superuser', 'user_permissions')}),
(_('Important dates'), {'fields': ('last_login', 'date_joined')}),
(_('Groups'), {'fields': ('groups',)}),
)
list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff')
list_filter = ('is_staff', 'is_superuser')
search_fields = ('username', 'first_name', 'last_name', 'email')
def __str__(self):
return self.username
def get_absolute_url(self):
return "/users/%s/" % self.username
def is_anonymous(self):
return False
def get_full_name(self):
full_name = '%s %s' % (self.first_name, self.last_name)
return full_name.strip()
def set_password(self, raw_password):
import sha, random
algo = 'sha1'
salt = sha.new(str(random.random())).hexdigest()[:5]
hsh = sha.new(salt+raw_password).hexdigest()
self.password = '%s$%s$%s' % (algo, salt, hsh)
def check_password(self, raw_password):
"""
Returns a boolean of whether the raw_password was correct. Handles
encryption formats behind the scenes.
"""
# Backwards-compatibility check. Older passwords won't include the
# algorithm or salt.
if '$' not in self.password:
import md5
is_correct = (self.password == md5.new(raw_password).hexdigest())
if is_correct:
# Convert the password to the new, more secure format.
self.set_password(raw_password)
self.save()
return is_correct
algo, salt, hsh = self.password.split('$')
if algo == 'md5':
import md5
return hsh == md5.new(salt+raw_password).hexdigest()
elif algo == 'sha1':
import sha
return hsh == sha.new(salt+raw_password).hexdigest()
raise ValueError, "Got unknown password algorithm type in password."
def get_group_permissions(self):
"Returns a list of permission strings that this user has through his/her groups."
if not hasattr(self, '_group_perm_cache'):
import sets
cursor = connection.cursor()
# The SQL below works out to the following, after DB quoting:
# cursor.execute("""
# SELECT ct."app_label", p."codename"
# FROM "auth_permission" p, "auth_group_permissions" gp, "auth_user_groups" ug, "django_content_type" ct
# WHERE p."id" = gp."permission_id"
# AND gp."group_id" = ug."group_id"
# AND ct."id" = p."content_type_id"
# AND ug."user_id" = %s, [self.id])
sql = """
SELECT ct.%s, p.%s
FROM %s p, %s gp, %s ug, %s ct
WHERE p.%s = gp.%s
AND gp.%s = ug.%s
AND ct.%s = p.%s
AND ug.%s = %%s""" % (
backend.quote_name('app_label'), backend.quote_name('codename'),
backend.quote_name('auth_permission'), backend.quote_name('auth_group_permissions'),
backend.quote_name('auth_user_groups'), backend.quote_name('django_content_type'),
backend.quote_name('id'), backend.quote_name('permission_id'),
backend.quote_name('group_id'), backend.quote_name('group_id'),
backend.quote_name('id'), backend.quote_name('content_type_id'),
backend.quote_name('user_id'),)
cursor.execute(sql, [self.id])
self._group_perm_cache = sets.Set(["%s.%s" % (row[0], row[1]) for row in cursor.fetchall()])
return self._group_perm_cache
def get_all_permissions(self):
if not hasattr(self, '_perm_cache'):
import sets
self._perm_cache = sets.Set(["%s.%s" % (p.content_type.app_label, p.codename) for p in self.user_permissions.all()])
self._perm_cache.update(self.get_group_permissions())
return self._perm_cache
def has_perm(self, perm):
"Returns True if the user has the specified permission."
if not self.is_active:
return False
if self.is_superuser:
return True
return perm in self.get_all_permissions()
def has_perms(self, perm_list):
"Returns True if the user has each of the specified permissions."
for perm in perm_list:
if not self.has_perm(perm):
return False
return True
def has_module_perms(self, app_label):
"Returns True if the user has any permissions in the given app label."
if self.is_superuser:
return True
return bool(len([p for p in self.get_all_permissions() if p[:p.index('.')] == app_label]))
def get_and_delete_messages(self):
messages = []
for m in self.message_set.all():
messages.append(m.message)
m.delete()
return messages
def email_user(self, subject, message, from_email=None):
"Sends an e-mail to this User."
from django.core.mail import send_mail
send_mail(subject, message, from_email, [self.email])
def get_profile(self):
"""
Returns site-specific profile for this user. Raises
SiteProfileNotAvailable if this site does not allow profiles.
"""
if not hasattr(self, '_profile_cache'):
from django.conf import settings
if not settings.AUTH_PROFILE_MODULE:
raise SiteProfileNotAvailable
try:
app_label, model_name = settings.AUTH_PROFILE_MODULE.split('.')
model = models.get_model(app_label, model_name)
self._profile_cache = model._default_manager.get(user__id__exact=self.id)
except ImportError, ImproperlyConfigured:
raise SiteProfileNotAvailable
return self._profile_cache
class Message(models.Model):
user = models.ForeignKey(User)
message = models.TextField(_('message'))
def __str__(self):
return self.message
class AnonymousUser(object):
id = None
username = ''
def __init__(self):
pass
def __str__(self):
return 'AnonymousUser'
def save(self):
raise NotImplementedError
def delete(self):
raise NotImplementedError
def set_password(self, raw_password):
raise NotImplementedError
def check_password(self, raw_password):
raise NotImplementedError
def _get_groups(self):
raise NotImplementedError
groups = property(_get_groups)
def _get_user_permissions(self):
raise NotImplementedError
user_permissions = property(_get_user_permissions)
def has_perm(self, perm):
return False
def has_module_perms(self, module):
return False
def get_and_delete_messages(self):
return []
def is_anonymous(self):
return True

View File

@ -0,0 +1,84 @@
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.forms import PasswordResetForm, PasswordChangeForm
from django import forms
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.contrib.auth.models import SESSION_KEY
from django.contrib.sites.models import Site
from django.http import HttpResponse, HttpResponseRedirect
from django.contrib.auth.decorators import login_required
from django.contrib.auth import LOGIN_URL, REDIRECT_FIELD_NAME
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/'
request.session[SESSION_KEY] = manipulator.get_user_id()
request.session.delete_test_cookie()
return HttpResponseRedirect(redirect_to)
else:
errors = {}
request.session.set_test_cookie()
return render_to_response('registration/login.html', {
'form': forms.FormWrapper(manipulator, request.POST, errors),
REDIRECT_FIELD_NAME: redirect_to,
'site_name': Site.objects.get_current().name,
}, context_instance=RequestContext(request))
def logout(request, next_page=None):
"Logs out the user and displays 'You are logged out' message."
try:
del request.session[SESSION_KEY]
except KeyError:
return render_to_response('registration/logged_out.html', {'title': 'Logged out'}, context_instance=RequestContext(request))
else:
# Redirect to this page until the session has been cleared.
return HttpResponseRedirect(next_page or request.path)
def logout_then_login(request, login_url=LOGIN_URL):
"Logs out the user if he is logged in. Then redirects to the log-in page."
return logout(request, login_url)
def redirect_to_login(next, login_url=LOGIN_URL):
"Redirects the user to the login page, passing the given 'next' page"
return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, next))
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)
return render_to_response('registration/password_reset_form.html', {'form': forms.FormWrapper(form, new_data, errors)},
context_instance=RequestContext(request))
def password_reset_done(request):
return render_to_response('registration/password_reset_done.html', context_instance=RequestContext(request))
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)
return render_to_response('registration/password_change_form.html', {'form': forms.FormWrapper(form, new_data, errors)},
context_instance=RequestContext(request))
password_change = login_required(password_change)
def password_change_done(request):
return render_to_response('registration/password_change_done.html', context_instance=RequestContext(request))

View File

@ -1,48 +1,48 @@
from django.conf import settings
from django.contrib.comments.models import Comment, FreeComment
from django.contrib.syndication.feeds import Feed
from django.core.exceptions import ObjectDoesNotExist
from django.models.core import sites
from django.models.comments import comments, freecomments
from django.contrib.sites.models import Site
class LatestFreeCommentsFeed(Feed):
"""Feed of latest comments on the current site"""
comments_module = freecomments
comments_class = FreeComment
def title(self):
if not hasattr(self, '_site'):
self._site = sites.get_current()
self._site = Site.objects.get_current()
return "%s comments" % self._site.name
def link(self):
if not hasattr(self, '_site'):
self._site = sites.get_current()
self._site = Site.objects.get_current()
return "http://%s/" % (self._site.domain)
def description(self):
if not hasattr(self, '_site'):
self._site = sites.get_current()
self._site = Site.objects.get_current()
return "Latest comments on %s" % self._site.name
def items(self):
return self.comments_module.get_list(**self._get_lookup_kwargs())
return self.comments_class.objects.filter(**self._get_lookup_kwargs())
def _get_lookup_kwargs(self):
return {
'site__pk' : settings.SITE_ID,
'is_public__exact' : True,
'limit' : 40,
'site__pk': settings.SITE_ID,
'is_public__exact': True,
'limit': 40,
}
class LatestCommentsFeed(LatestFreeCommentsFeed):
"""Feed of latest free comments on the current site"""
comments_module = comments
comments_class = Comment
def _get_lookup_kwargs(self):
kwargs = LatestFreeCommentsFeed._get_lookup_kwargs(self)
kwargs['is_removed__exact'] = False
if settings.COMMENTS_BANNED_USERS_GROUP:
kwargs['where'] = ['user_id NOT IN (SELECT user_id FROM auth_users_group WHERE group_id = %s)']
kwargs['params'] = [COMMENTS_BANNED_USERS_GROUP]
return kwargs
return kwargs

View File

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

View File

@ -1 +0,0 @@
__all__ = ['comments']

View File

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

View File

@ -1,16 +1,16 @@
"Custom template tags for user comments"
from django.core import template
from django.core.template import loader
from django.contrib.comments.models import Comment, FreeComment
from django.contrib.comments.models import PHOTOS_REQUIRED, PHOTOS_OPTIONAL, RATINGS_REQUIRED, RATINGS_OPTIONAL, IS_PUBLIC
from django.contrib.comments.models import MIN_PHOTO_DIMENSION, MAX_PHOTO_DIMENSION
from django import template
from django.template import loader
from django.core.exceptions import ObjectDoesNotExist
from django.models.comments import comments, freecomments
from django.models.core import contenttypes
from django.contrib.contenttypes.models import ContentType
import re
register = template.Library()
COMMENT_FORM = 'comments/form'
FREE_COMMENT_FORM = 'comments/freeform'
COMMENT_FORM = 'comments/form.html'
FREE_COMMENT_FORM = 'comments/freeform.html'
class CommentFormNode(template.Node):
def __init__(self, content_type, obj_id_lookup_var, obj_id, free,
@ -46,24 +46,24 @@ class CommentFormNode(template.Node):
context['display_form'] = True
context['target'] = '%s:%s' % (self.content_type.id, self.obj_id)
options = []
for var, abbr in (('photos_required', comments.PHOTOS_REQUIRED),
('photos_optional', comments.PHOTOS_OPTIONAL),
('ratings_required', comments.RATINGS_REQUIRED),
('ratings_optional', comments.RATINGS_OPTIONAL),
('is_public', comments.IS_PUBLIC)):
for var, abbr in (('photos_required', PHOTOS_REQUIRED),
('photos_optional', PHOTOS_OPTIONAL),
('ratings_required', RATINGS_REQUIRED),
('ratings_optional', RATINGS_OPTIONAL),
('is_public', IS_PUBLIC)):
context[var] = getattr(self, var)
if getattr(self, var):
options.append(abbr)
context['options'] = ','.join(options)
if self.free:
context['hash'] = comments.get_security_hash(context['options'], '', '', context['target'])
context['hash'] = Comment.objects.get_security_hash(context['options'], '', '', context['target'])
default_form = loader.get_template(FREE_COMMENT_FORM)
else:
context['photo_options'] = self.photo_options
context['rating_options'] = normalize_newlines(base64.encodestring(self.rating_options).strip())
if self.rating_options:
context['rating_range'], context['rating_choices'] = comments.get_rating_options(self.rating_options)
context['hash'] = comments.get_security_hash(context['options'], context['photo_options'], context['rating_options'], context['target'])
context['rating_range'], context['rating_choices'] = Comment.objects.get_rating_options(self.rating_options)
context['hash'] = Comment.objects.get_security_hash(context['options'], context['photo_options'], context['rating_options'], context['target'])
default_form = loader.get_template(COMMENT_FORM)
output = default_form.render(context)
context.pop()
@ -76,13 +76,13 @@ class CommentCountNode(template.Node):
self.var_name, self.free = var_name, free
def render(self, context):
from django.conf.settings import SITE_ID
get_count_function = self.free and freecomments.get_count or comments.get_count
from django.conf import settings
manager = self.free and FreeComment.objects or Comment.objects
if self.context_var_name is not None:
self.obj_id = template.resolve_variable(self.context_var_name, context)
comment_count = get_count_function(object_id__exact=self.obj_id,
content_type__package__label__exact=self.package,
content_type__python_module_name__exact=self.module, site__id__exact=SITE_ID)
comment_count = manager.filter(object_id__exact=self.obj_id,
content_type__app_label__exact=self.package,
content_type__model__exact=self.module, site__id__exact=settings.SITE_ID).count()
context[self.var_name] = comment_count
return ''
@ -95,8 +95,8 @@ class CommentListNode(template.Node):
self.extra_kwargs = extra_kwargs or {}
def render(self, context):
from django.conf.settings import COMMENTS_BANNED_USERS_GROUP, SITE_ID
get_list_function = self.free and freecomments.get_list or comments.get_list_with_karma
from django.conf import settings
get_list_function = self.free and FreeComment.objects.filter or Comment.objects.get_list_with_karma
if self.context_var_name is not None:
try:
self.obj_id = template.resolve_variable(self.context_var_name, context)
@ -104,26 +104,24 @@ class CommentListNode(template.Node):
return ''
kwargs = {
'object_id__exact': self.obj_id,
'content_type__package__label__exact': self.package,
'content_type__python_module_name__exact': self.module,
'site__id__exact': SITE_ID,
'select_related': True,
'order_by': (self.ordering + 'submit_date',),
'content_type__app_label__exact': self.package,
'content_type__model__exact': self.module,
'site__id__exact': settings.SITE_ID,
}
kwargs.update(self.extra_kwargs)
if not self.free and COMMENTS_BANNED_USERS_GROUP:
kwargs['select'] = {'is_hidden': 'user_id IN (SELECT user_id FROM auth_users_groups WHERE group_id = %s)' % COMMENTS_BANNED_USERS_GROUP}
comment_list = get_list_function(**kwargs)
if not self.free and settings.COMMENTS_BANNED_USERS_GROUP:
kwargs['select'] = {'is_hidden': 'user_id IN (SELECT user_id FROM auth_user_groups WHERE group_id = %s)' % settings.COMMENTS_BANNED_USERS_GROUP}
comment_list = get_list_function(**kwargs).order_by(self.ordering + 'submit_date').select_related()
if not self.free:
if context.has_key('user') and not context['user'].is_anonymous():
user_id = context['user'].id
context['user_can_moderate_comments'] = comments.user_is_moderator(context['user'])
context['user_can_moderate_comments'] = Comment.objects.user_is_moderator(context['user'])
else:
user_id = None
context['user_can_moderate_comments'] = False
# Only display comments by banned users to those users themselves.
if COMMENTS_BANNED_USERS_GROUP:
if settings.COMMENTS_BANNED_USERS_GROUP:
comment_list = [c for c in comment_list if not c.is_hidden or (user_id == c.user_id)]
context[self.var_name] = comment_list
@ -157,8 +155,8 @@ class DoCommentForm:
except ValueError: # unpack list of wrong size
raise template.TemplateSyntaxError, "Third argument in %r tag must be in the format 'package.module'" % tokens[0]
try:
content_type = contenttypes.get_object(package__label__exact=package, python_module_name__exact=module)
except contenttypes.ContentTypeDoesNotExist:
content_type = ContentType.objects.get(app_label__exact=package, model__exact=module)
except ContentType.DoesNotExist:
raise template.TemplateSyntaxError, "%r tag has invalid content-type '%s.%s'" % (tokens[0], package, module)
obj_id_lookup_var, obj_id = None, None
if tokens[3].isdigit():
@ -183,8 +181,8 @@ class DoCommentForm:
if not opt.isalnum():
raise template.TemplateSyntaxError, "Invalid photo directory name in %r tag: '%s'" % (tokens[0], opt)
for opt in option_list[1::3] + option_list[2::3]:
if not opt.isdigit() or not (comments.MIN_PHOTO_DIMENSION <= int(opt) <= comments.MAX_PHOTO_DIMENSION):
raise template.TemplateSyntaxError, "Invalid photo dimension in %r tag: '%s'. Only values between %s and %s are allowed." % (tokens[0], opt, comments.MIN_PHOTO_DIMENSION, comments.MAX_PHOTO_DIMENSION)
if not opt.isdigit() or not (MIN_PHOTO_DIMENSION <= int(opt) <= MAX_PHOTO_DIMENSION):
raise template.TemplateSyntaxError, "Invalid photo dimension in %r tag: '%s'. Only values between %s and %s are allowed." % (tokens[0], opt, MIN_PHOTO_DIMENSION, MAX_PHOTO_DIMENSION)
# VALIDATION ENDS #########################################
kwargs[option] = True
kwargs['photo_options'] = args
@ -237,8 +235,8 @@ class DoCommentCount:
except ValueError: # unpack list of wrong size
raise template.TemplateSyntaxError, "Third argument in %r tag must be in the format 'package.module'" % tokens[0]
try:
content_type = contenttypes.get_object(package__label__exact=package, python_module_name__exact=module)
except contenttypes.ContentTypeDoesNotExist:
content_type = ContentType.objects.get(app_label__exact=package, model__exact=module)
except ContentType.DoesNotExist:
raise template.TemplateSyntaxError, "%r tag has invalid content-type '%s.%s'" % (tokens[0], package, module)
var_name, obj_id = None, None
if tokens[3].isdigit():
@ -292,8 +290,8 @@ class DoGetCommentList:
except ValueError: # unpack list of wrong size
raise template.TemplateSyntaxError, "Third argument in %r tag must be in the format 'package.module'" % tokens[0]
try:
content_type = contenttypes.get_object(package__label__exact=package, python_module_name__exact=module)
except contenttypes.ContentTypeDoesNotExist:
content_type = ContentType.objects.get(app_label__exact=package,model__exact=module)
except ContentType.DoesNotExist:
raise template.TemplateSyntaxError, "%r tag has invalid content-type '%s.%s'" % (tokens[0], package, module)
var_name, obj_id = None, None
if tokens[3].isdigit():

View File

@ -1,14 +1,17 @@
from django.core import formfields, validators
from django.core import validators
from django import forms
from django.core.mail import mail_admins, mail_managers
from django.core.exceptions import Http404, ObjectDoesNotExist
from django.core.extensions import DjangoContext, render_to_response
from django.models.auth import users
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 HttpResponseRedirect
from django.http import Http404
from django.core.exceptions import ObjectDoesNotExist
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.contrib.auth.models import SESSION_KEY
from django.contrib.comments.models import Comment, FreeComment, PHOTOS_REQUIRED, PHOTOS_OPTIONAL, RATINGS_REQUIRED, RATINGS_OPTIONAL, IS_PUBLIC
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.forms import AuthenticationForm
from django.http import 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
from django.conf import settings
from django.utils.translation import ngettext
import base64, datetime
@ -26,37 +29,37 @@ class PublicCommentManipulator(AuthenticationForm):
else:
return []
self.fields.extend([
formfields.LargeTextField(field_name="comment", maxlength=3000, is_required=True,
forms.LargeTextField(field_name="comment", maxlength=3000, is_required=True,
validator_list=[self.hasNoProfanities]),
formfields.RadioSelectField(field_name="rating1", choices=choices,
forms.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,
forms.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,
forms.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,
forms.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,
forms.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,
forms.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,
forms.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,
forms.RadioSelectField(field_name="rating8", choices=choices,
is_required=ratings_required and num_rating_choices > 7,
validator_list=get_validator_list(8),
),
@ -69,25 +72,25 @@ class PublicCommentManipulator(AuthenticationForm):
self.user_cache = user
def hasNoProfanities(self, field_data, all_data):
if COMMENTS_ALLOW_PROFANITIES:
if settings.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"],
return 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)
datetime.datetime.now(), new_data["is_public"], new_data["ip_address"], False, settings.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"],
for old in Comment.objects.filter(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
@ -105,37 +108,37 @@ class PublicCommentManipulator(AuthenticationForm):
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:
if self.user_cache.comment_set.count() <= settings.COMMENTS_FIRST_FEW:
message = ngettext('This comment was posted by a user who has posted fewer than %(count)s comment:\n\n%(text)s',
'This comment was posted by a user who has posted fewer than %(count)s comments:\n\n%(text)s') % \
{'count': COMMENTS_FIRST_FEW, 'text': c.get_as_text()}
{'count': settings.COMMENTS_FIRST_FEW, 'text': 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_group_list()]:
if settings.COMMENTS_SKETCHY_USERS_GROUP and settings.COMMENTS_SKETCHY_USERS_GROUP in [g.id for g in self.user_cache.get_group_list()]:
message = _('This comment was posted by a sketchy user:\n\n%(text)s') % {'text': 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):
class PublicFreeCommentManipulator(forms.Manipulator):
"Manipulator that handles public free (unregistered) comments"
def __init__(self):
self.fields = (
formfields.TextField(field_name="person_name", maxlength=50, is_required=True,
forms.TextField(field_name="person_name", maxlength=50, is_required=True,
validator_list=[self.hasNoProfanities]),
formfields.LargeTextField(field_name="comment", maxlength=3000, is_required=True,
forms.LargeTextField(field_name="comment", maxlength=3000, is_required=True,
validator_list=[self.hasNoProfanities]),
)
def hasNoProfanities(self, field_data, all_data):
if COMMENTS_ALLOW_PROFANITIES:
if settings.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"],
return 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)
new_data["ip_address"], False, settings.SITE_ID)
def save(self, new_data):
today = datetime.date.today()
@ -143,7 +146,7 @@ class PublicFreeCommentManipulator(formfields.Manipulator):
# 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"],
for old_comment in FreeComment.objects.filter(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):
@ -190,16 +193,16 @@ def post_comment(request):
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:
if Comment.objects.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))
rating_range, rating_choices = Comment.objects.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(pk=content_type_id).get_object_for_this_type(pk=object_id)
obj = ContentType.objects.get(pk=content_type_id).get_object_for_this_type(pk=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'
@ -207,20 +210,20 @@ def post_comment(request):
new_data['content_type_id'] = content_type_id
new_data['object_id'] = object_id
new_data['ip_address'] = request.META.get('REMOTE_ADDR')
new_data['is_public'] = comments.IS_PUBLIC in option_list
new_data['is_public'] = IS_PUBLIC in option_list
manipulator = PublicCommentManipulator(request.user,
ratings_required=comments.RATINGS_REQUIRED in option_list,
ratings_required=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']):
request.session[users.SESSION_KEY] = manipulator.get_user_id()
request.session[SESSION_KEY] = manipulator.get_user_id()
if errors or request.POST.has_key('preview'):
class CommentFormWrapper(formfields.FormWrapper):
class CommentFormWrapper(forms.FormWrapper):
def __init__(self, manipulator, new_data, errors, rating_choices):
formfields.FormWrapper.__init__(self, manipulator, new_data, errors)
forms.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))]
@ -229,22 +232,22 @@ def post_comment(request):
return field_list
comment = errors and '' or manipulator.get_comment(new_data)
comment_form = CommentFormWrapper(manipulator, new_data, errors, rating_choices)
return render_to_response('comments/preview', {
return render_to_response('comments/preview.html', {
'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,
'ratings_optional': RATINGS_OPTIONAL in option_list,
'ratings_required': RATINGS_REQUIRED in option_list,
'rating_range': rating_range,
'rating_choices': rating_choices,
}, context_instance=DjangoContext(request))
}, context_instance=RequestContext(request))
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:
if request.META['REMOTE_ADDR'] in settings.BANNED_IPS:
mail_admins("Banned IP attempted to post comment", str(request.POST) + "\n\n" + str(request.META))
else:
manipulator.do_html2python(new_data)
@ -279,10 +282,10 @@ def post_free_comment(request):
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:
if Comment.objects.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(pk=content_type_id)
content_type = ContentType.objects.get(pk=content_type_id)
try:
obj = content_type.get_object_for_this_type(pk=object_id)
except ObjectDoesNotExist:
@ -292,22 +295,22 @@ def post_free_comment(request):
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
new_data['is_public'] = IS_PUBLIC in option_list
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)
return render_to_response('comments/free_preview', {
return render_to_response('comments/free_preview.html', {
'comment': comment,
'comment_form': formfields.FormWrapper(manipulator, new_data, errors),
'comment_form': forms.FormWrapper(manipulator, new_data, errors),
'options': options,
'target': target,
'hash': security_hash,
}, context_instance=DjangoContext(request))
}, context_instance=RequestContext(request))
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:
if request.META['REMOTE_ADDR'] in settings.BANNED_IPS:
from django.core.mail import mail_admins
mail_admins("Practical joker", str(request.POST) + "\n\n" + str(request.META))
else:
@ -330,8 +333,8 @@ def comment_was_posted(request):
if request.GET.has_key('c'):
content_type_id, object_id = request.GET['c'].split(':')
try:
content_type = contenttypes.get_object(pk=content_type_id)
content_type = ContentType.objects.get(pk=content_type_id)
obj = content_type.get_object_for_this_type(pk=object_id)
except ObjectDoesNotExist:
pass
return render_to_response('comments/posted', {'object': obj}, context_instance=DjangoContext(request))
return render_to_response('comments/posted.html', {'object': obj}, context_instance=RequestContext(request))

View File

@ -1,6 +1,7 @@
from django.core.exceptions import Http404
from django.core.extensions import DjangoContext, render_to_response
from django.models.comments import comments, karma
from django.http import Http404
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.contrib.comments.models import Comment, KarmaScore
def vote(request, comment_id, vote):
"""
@ -17,12 +18,12 @@ def vote(request, comment_id, vote):
if request.user.is_anonymous():
raise Http404, _("Anonymous users cannot vote")
try:
comment = comments.get_object(pk=comment_id)
except comments.CommentDoesNotExist:
comment = Comment.objects.get(pk=comment_id)
except Comment.DoesNotExist:
raise Http404, _("Invalid comment ID")
if comment.user_id == request.user.id:
if comment.user.id == request.user.id:
raise Http404, _("No voting for yourself")
karma.vote(request.user.id, comment_id, rating)
KarmaScore.objects.vote(request.user.id, comment_id, rating)
# Reload comment to ensure we have up to date karma count
comment = comments.get_object(pk=comment_id)
return render_to_response('comments/karma_vote_accepted', {'comment': comment}, context_instance=DjangoContext(request))
comment = Comment.objects.get(pk=comment_id)
return render_to_response('comments/karma_vote_accepted.html', {'comment': comment}, context_instance=RequestContext(request))

View File

@ -1,9 +1,10 @@
from django.core.extensions import DjangoContext, render_to_response
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 HttpResponseRedirect
from django.conf.settings import SITE_ID
from django.shortcuts import render_to_response, get_object_or_404
from django.template import RequestContext
from django.http import Http404
from django.contrib.comments.models import Comment, ModeratorDeletion, UserFlag
from django.contrib.auth.decorators import login_required
from django.http import HttpResponseRedirect
from django.conf import settings
def flag(request, comment_id):
"""
@ -14,22 +15,16 @@ def flag(request, comment_id):
comment
the flagged `comments.comments` object
"""
try:
comment = comments.get_object(pk=comment_id, site__id__exact=SITE_ID)
except comments.CommentDoesNotExist:
raise Http404
comment = get_object_or_404(Comment,pk=comment_id, site__id__exact=settings.SITE_ID)
if request.POST:
userflags.flag(comment, request.user)
UserFlag.objects.flag(comment, request.user)
return HttpResponseRedirect('%sdone/' % request.path)
return render_to_response('comments/flag_verify', {'comment': comment}, context_instance=DjangoContext(request))
return render_to_response('comments/flag_verify.html', {'comment': comment}, context_instance=RequestContext(request))
flag = login_required(flag)
def flag_done(request, comment_id):
try:
comment = comments.get_object(pk=comment_id, site__id__exact=SITE_ID)
except comments.CommentDoesNotExist:
raise Http404
return render_to_response('comments/flag_done', {'comment': comment}, context_instance=DjangoContext(request))
comment = get_object_or_404(Comment,pk=comment_id, site__id__exact=settings.SITE_ID)
return render_to_response('comments/flag_done.html', {'comment': comment}, context_instance=RequestContext(request))
def delete(request, comment_id):
"""
@ -40,26 +35,20 @@ def delete(request, comment_id):
comment
the flagged `comments.comments` object
"""
try:
comment = comments.get_object(pk=comment_id, site__id__exact=SITE_ID)
except comments.CommentDoesNotExist:
raise Http404
if not comments.user_is_moderator(request.user):
comment = get_object_or_404(Comment,pk=comment_id, site__id__exact=settings.SITE_ID)
if not Comment.objects.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 = ModeratorDeletion(None, request.user.id, comment.id, None)
m.save()
return HttpResponseRedirect('%sdone/' % request.path)
return render_to_response('comments/delete_verify', {'comment': comment}, context_instance=DjangoContext(request))
return render_to_response('comments/delete_verify.html', {'comment': comment}, context_instance=RequestContext(request))
delete = login_required(delete)
def delete_done(request, comment_id):
try:
comment = comments.get_object(pk=comment_id, site__id__exact=SITE_ID)
except comments.CommentDoesNotExist:
raise Http404
return render_to_response('comments/delete_done', {'comment': comment}, context_instance=DjangoContext(request))
comment = get_object_or_404(Comment,pk=comment_id, site__id__exact=settings.SITE_ID)
return render_to_response('comments/delete_done.html', {'comment': comment}, context_instance=RequestContext(request))

View File

@ -0,0 +1,49 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
class ContentTypeManager(models.Manager):
def get_for_model(self, model):
"""
Returns the ContentType object for the given model, creating the
ContentType if necessary.
"""
opts = model._meta
try:
return self.model._default_manager.get(app_label=opts.app_label,
model=opts.object_name.lower())
except self.model.DoesNotExist:
# The str() is needed around opts.verbose_name because it's a
# django.utils.functional.__proxy__ object.
ct = self.model(name=str(opts.verbose_name),
app_label=opts.app_label, model=opts.object_name.lower())
ct.save()
return ct
class ContentType(models.Model):
name = models.CharField(maxlength=100)
app_label = models.CharField(maxlength=100)
model = models.CharField(_('python model class name'), maxlength=100)
objects = ContentTypeManager()
class Meta:
verbose_name = _('content type')
verbose_name_plural = _('content types')
db_table = 'django_content_type'
ordering = ('name',)
unique_together = (('app_label', 'model'),)
def __repr__(self):
return self.name
def model_class(self):
"Returns the Python model class for this type of content."
from django.db import models
return models.get_model(self.app_label, self.model)
def get_object_for_this_type(self, **kwargs):
"""
Returns an object of this type for the keyword arguments given.
Basically, this is a proxy around this object_type's get_object() model
method. The ObjectNotExist exception, if thrown, will not be caught,
so code that calls this method should catch it.
"""
return self.model_class()._default_manager.get(**kwargs)

View File

@ -1,6 +1,6 @@
from django.contrib.flatpages.views import flatpage
from django.core.extensions import Http404
from django.conf.settings import DEBUG
from django.http import Http404
from django.conf import settings
class FlatpageFallbackMiddleware:
def process_response(self, request, response):
@ -13,6 +13,6 @@ class FlatpageFallbackMiddleware:
except Http404:
return response
except:
if DEBUG:
if settings.DEBUG:
raise
return response

View File

@ -0,0 +1,33 @@
from django.core import validators
from django.db import models
from django.contrib.sites.models import Site
from django.utils.translation import gettext_lazy as _
class FlatPage(models.Model):
url = models.CharField(_('URL'), maxlength=100, validator_list=[validators.isAlphaNumericURL],
help_text=_("Example: '/about/contact/'. Make sure to have leading and trailing slashes."))
title = models.CharField(_('title'), maxlength=200)
content = models.TextField(_('content'))
enable_comments = models.BooleanField(_('enable comments'))
template_name = models.CharField(_('template name'), maxlength=70, blank=True,
help_text=_("Example: 'flatpages/contact_page'. If this isn't provided, the system will use 'flatpages/default'."))
registration_required = models.BooleanField(_('registration required'), help_text=_("If this is checked, only logged-in users will be able to view the page."))
sites = models.ManyToManyField(Site)
class Meta:
db_table = 'django_flatpage'
verbose_name = _('flat page')
verbose_name_plural = _('flat pages')
ordering = ('url',)
class Admin:
fields = (
(None, {'fields': ('url', 'title', 'content', 'sites')}),
('Advanced options', {'classes': 'collapse', 'fields': ('enable_comments', 'registration_required', 'template_name')}),
)
list_filter = ('sites',)
search_fields = ('url', 'title')
def __repr__(self):
return "%s -- %s" % (self.url, self.title)
def get_absolute_url(self):
return self.url

View File

@ -1 +0,0 @@
__all__ = ['flatpages']

View File

@ -1,33 +0,0 @@
from django.core import meta, validators
from django.models.core import Site
from django.utils.translation import gettext_lazy as _
class FlatPage(meta.Model):
url = meta.CharField(_('URL'), maxlength=100, validator_list=[validators.isAlphaNumericURL],
help_text=_("Example: '/about/contact/'. Make sure to have leading and trailing slashes."))
title = meta.CharField(_('title'), maxlength=200)
content = meta.TextField(_('content'))
enable_comments = meta.BooleanField(_('enable comments'))
template_name = meta.CharField(_('template name'), maxlength=70, blank=True,
help_text=_("Example: 'flatpages/contact_page'. If this isn't provided, the system will use 'flatpages/default'."))
registration_required = meta.BooleanField(_('registration required'), help_text=_("If this is checked, only logged-in users will be able to view the page."))
sites = meta.ManyToManyField(Site)
class META:
db_table = 'django_flatpages'
verbose_name = _('flat page')
verbose_name_plural = _('flat pages')
ordering = ('url',)
admin = meta.Admin(
fields = (
(None, {'fields': ('url', 'title', 'content', 'sites')}),
('Advanced options', {'classes': 'collapse', 'fields': ('enable_comments', 'registration_required', 'template_name')}),
),
list_filter = ('sites',),
search_fields = ('url', 'title'),
)
def __repr__(self):
return "%s -- %s" % (self.url, self.title)
def get_absolute_url(self):
return self.url

View File

@ -1,10 +1,10 @@
from django.core import template_loader
from django.core.extensions import get_object_or_404, DjangoContext
from django.models.flatpages import flatpages
from django.utils.httpwrappers import HttpResponse
from django.conf.settings import SITE_ID
from django.contrib.flatpages.models import FlatPage
from django.template import loader, RequestContext
from django.shortcuts import get_object_or_404
from django.http import HttpResponse
from django.conf import settings
DEFAULT_TEMPLATE = 'flatpages/default'
DEFAULT_TEMPLATE = 'flatpages/default.html'
def flatpage(request, url):
"""
@ -12,24 +12,24 @@ def flatpage(request, url):
Models: `flatpages.flatpages`
Templates: Uses the template defined by the ``template_name`` field,
or `flatpages/default` if template_name is not defined.
or `flatpages/default.html` if template_name is not defined.
Context:
flatpage
`flatpages.flatpages` object
"""
if not url.startswith('/'):
url = "/" + url
f = get_object_or_404(flatpages, url__exact=url, sites__id__exact=SITE_ID)
f = get_object_or_404(FlatPage, url__exact=url, sites__id__exact=settings.SITE_ID)
# If registration is required for accessing this page, and the user isn't
# logged in, redirect to the login page.
if f.registration_required and request.user.is_anonymous():
from django.views.auth.login import redirect_to_login
from django.contrib.auth.views import redirect_to_login
return redirect_to_login(request.path)
if f.template_name:
t = template_loader.select_template((f.template_name, DEFAULT_TEMPLATE))
t = loader.select_template((f.template_name, DEFAULT_TEMPLATE))
else:
t = template_loader.get_template(DEFAULT_TEMPLATE)
c = DjangoContext(request, {
t = loader.get_template(DEFAULT_TEMPLATE)
c = RequestContext(request, {
'flatpage': f,
})
return HttpResponse(t.render(c))

View File

@ -14,7 +14,8 @@ In each case, if the required library is not installed, the filter will
silently fail and return the un-marked-up text.
"""
from django.core import template
from django import template
from django.conf import settings
register = template.Library()
@ -22,6 +23,8 @@ def textile(value):
try:
import textile
except ImportError:
if settings.DEBUG:
raise template.TemplateSyntaxError, "Error in {% textile %} filter: The Python textile library isn't installed."
return value
else:
return textile.textile(value)
@ -30,6 +33,8 @@ def markdown(value):
try:
import markdown
except ImportError:
if settings.DEBUG:
raise template.TemplateSyntaxError, "Error in {% markdown %} filter: The Python markdown library isn't installed."
return value
else:
return markdown.markdown(value)
@ -38,6 +43,8 @@ def restructuredtext(value):
try:
from docutils.core import publish_parts
except ImportError:
if settings.DEBUG:
raise template.TemplateSyntaxError, "Error in {% restructuredtext %} filter: The Python docutils library isn't installed."
return value
else:
parts = publish_parts(source=value, writer_name="html4css1")

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