mirror of https://github.com/django/django.git
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:
parent
d5dbeaa9be
commit
f69cf70ed8
4
AUTHORS
4
AUTHORS
|
@ -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
|
||||
|
|
|
@ -1 +1 @@
|
|||
VERSION = (0, 9, 1, 'SVN')
|
||||
VERSION = (0, 95, 'post-magic-removal')
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
from django.db import models
|
||||
|
||||
# Create your models here.
|
|
@ -1 +0,0 @@
|
|||
__all__ = ['{{ app_name }}']
|
|
@ -1,3 +0,0 @@
|
|||
from django.core import meta
|
||||
|
||||
# Create your models here.
|
|
@ -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",
|
||||
|
|
|
@ -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',
|
||||
)
|
||||
|
|
|
@ -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')),
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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'),
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"; /**/
|
|
@ -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; }
|
||||
|
|
|
@ -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; }
|
|
@ -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; }
|
|
@ -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; }
|
||||
|
|
|
@ -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; }
|
|
@ -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; }
|
|
@ -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 */
|
|
@ -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 |
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)
|
|
@ -1 +0,0 @@
|
|||
__all__ = ['admin']
|
|
@ -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()
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "admin/base_site" %}
|
||||
{% extends "admin/base_site.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans 'Page not found' %}{% endblock %}
|
||||
|
|
|
@ -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> › {% trans "Server error" %}</div>{% endblock %}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "admin/base" %}
|
||||
{% extends "admin/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{{ title }} | {% trans 'Django site admin' %}{% endblock %}
|
||||
|
|
|
@ -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> ›
|
||||
<a href="../">{{ bound_manipulator.verbose_name_plural|capfirst }}</a> ›
|
||||
{% if add %}{% trans "Add" %} {{ bound_manipulator.verbose_name }}{% else %}{{ bound_manipulator.original|striptags|truncatewords:"18" }}{% endif %}
|
||||
<a href="../">{{ opts.verbose_name_plural|capfirst }}</a> ›
|
||||
{% 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 %}
|
||||
|
|
|
@ -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> › {{ cl.opts.verbose_name_plural|capfirst }} </div>{% endblock %}{% endif %}
|
||||
{% if not is_popup %}{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans "Home" %}</a> › {{ cl.opts.verbose_name_plural|capfirst }}</div>{% endblock %}{% endif %}
|
||||
{% block coltype %}flex{% endblock %}
|
||||
{% block content %}
|
||||
<div id="content-main">
|
||||
|
|
|
@ -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> ›
|
||||
<a href="../../">{{ opts.verbose_name_plural|capfirst }}</a> ›
|
||||
<a href="../">{{ object|striptags|truncatewords:"18" }}</a> ›
|
||||
{% 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 %}
|
||||
|
|
|
@ -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 %} <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>
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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> </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> </label><input type="submit" value="{% trans 'Log in' %}" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "admin/base_site" %}
|
||||
{% extends "admin/base_site.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
|
|
|
@ -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> › <a href="../">{% trans "Documentation" %}</a> › {% trans "Bookmarklets" %}</div>{% endblock %}
|
||||
{% block userlinks %}<a href="../../password_change/">{% trans 'Change password' %}</a> / <a href="../../logout/">{% trans 'Log out' %}</a>{% endblock %}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "admin/base_site" %}
|
||||
{% extends "admin/base_site.html" %}
|
||||
{% load i18n %}
|
||||
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">Home</a> › Documentation</div>{% endblock %}
|
||||
{% block userlinks %}<a href="../password_change/">{% trans 'Change password' %}</a> / <a href="../logout/">{% trans 'Log out' %}</a>{% endblock %}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "admin/base_site" %}
|
||||
{% extends "admin/base_site.html" %}
|
||||
{% load i18n %}
|
||||
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">Home</a> › Documentation</div>{% endblock %}
|
||||
{% block userlinks %}<a href="../password_change/">{% trans 'Change password' %}</a> / <a href="../logout/">{% trans 'Log out' %}</a>{% endblock %}
|
||||
|
|
|
@ -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="../">‹ Back to Models Documentation</p>
|
||||
<p class="small"><a href="../">‹ Back to Models Documentation</a></p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -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> › <a href="../">Documentation</a> › 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>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "admin/base_site" %}
|
||||
{% extends "admin/base_site.html" %}
|
||||
{% load i18n %}
|
||||
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../../">Home</a> › <a href="../../">Documentation</a> › Templates › {{ name }}</div>{% endblock %}
|
||||
{% block userlinks %}<a href="../../../password_change/">{% trans 'Change password' %}</a> / <a href="../../../logout/">{% trans 'Log out' %}</a>{% endblock %}
|
||||
|
|
|
@ -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> › <a href="../">Documentation</a> › filters</div>{% endblock %}
|
||||
|
|
|
@ -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> › <a href="../">Documentation</a> › Tags</div>{% endblock %}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "admin/base_site" %}
|
||||
{% extends "admin/base_site.html" %}
|
||||
{% load i18n %}
|
||||
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../../">Home</a> › <a href="../../">Documentation</a> › <a href="../">Views</a> › {{ name }}</div>{% endblock %}
|
||||
{% block userlinks %}<a href="../../../password_change/">{% trans 'Change password' %}</a> / <a href="../../../logout/">{% trans 'Log out' %}</a>{% endblock %}
|
||||
|
|
|
@ -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> › <a href="../">Documentation</a> › Views</div>{% endblock %}
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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> › {% trans 'Password change' %}</div>{% endblock %}
|
||||
|
|
|
@ -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> › {% trans 'Password change' %}</div>{% endblock %}
|
||||
|
|
|
@ -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> › {% trans 'Password reset' %}</div>{% endblock %}
|
||||
|
|
|
@ -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> › {% trans 'Password reset' %}</div>{% endblock %}
|
||||
|
|
|
@ -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 %}&{% 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 %} <strong>{{ bound_field.existing_display|truncatewords:"14" }}</strong>{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{% include "widget/foreign" %}
|
||||
{% include "widget/foreign.html" %}
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
{% include "widget/foreign" %}
|
||||
{% if add %}{% include "widget/foreign.html" %}{% endif %}
|
||||
{% if change %}{% if bound_field.existing_display %} <strong>{{ bound_field.existing_display|truncatewords:"14" }}</strong>{% endif %}{% endif %}
|
||||
|
|
|
@ -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 = ' '
|
||||
result_repr = ' '
|
||||
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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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'),
|
||||
)
|
|
@ -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)
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
@ -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):
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
LOGIN_URL = '/accounts/login/'
|
||||
REDIRECT_FIELD_NAME = 'next'
|
|
@ -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."
|
|
@ -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
|
||||
|
|
@ -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()
|
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -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
|
|
@ -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
|
|
@ -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))
|
|
@ -1,43 +1,43 @@
|
|||
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)
|
||||
|
|
|
@ -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
|
|
@ -1 +0,0 @@
|
|||
__all__ = ['comments']
|
|
@ -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()
|
||||
|
|
@ -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():
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -1 +0,0 @@
|
|||
__all__ = ['flatpages']
|
|
@ -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
|
|
@ -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))
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from django.models.redirects import redirects
|
||||
from django.utils import httpwrappers
|
||||
from django.conf.settings import APPEND_SLASH, SITE_ID
|
||||
from django.contrib.redirects.models import Redirect
|
||||
from django import http
|
||||
from django.conf import settings
|
||||
|
||||
class RedirectFallbackMiddleware:
|
||||
def process_response(self, request, response):
|
||||
|
@ -8,20 +8,20 @@ class RedirectFallbackMiddleware:
|
|||
return response # No need to check for a redirect for non-404 responses.
|
||||
path = request.get_full_path()
|
||||
try:
|
||||
r = redirects.get_object(site__id__exact=SITE_ID, old_path__exact=path)
|
||||
except redirects.RedirectDoesNotExist:
|
||||
r = Redirect.objects.get(site__id__exact=settings.SITE_ID, old_path=path)
|
||||
except Redirect.DoesNotExist:
|
||||
r = None
|
||||
if r is None and APPEND_SLASH:
|
||||
if r is None and settings.APPEND_SLASH:
|
||||
# Try removing the trailing slash.
|
||||
try:
|
||||
r = redirects.get_object(site__id__exact=SITE_ID,
|
||||
old_path__exact=path[:path.rfind('/')]+path[path.rfind('/')+1:])
|
||||
except redirects.RedirectDoesNotExist:
|
||||
r = Redirect.objects.get(site__id__exact=settings.SITE_ID,
|
||||
old_path=path[:path.rfind('/')]+path[path.rfind('/')+1:])
|
||||
except Redirect.DoesNotExist:
|
||||
pass
|
||||
if r is not None:
|
||||
if r == '':
|
||||
return httpwrappers.HttpResponseGone()
|
||||
return httpwrappers.HttpResponsePermanentRedirect(r.new_path)
|
||||
return http.HttpResponseGone()
|
||||
return http.HttpResponsePermanentRedirect(r.new_path)
|
||||
|
||||
# No redirect was found. Return the response.
|
||||
return response
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue