diff options
Diffstat (limited to 'webapp/django/contrib/admin')
120 files changed, 6203 insertions, 0 deletions
diff --git a/webapp/django/contrib/admin/__init__.py b/webapp/django/contrib/admin/__init__.py new file mode 100644 index 0000000000..704dc58ee4 --- /dev/null +++ b/webapp/django/contrib/admin/__init__.py @@ -0,0 +1,19 @@ +from django.contrib.admin.options import ModelAdmin, HORIZONTAL, VERTICAL +from django.contrib.admin.options import StackedInline, TabularInline +from django.contrib.admin.sites import AdminSite, site + +def autodiscover(): + """ + Auto-discover INSTALLED_APPS admin.py modules and fail silently when + not present. This forces an import on them to register any admin bits they + may want. + """ + import imp + from django.conf import settings + for app in settings.INSTALLED_APPS: + try: + imp.find_module("admin", __import__(app, {}, {}, [app.split(".")[-1]]).__path__) + except ImportError: + # there is no app admin.py, skip it + continue + __import__("%s.admin" % app) diff --git a/webapp/django/contrib/admin/filterspecs.py b/webapp/django/contrib/admin/filterspecs.py new file mode 100644 index 0000000000..d6a4a0bc48 --- /dev/null +++ b/webapp/django/contrib/admin/filterspecs.py @@ -0,0 +1,179 @@ +""" +FilterSpec encapsulates the logic for displaying filters in the Django admin. +Filters are specified in models with the "list_filter" option. + +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.db import models +from django.utils.encoding import smart_unicode, iri_to_uri +from django.utils.translation import ugettext as _ +from django.utils.html import escape +from django.utils.safestring import mark_safe +import datetime + +class FilterSpec(object): + filter_specs = [] + def __init__(self, f, request, params, model, model_admin): + self.field = f + self.params = params + + def register(cls, test, factory): + cls.filter_specs.append((test, factory)) + register = classmethod(register) + + def create(cls, f, request, params, model, model_admin): + for test, factory in cls.filter_specs: + if test(f): + return factory(f, request, params, model, model_admin) + create = classmethod(create) + + def has_output(self): + return True + + def choices(self, cl): + raise NotImplementedError() + + def title(self): + return self.field.verbose_name + + def output(self, cl): + t = [] + if self.has_output(): + t.append(_(u'<h3>By %s:</h3>\n<ul>\n') % escape(self.title())) + + for choice in self.choices(cl): + t.append(u'<li%s><a href="%s">%s</a></li>\n' % \ + ((choice['selected'] and ' class="selected"' or ''), + iri_to_uri(choice['query_string']), + choice['display'])) + t.append('</ul>\n\n') + return mark_safe("".join(t)) + +class RelatedFilterSpec(FilterSpec): + def __init__(self, f, request, params, model, model_admin): + super(RelatedFilterSpec, self).__init__(f, request, params, model, model_admin) + 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._meta.pk.name) + self.lookup_val = request.GET.get(self.lookup_kwarg, None) + self.lookup_choices = f.rel.to._default_manager.all() + + def has_output(self): + return len(self.lookup_choices) > 1 + + def title(self): + return self.lookup_title + + def choices(self, cl): + yield {'selected': self.lookup_val is None, + '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._meta.pk.attname) + yield {'selected': self.lookup_val == smart_unicode(pk_val), + 'query_string': cl.get_query_string({self.lookup_kwarg: pk_val}), + 'display': val} + +FilterSpec.register(lambda f: bool(f.rel), RelatedFilterSpec) + +class ChoicesFilterSpec(FilterSpec): + def __init__(self, f, request, params, model, model_admin): + super(ChoicesFilterSpec, self).__init__(f, request, params, model, model_admin) + self.lookup_kwarg = '%s__exact' % f.name + self.lookup_val = request.GET.get(self.lookup_kwarg, None) + + def choices(self, cl): + yield {'selected': self.lookup_val is None, + 'query_string': cl.get_query_string({}, [self.lookup_kwarg]), + 'display': _('All')} + for k, v in self.field.choices: + yield {'selected': smart_unicode(k) == self.lookup_val, + 'query_string': cl.get_query_string({self.lookup_kwarg: k}), + 'display': v} + +FilterSpec.register(lambda f: bool(f.choices), ChoicesFilterSpec) + +class DateFieldFilterSpec(FilterSpec): + def __init__(self, f, request, params, model, model_admin): + super(DateFieldFilterSpec, self).__init__(f, request, params, model, model_admin) + + self.field_generic = '%s__' % self.field.name + + self.date_params = dict([(k, v) for k, v in params.items() if k.startswith(self.field_generic)]) + + today = datetime.date.today() + one_week_ago = today - datetime.timedelta(days=7) + 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'), {}), + (_('Today'), {'%s__year' % self.field.name: str(today.year), + '%s__month' % self.field.name: str(today.month), + '%s__day' % self.field.name: str(today.day)}), + (_('Past 7 days'), {'%s__gte' % self.field.name: one_week_ago.strftime('%Y-%m-%d'), + '%s__lte' % f.name: today_str}), + (_('This month'), {'%s__year' % self.field.name: str(today.year), + '%s__month' % f.name: str(today.month)}), + (_('This year'), {'%s__year' % self.field.name: str(today.year)}) + ) + + def title(self): + return self.field.verbose_name + + def choices(self, cl): + for title, param_dict in self.links: + yield {'selected': self.date_params == param_dict, + 'query_string': cl.get_query_string(param_dict, [self.field_generic]), + 'display': title} + +FilterSpec.register(lambda f: isinstance(f, models.DateField), DateFieldFilterSpec) + +class BooleanFieldFilterSpec(FilterSpec): + def __init__(self, f, request, params, model, model_admin): + super(BooleanFieldFilterSpec, self).__init__(f, request, params, model, model_admin) + self.lookup_kwarg = '%s__exact' % f.name + self.lookup_kwarg2 = '%s__isnull' % f.name + self.lookup_val = request.GET.get(self.lookup_kwarg, None) + self.lookup_val2 = request.GET.get(self.lookup_kwarg2, None) + + def title(self): + return self.field.verbose_name + + def choices(self, cl): + for k, v in ((_('All'), None), (_('Yes'), '1'), (_('No'), '0')): + 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, 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, models.BooleanField) or isinstance(f, models.NullBooleanField), BooleanFieldFilterSpec) + +# This should be registered last, because it's a last resort. For example, +# if a field is eligible to use the BooleanFieldFilterSpec, that'd be much +# more appropriate, and the AllValuesFilterSpec won't get used for it. +class AllValuesFilterSpec(FilterSpec): + def __init__(self, f, request, params, model, model_admin): + super(AllValuesFilterSpec, self).__init__(f, request, params, model, model_admin) + self.lookup_val = request.GET.get(f.name, None) + self.lookup_choices = model_admin.queryset(request).distinct().order_by(f.name).values(f.name) + + def title(self): + return self.field.verbose_name + + def choices(self, cl): + yield {'selected': self.lookup_val is None, + 'query_string': cl.get_query_string({}, [self.field.name]), + 'display': _('All')} + for val in self.lookup_choices: + val = smart_unicode(val[self.field.name]) + yield {'selected': self.lookup_val == val, + 'query_string': cl.get_query_string({self.field.name: val}), + 'display': val} +FilterSpec.register(lambda f: True, AllValuesFilterSpec) diff --git a/webapp/django/contrib/admin/helpers.py b/webapp/django/contrib/admin/helpers.py new file mode 100644 index 0000000000..ba49f8b7cf --- /dev/null +++ b/webapp/django/contrib/admin/helpers.py @@ -0,0 +1,141 @@ + +from django import forms +from django.conf import settings +from django.utils.html import escape +from django.utils.safestring import mark_safe +from django.utils.encoding import force_unicode +from django.contrib.admin.util import flatten_fieldsets + +class AdminForm(object): + def __init__(self, form, fieldsets, prepopulated_fields): + self.form, self.fieldsets = form, fieldsets + self.prepopulated_fields = [{ + 'field': form[field_name], + 'dependencies': [form[f] for f in dependencies] + } for field_name, dependencies in prepopulated_fields.items()] + + def __iter__(self): + for name, options in self.fieldsets: + yield Fieldset(self.form, name, **options) + + def first_field(self): + for bf in self.form: + return bf + + def _media(self): + media = self.form.media + for fs in self: + media = media + fs.media + return media + media = property(_media) + +class Fieldset(object): + def __init__(self, form, name=None, fields=(), classes=(), description=None): + self.form = form + self.name, self.fields = name, fields + self.classes = u' '.join(classes) + self.description = description + + def _media(self): + if 'collapse' in self.classes: + return forms.Media(js=['%sjs/admin/CollapsedFieldsets.js' % settings.ADMIN_MEDIA_PREFIX]) + return forms.Media() + media = property(_media) + + def __iter__(self): + for field in self.fields: + yield Fieldline(self.form, field) + +class Fieldline(object): + def __init__(self, form, field): + self.form = form # A django.forms.Form instance + if isinstance(field, basestring): + self.fields = [field] + else: + self.fields = field + + def __iter__(self): + for i, field in enumerate(self.fields): + yield AdminField(self.form, field, is_first=(i == 0)) + + def errors(self): + return mark_safe(u'\n'.join([self.form[f].errors.as_ul() for f in self.fields]).strip('\n')) + +class AdminField(object): + def __init__(self, form, field, is_first): + self.field = form[field] # A django.forms.BoundField instance + self.is_first = is_first # Whether this field is first on the line + self.is_checkbox = isinstance(self.field.field.widget, forms.CheckboxInput) + + def label_tag(self): + classes = [] + if self.is_checkbox: + classes.append(u'vCheckboxLabel') + contents = force_unicode(escape(self.field.label)) + else: + contents = force_unicode(escape(self.field.label)) + u':' + if self.field.field.required: + classes.append(u'required') + if not self.is_first: + classes.append(u'inline') + attrs = classes and {'class': u' '.join(classes)} or {} + return self.field.label_tag(contents=contents, attrs=attrs) + +class InlineAdminFormSet(object): + """ + A wrapper around an inline formset for use in the admin system. + """ + def __init__(self, inline, formset, fieldsets): + self.opts = inline + self.formset = formset + self.fieldsets = fieldsets + + def __iter__(self): + for form, original in zip(self.formset.initial_forms, self.formset.get_queryset()): + yield InlineAdminForm(self.formset, form, self.fieldsets, self.opts.prepopulated_fields, original) + for form in self.formset.extra_forms: + yield InlineAdminForm(self.formset, form, self.fieldsets, self.opts.prepopulated_fields, None) + + def fields(self): + for field_name in flatten_fieldsets(self.fieldsets): + yield self.formset.form.base_fields[field_name] + + def _media(self): + media = self.opts.media + self.formset.media + for fs in self: + media = media + fs.media + return media + media = property(_media) + +class InlineAdminForm(AdminForm): + """ + A wrapper around an inline form for use in the admin system. + """ + def __init__(self, formset, form, fieldsets, prepopulated_fields, original): + self.formset = formset + self.original = original + self.show_url = original and hasattr(original, 'get_absolute_url') + super(InlineAdminForm, self).__init__(form, fieldsets, prepopulated_fields) + + def pk_field(self): + return AdminField(self.form, self.formset._pk_field_name, False) + + def deletion_field(self): + from django.forms.formsets import DELETION_FIELD_NAME + return AdminField(self.form, DELETION_FIELD_NAME, False) + + def ordering_field(self): + from django.forms.formsets import ORDERING_FIELD_NAME + return AdminField(self.form, ORDERING_FIELD_NAME, False) + +class AdminErrorList(forms.util.ErrorList): + """ + Stores all errors for the form/formsets in an add/change stage view. + """ + def __init__(self, form, inline_formsets): + if form.is_bound: + self.extend(form.errors.values()) + for inline_formset in inline_formsets: + self.extend(inline_formset.non_form_errors()) + for errors_in_inline_form in inline_formset.errors: + self.extend(errors_in_inline_form.values()) diff --git a/webapp/django/contrib/admin/media/css/base.css b/webapp/django/contrib/admin/media/css/base.css new file mode 100644 index 0000000000..9760d67dc4 --- /dev/null +++ b/webapp/django/contrib/admin/media/css/base.css @@ -0,0 +1,14 @@ +/* + DJANGO Admin + by Wilson Miner wilson@lawrence.com +*/ + +/* Block IE 5 */ +@import "null.css?\"\{"; + +/* Import other styles */ +@import url('global.css'); +@import url('layout.css'); + +/* Import patch for IE 6 Windows */ +/*\*/ @import "patch-iewin.css"; /**/ diff --git a/webapp/django/contrib/admin/media/css/changelists.css b/webapp/django/contrib/admin/media/css/changelists.css new file mode 100644 index 0000000000..4834be4685 --- /dev/null +++ b/webapp/django/contrib/admin/media/css/changelists.css @@ -0,0 +1,50 @@ +@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; } +.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: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; } + +/* CHANGELIST TABLES */ +#changelist table thead th { white-space:nowrap; } +#changelist table tbody td { border-left: 1px solid #ddd; } +#changelist table tfoot { color: #666; } + +/* TOOLBAR */ +#changelist #toolbar { padding:3px; border-bottom:1px solid #ddd; background:#e1e1e1 url(../img/admin/nav-bg.gif) top left repeat-x; color:#666; } +#changelist #toolbar form input { font-size:11px; padding:1px 2px; } +#changelist #toolbar form #searchbar { padding:2px; } +#changelist #changelist-search img { vertical-align:middle; } + +/* FILTER COLUMN */ +#changelist-filter { position:absolute; top:0; right:0; z-index:1000; width:160px; border-left:1px solid #ddd; background:#efefef; margin:0; } +#changelist-filter h2 { font-size:11px; padding:2px 5px; border-bottom:1px solid #ddd; } +#changelist-filter h3 { font-size:12px; margin-bottom:0; } +#changelist-filter ul { padding-left:0;margin-left:10px; } +#changelist-filter li { list-style-type:none; margin-left:0; padding-left:0; } +#changelist-filter a { color:#999; } +#changelist-filter a:hover { color:#036; } +#changelist-filter li.selected { border-left:5px solid #ccc; padding-left:5px;margin-left:-10px; } +#changelist-filter li.selected a { color:#5b80b2 !important; } + +/* DATE DRILLDOWN */ +.change-list ul.toplinks { display:block; background:white url(../img/admin/nav-bg-reverse.gif) 0 -10px repeat-x; border-top:1px solid white; float:left; padding:0 !important; margin:0 !important; width:100%; } +.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; } diff --git a/webapp/django/contrib/admin/media/css/dashboard.css b/webapp/django/contrib/admin/media/css/dashboard.css new file mode 100644 index 0000000000..d27797324b --- /dev/null +++ b/webapp/django/contrib/admin/media/css/dashboard.css @@ -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; }
\ No newline at end of file diff --git a/webapp/django/contrib/admin/media/css/forms.css b/webapp/django/contrib/admin/media/css/forms.css new file mode 100644 index 0000000000..234a21ba79 --- /dev/null +++ b/webapp/django/contrib/admin/media/css/forms.css @@ -0,0 +1,83 @@ +@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; } +.vTextField { width:20em; } +.vIntegerField { width:5em; } + +/* x unsorted */ +.inline-group {padding:0; border:1px solid #ccc; margin:10px 0;} +.inline-group .aligned label { width: 7em; } + +.inline-related {position:relative;} +.inline-related h3 {margin: 0; color:#666; padding:3px 5px; font-size:11px; background:#e1e1e1 url(../img/admin/nav-bg.gif) top left repeat-x; border-bottom:1px solid #ddd;} +.inline-related h3 span.delete {padding-left:20px; position:absolute; top:2px; right:10px;} +.inline-related h3 span.delete label {margin-left:2px; font-size: 11px;} +.inline-related fieldset {margin: 0; background:#fff; border: none; } +.inline-related fieldset.module h3 { margin:0; padding:2px 5px 3px 5px; font-size:11px; text-align:left; font-weight:bold; background:#bcd; color:#fff; } +.inline-related.tabular fieldset.module table {width:100%;} +.last-related fieldset {border: none;} + +.inline-group .tabular tr.has_original td {padding-top:2em;} +.inline-group .tabular tr td.original { padding:2px 0 0 0; width:0; _position:relative; } +.inline-group .tabular th.original {width:0px; padding:0;} +.inline-group .tabular td.original p {position:absolute; left:0; height:1.1em; padding:2px 7px; overflow:hidden; font-size:9px; font-weight:bold; color:#666; _width:700px; } +.inline-group ul.tools {padding:0; margin: 0; list-style:none;} +.inline-group ul.tools li {display:inline; padding:0 5px;} +.inline-group ul.tools a.add {background:url(../img/admin/icon_addlink.gif) 0 50% no-repeat; padding-left:14px;}
\ No newline at end of file diff --git a/webapp/django/contrib/admin/media/css/global.css b/webapp/django/contrib/admin/media/css/global.css new file mode 100644 index 0000000000..fc2ab7319d --- /dev/null +++ b/webapp/django/contrib/admin/media/css/global.css @@ -0,0 +1,142 @@ +body { margin:0; padding:0; font-size:12px; font-family:"Lucida Grande","DejaVu Sans","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; } +a.section:link, a.section:visited { color: white; text-decoration:none; } + +/* 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; } +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; } +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; } + +/* TEXT STYLES & MODIFIERS */ +.small { font-size:11px; } +.tiny { font-size:10px; } +p.tiny { margin-top:-2px; } +.mini { font-size:9px; } +p.mini { margin-top:-3px; } +.help, p.help { font-size:10px !important; color:#999; } +p img, h1 img, h2 img, h3 img, h4 img, td img { vertical-align:middle; } +.quiet, a.quiet:link, a.quiet:visited { color:#999 !important;font-weight:normal !important; } +.quiet strong { font-weight:bold !important; } +.float-right { float:right; } +.float-left { float:left; } +.clear { clear:both; } +.align-left { text-align:left; } +.align-right { text-align:right; } +.example { margin:10px 0; padding:5px 10px; background:#efefef; } +.nowrap { white-space:nowrap; } + +/* 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; border:1px solid #bbb; border-color:#ddd #aaa #aaa #ddd; } +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; float:right; } +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, .inline-group h2 { 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; } diff --git a/webapp/django/contrib/admin/media/css/layout.css b/webapp/django/contrib/admin/media/css/layout.css new file mode 100644 index 0000000000..17c52861ee --- /dev/null +++ b/webapp/django/contrib/admin/media/css/layout.css @@ -0,0 +1,29 @@ +/* PAGE STRUCTURE */ +#container { position:relative; width:100%; min-width:760px; padding:0; } +#content { margin:10px 15px; } +#header { width:100%; } +#content-main { float:left; width:100%; } +#content-related { float:right; width:18em; position:relative; margin-right:-19em; } +#footer { clear:both; padding:10px; } + +/* COLUMN TYPES */ +.colMS { margin-right:20em !important; } +.colSM { margin-left:20em !important; } +.colSM #content-related { float:left; margin-right:0; margin-left:-19em; } +.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; }
\ No newline at end of file diff --git a/webapp/django/contrib/admin/media/css/login.css b/webapp/django/contrib/admin/media/css/login.css new file mode 100644 index 0000000000..f904957cc5 --- /dev/null +++ b/webapp/django/contrib/admin/media/css/login.css @@ -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:9em; 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:14em; } +.login span.help { font-size:10px; display:block; } +.login .submit-row { clear:both; padding:1em 0 0 9.4em; }
\ No newline at end of file diff --git a/webapp/django/contrib/admin/media/css/null.css b/webapp/django/contrib/admin/media/css/null.css new file mode 100644 index 0000000000..1a93f22058 --- /dev/null +++ b/webapp/django/contrib/admin/media/css/null.css @@ -0,0 +1 @@ +/* Nothing to see here. Dummy file to feed to the high pass filter which hides CSS from IE5/win. Details: http://tantek.com/CSS/Examples/highpass.html */
\ No newline at end of file diff --git a/webapp/django/contrib/admin/media/css/patch-iewin.css b/webapp/django/contrib/admin/media/css/patch-iewin.css new file mode 100644 index 0000000000..2de1305e44 --- /dev/null +++ b/webapp/django/contrib/admin/media/css/patch-iewin.css @@ -0,0 +1,8 @@ +* 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 #changelist-filter ul { margin-right:-10px; } /* fix right margin for changelist filters in IE6 */ +* html .change-list .filtered { height:400px; } /* IE ignores min-height, but treats height as if it were min-height */
\ No newline at end of file diff --git a/webapp/django/contrib/admin/media/css/rtl.css b/webapp/django/contrib/admin/media/css/rtl.css new file mode 100644 index 0000000000..900ddbd610 --- /dev/null +++ b/webapp/django/contrib/admin/media/css/rtl.css @@ -0,0 +1,51 @@ +body { direction: rtl; } + +/* login styles */ + +.login .form-row { float:right; } +.login .form-row label { float:right; padding-left:0.5em; padding-right:0; text-align:left;} +.login .submit-row { clear:both; padding:1em 9.4em 0 0; } + + +/* global styles */ +th { text-align: right; } +.module h2, .module caption { text-align: right; } +.addlink, .changelink { padding-left:0px; padding-right:12px; background-position:100% 0.2em; } +.deletelink { padding-left:0px; padding-right:12px; background-position:100% 0.25em; } +.object-tools { float:left; } + + +/* layout styles */ +#user-tools { right:auto; left:0; text-align:left; } +div.breadcrumbs { text-align:right; } +#content-main { float:right;} +#content-related { float:left; margin-left:-19em; margin-right:auto;} +.colMS { margin-left:20em !important; margin-right:10px !important;} + +/* dashboard styles */ +.dashboard .module table td a { padding-left:.6em; padding-right:12px; } + +/* changelists styles */ +.change-list .filtered { background:white url(../img/admin/changelist-bg_rtl.gif) top left repeat-y !important; } +.change-list .filtered table { border-left:1px solid #ddd; border-right:0px none; } +#changelist-filter { right:auto; left:0; border-left:0px none; border-right:1px solid #ddd;} +.change-list .filtered table, .change-list .filtered .paginator, .filtered #toolbar, .filtered div.xfull { margin-right:0px !important; margin-left:160px !important; } +#changelist-filter li.selected { border-left:0px none; padding-left:0px; margin-left:0; border-right:5px solid #ccc; padding-right:5px;margin-right:-10px; } + +/* form styles */ +.aligned label { padding:0 0 3px 1em; float:right; } +.submit-row { text-align: left } +.vDateField, .vTimeField { margin-left:2px; } +form ul.inline li { float:right; padding-right:0; padding-left:7px; } +input[type=submit].default, .submit-row input.default { float:left; } + +/* widget styles */ +.calendarnav-previous { top:0; left:auto; right:0; } +.calendarnav-next { top:0; right:auto; left:0;} +.calendar caption, .calendarbox h2 { text-align:center; } + +.selector { float: right;} +.selector .selector-filter { text-align: right;} + +/* x unsorted */ +.inline-related h2 { text-align:right } diff --git a/webapp/django/contrib/admin/media/css/widgets.css b/webapp/django/contrib/admin/media/css/widgets.css new file mode 100644 index 0000000000..67d9662f3e --- /dev/null +++ b/webapp/django/contrib/admin/media/css/widgets.css @@ -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:auto; } +.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; }
\ No newline at end of file diff --git a/webapp/django/contrib/admin/media/img/admin/arrow-down.gif b/webapp/django/contrib/admin/media/img/admin/arrow-down.gif Binary files differnew file mode 100644 index 0000000000..a967b9fd55 --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/arrow-down.gif diff --git a/webapp/django/contrib/admin/media/img/admin/arrow-up.gif b/webapp/django/contrib/admin/media/img/admin/arrow-up.gif Binary files differnew file mode 100644 index 0000000000..3fe4851399 --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/arrow-up.gif diff --git a/webapp/django/contrib/admin/media/img/admin/changelist-bg.gif b/webapp/django/contrib/admin/media/img/admin/changelist-bg.gif Binary files differnew file mode 100644 index 0000000000..7f4699470a --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/changelist-bg.gif diff --git a/webapp/django/contrib/admin/media/img/admin/changelist-bg_rtl.gif b/webapp/django/contrib/admin/media/img/admin/changelist-bg_rtl.gif Binary files differnew file mode 100644 index 0000000000..237971257f --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/changelist-bg_rtl.gif diff --git a/webapp/django/contrib/admin/media/img/admin/chooser-bg.gif b/webapp/django/contrib/admin/media/img/admin/chooser-bg.gif Binary files differnew file mode 100644 index 0000000000..30e83c2518 --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/chooser-bg.gif diff --git a/webapp/django/contrib/admin/media/img/admin/chooser_stacked-bg.gif b/webapp/django/contrib/admin/media/img/admin/chooser_stacked-bg.gif Binary files differnew file mode 100644 index 0000000000..5d104b6d98 --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/chooser_stacked-bg.gif diff --git a/webapp/django/contrib/admin/media/img/admin/default-bg-reverse.gif b/webapp/django/contrib/admin/media/img/admin/default-bg-reverse.gif Binary files differnew file mode 100644 index 0000000000..0873281e51 --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/default-bg-reverse.gif diff --git a/webapp/django/contrib/admin/media/img/admin/default-bg.gif b/webapp/django/contrib/admin/media/img/admin/default-bg.gif Binary files differnew file mode 100644 index 0000000000..003aeca59f --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/default-bg.gif diff --git a/webapp/django/contrib/admin/media/img/admin/deleted-overlay.gif b/webapp/django/contrib/admin/media/img/admin/deleted-overlay.gif Binary files differnew file mode 100644 index 0000000000..dc3828fe06 --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/deleted-overlay.gif diff --git a/webapp/django/contrib/admin/media/img/admin/icon-no.gif b/webapp/django/contrib/admin/media/img/admin/icon-no.gif Binary files differnew file mode 100644 index 0000000000..1b4ee58145 --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/icon-no.gif diff --git a/webapp/django/contrib/admin/media/img/admin/icon-unknown.gif b/webapp/django/contrib/admin/media/img/admin/icon-unknown.gif Binary files differnew file mode 100644 index 0000000000..cfd2b02ad9 --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/icon-unknown.gif diff --git a/webapp/django/contrib/admin/media/img/admin/icon-yes.gif b/webapp/django/contrib/admin/media/img/admin/icon-yes.gif Binary files differnew file mode 100644 index 0000000000..7399282740 --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/icon-yes.gif diff --git a/webapp/django/contrib/admin/media/img/admin/icon_addlink.gif b/webapp/django/contrib/admin/media/img/admin/icon_addlink.gif Binary files differnew file mode 100644 index 0000000000..ee70e1adba --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/icon_addlink.gif diff --git a/webapp/django/contrib/admin/media/img/admin/icon_alert.gif b/webapp/django/contrib/admin/media/img/admin/icon_alert.gif Binary files differnew file mode 100644 index 0000000000..a1dde26254 --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/icon_alert.gif diff --git a/webapp/django/contrib/admin/media/img/admin/icon_calendar.gif b/webapp/django/contrib/admin/media/img/admin/icon_calendar.gif Binary files differnew file mode 100644 index 0000000000..7587b305a4 --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/icon_calendar.gif diff --git a/webapp/django/contrib/admin/media/img/admin/icon_changelink.gif b/webapp/django/contrib/admin/media/img/admin/icon_changelink.gif Binary files differnew file mode 100644 index 0000000000..e1b9afde65 --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/icon_changelink.gif diff --git a/webapp/django/contrib/admin/media/img/admin/icon_clock.gif b/webapp/django/contrib/admin/media/img/admin/icon_clock.gif Binary files differnew file mode 100644 index 0000000000..ff2d57e0a3 --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/icon_clock.gif diff --git a/webapp/django/contrib/admin/media/img/admin/icon_deletelink.gif b/webapp/django/contrib/admin/media/img/admin/icon_deletelink.gif Binary files differnew file mode 100644 index 0000000000..72523e3a3b --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/icon_deletelink.gif diff --git a/webapp/django/contrib/admin/media/img/admin/icon_error.gif b/webapp/django/contrib/admin/media/img/admin/icon_error.gif Binary files differnew file mode 100644 index 0000000000..3730a00b26 --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/icon_error.gif diff --git a/webapp/django/contrib/admin/media/img/admin/icon_searchbox.png b/webapp/django/contrib/admin/media/img/admin/icon_searchbox.png Binary files differnew file mode 100644 index 0000000000..8ab579e526 --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/icon_searchbox.png diff --git a/webapp/django/contrib/admin/media/img/admin/icon_success.gif b/webapp/django/contrib/admin/media/img/admin/icon_success.gif Binary files differnew file mode 100644 index 0000000000..5cf90a15aa --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/icon_success.gif diff --git a/webapp/django/contrib/admin/media/img/admin/inline-delete-8bit.png b/webapp/django/contrib/admin/media/img/admin/inline-delete-8bit.png Binary files differnew file mode 100644 index 0000000000..95caf59a8d --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/inline-delete-8bit.png diff --git a/webapp/django/contrib/admin/media/img/admin/inline-delete.png b/webapp/django/contrib/admin/media/img/admin/inline-delete.png Binary files differnew file mode 100644 index 0000000000..d59bcd2444 --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/inline-delete.png diff --git a/webapp/django/contrib/admin/media/img/admin/inline-restore-8bit.png b/webapp/django/contrib/admin/media/img/admin/inline-restore-8bit.png Binary files differnew file mode 100644 index 0000000000..e087c8ead3 --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/inline-restore-8bit.png diff --git a/webapp/django/contrib/admin/media/img/admin/inline-restore.png b/webapp/django/contrib/admin/media/img/admin/inline-restore.png Binary files differnew file mode 100644 index 0000000000..efdd92ac39 --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/inline-restore.png diff --git a/webapp/django/contrib/admin/media/img/admin/inline-splitter-bg.gif b/webapp/django/contrib/admin/media/img/admin/inline-splitter-bg.gif Binary files differnew file mode 100644 index 0000000000..32ac5b3498 --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/inline-splitter-bg.gif diff --git a/webapp/django/contrib/admin/media/img/admin/nav-bg-grabber.gif b/webapp/django/contrib/admin/media/img/admin/nav-bg-grabber.gif Binary files differnew file mode 100644 index 0000000000..0a784fa769 --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/nav-bg-grabber.gif diff --git a/webapp/django/contrib/admin/media/img/admin/nav-bg-reverse.gif b/webapp/django/contrib/admin/media/img/admin/nav-bg-reverse.gif Binary files differnew file mode 100644 index 0000000000..f11029f90f --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/nav-bg-reverse.gif diff --git a/webapp/django/contrib/admin/media/img/admin/nav-bg.gif b/webapp/django/contrib/admin/media/img/admin/nav-bg.gif Binary files differnew file mode 100644 index 0000000000..f8402b809d --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/nav-bg.gif diff --git a/webapp/django/contrib/admin/media/img/admin/selector-add.gif b/webapp/django/contrib/admin/media/img/admin/selector-add.gif Binary files differnew file mode 100644 index 0000000000..50132d1c43 --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/selector-add.gif diff --git a/webapp/django/contrib/admin/media/img/admin/selector-addall.gif b/webapp/django/contrib/admin/media/img/admin/selector-addall.gif Binary files differnew file mode 100644 index 0000000000..d6e7c639bb --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/selector-addall.gif diff --git a/webapp/django/contrib/admin/media/img/admin/selector-remove.gif b/webapp/django/contrib/admin/media/img/admin/selector-remove.gif Binary files differnew file mode 100644 index 0000000000..2b9b0a2ac3 --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/selector-remove.gif diff --git a/webapp/django/contrib/admin/media/img/admin/selector-removeall.gif b/webapp/django/contrib/admin/media/img/admin/selector-removeall.gif Binary files differnew file mode 100644 index 0000000000..5a4421926d --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/selector-removeall.gif diff --git a/webapp/django/contrib/admin/media/img/admin/selector-search.gif b/webapp/django/contrib/admin/media/img/admin/selector-search.gif Binary files differnew file mode 100644 index 0000000000..6d5f4c7492 --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/selector-search.gif diff --git a/webapp/django/contrib/admin/media/img/admin/selector_stacked-add.gif b/webapp/django/contrib/admin/media/img/admin/selector_stacked-add.gif Binary files differnew file mode 100644 index 0000000000..7426169652 --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/selector_stacked-add.gif diff --git a/webapp/django/contrib/admin/media/img/admin/selector_stacked-remove.gif b/webapp/django/contrib/admin/media/img/admin/selector_stacked-remove.gif Binary files differnew file mode 100644 index 0000000000..60412cee19 --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/selector_stacked-remove.gif diff --git a/webapp/django/contrib/admin/media/img/admin/tool-left.gif b/webapp/django/contrib/admin/media/img/admin/tool-left.gif Binary files differnew file mode 100644 index 0000000000..011490ff3a --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/tool-left.gif diff --git a/webapp/django/contrib/admin/media/img/admin/tool-left_over.gif b/webapp/django/contrib/admin/media/img/admin/tool-left_over.gif Binary files differnew file mode 100644 index 0000000000..937e07bb1a --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/tool-left_over.gif diff --git a/webapp/django/contrib/admin/media/img/admin/tool-right.gif b/webapp/django/contrib/admin/media/img/admin/tool-right.gif Binary files differnew file mode 100644 index 0000000000..cdc140cc59 --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/tool-right.gif diff --git a/webapp/django/contrib/admin/media/img/admin/tool-right_over.gif b/webapp/django/contrib/admin/media/img/admin/tool-right_over.gif Binary files differnew file mode 100644 index 0000000000..4db977e838 --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/tool-right_over.gif diff --git a/webapp/django/contrib/admin/media/img/admin/tooltag-add.gif b/webapp/django/contrib/admin/media/img/admin/tooltag-add.gif Binary files differnew file mode 100644 index 0000000000..8b53d49ae5 --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/tooltag-add.gif diff --git a/webapp/django/contrib/admin/media/img/admin/tooltag-add_over.gif b/webapp/django/contrib/admin/media/img/admin/tooltag-add_over.gif Binary files differnew file mode 100644 index 0000000000..bfc52f10de --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/tooltag-add_over.gif diff --git a/webapp/django/contrib/admin/media/img/admin/tooltag-arrowright.gif b/webapp/django/contrib/admin/media/img/admin/tooltag-arrowright.gif Binary files differnew file mode 100644 index 0000000000..cdaaae77ed --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/tooltag-arrowright.gif diff --git a/webapp/django/contrib/admin/media/img/admin/tooltag-arrowright_over.gif b/webapp/django/contrib/admin/media/img/admin/tooltag-arrowright_over.gif Binary files differnew file mode 100644 index 0000000000..7163189604 --- /dev/null +++ b/webapp/django/contrib/admin/media/img/admin/tooltag-arrowright_over.gif diff --git a/webapp/django/contrib/admin/media/img/gis/move_vertex_off.png b/webapp/django/contrib/admin/media/img/gis/move_vertex_off.png Binary files differnew file mode 100644 index 0000000000..296b2e29c9 --- /dev/null +++ b/webapp/django/contrib/admin/media/img/gis/move_vertex_off.png diff --git a/webapp/django/contrib/admin/media/img/gis/move_vertex_on.png b/webapp/django/contrib/admin/media/img/gis/move_vertex_on.png Binary files differnew file mode 100644 index 0000000000..21f4758d9c --- /dev/null +++ b/webapp/django/contrib/admin/media/img/gis/move_vertex_on.png diff --git a/webapp/django/contrib/admin/media/js/SelectBox.js b/webapp/django/contrib/admin/media/js/SelectBox.js new file mode 100644 index 0000000000..f28c861513 --- /dev/null +++ b/webapp/django/contrib/admin/media/js/SelectBox.js @@ -0,0 +1,111 @@ +var SelectBox = { + cache: new Object(), + init: function(id) { + var box = document.getElementById(id); + var node; + SelectBox.cache[id] = new Array(); + var cache = SelectBox.cache[id]; + for (var i = 0; (node = box.options[i]); i++) { + cache.push({value: node.value, text: node.text, displayed: 1}); + } + }, + redisplay: function(id) { + // Repopulate HTML select box from cache + var box = document.getElementById(id); + box.options.length = 0; // clear all options + for (var i = 0, j = SelectBox.cache[id].length; i < j; i++) { + var node = SelectBox.cache[id][i]; + if (node.displayed) { + box.options[box.options.length] = new Option(node.text, node.value, false, false); + } + } + }, + filter: function(id, text) { + // Redisplay the HTML select box, displaying only the choices containing ALL + // the words in text. (It's an AND search.) + var tokens = text.toLowerCase().split(/\s+/); + var node, token; + for (var i = 0; (node = SelectBox.cache[id][i]); i++) { + node.displayed = 1; + for (var j = 0; (token = tokens[j]); j++) { + if (node.text.toLowerCase().indexOf(token) == -1) { + node.displayed = 0; + } + } + } + SelectBox.redisplay(id); + }, + delete_from_cache: function(id, value) { + var node, delete_index = null; + for (var i = 0; (node = SelectBox.cache[id][i]); i++) { + if (node.value == value) { + delete_index = i; + break; + } + } + var j = SelectBox.cache[id].length - 1; + for (var i = delete_index; i < j; i++) { + SelectBox.cache[id][i] = SelectBox.cache[id][i+1]; + } + SelectBox.cache[id].length--; + }, + add_to_cache: function(id, option) { + SelectBox.cache[id].push({value: option.value, text: option.text, displayed: 1}); + }, + cache_contains: function(id, value) { + // Check if an item is contained in the cache + var node; + for (var i = 0; (node = SelectBox.cache[id][i]); i++) { + if (node.value == value) { + return true; + } + } + return false; + }, + move: function(from, to) { + var from_box = document.getElementById(from); + var to_box = document.getElementById(to); + var option; + for (var i = 0; (option = from_box.options[i]); i++) { + if (option.selected && SelectBox.cache_contains(from, option.value)) { + SelectBox.add_to_cache(to, {value: option.value, text: option.text, displayed: 1}); + SelectBox.delete_from_cache(from, option.value); + } + } + SelectBox.redisplay(from); + SelectBox.redisplay(to); + }, + move_all: function(from, to) { + var from_box = document.getElementById(from); + var to_box = document.getElementById(to); + var option; + for (var i = 0; (option = from_box.options[i]); i++) { + if (SelectBox.cache_contains(from, option.value)) { + SelectBox.add_to_cache(to, {value: option.value, text: option.text, displayed: 1}); + SelectBox.delete_from_cache(from, option.value); + } + } + SelectBox.redisplay(from); + SelectBox.redisplay(to); + }, + sort: function(id) { + SelectBox.cache[id].sort( function(a, b) { + a = a.text.toLowerCase(); + b = b.text.toLowerCase(); + try { + if (a > b) return 1; + if (a < b) return -1; + } + catch (e) { + // silently fail on IE 'unknown' exception + } + return 0; + } ); + }, + select_all: function(id) { + var box = document.getElementById(id); + for (var i = 0; i < box.options.length; i++) { + box.options[i].selected = 'selected'; + } + } +} diff --git a/webapp/django/contrib/admin/media/js/SelectFilter2.js b/webapp/django/contrib/admin/media/js/SelectFilter2.js new file mode 100644 index 0000000000..db946a6f6f --- /dev/null +++ b/webapp/django/contrib/admin/media/js/SelectFilter2.js @@ -0,0 +1,113 @@ +/* +SelectFilter2 - Turns a multiple-select box into a filter interface. + +Different than SelectFilter because this is coupled to the admin framework. + +Requires core.js, SelectBox.js and addevent.js. +*/ + +function findForm(node) { + // returns the node of the form containing the given node + if (node.tagName.toLowerCase() != 'form') { + return findForm(node.parentNode); + } + return node; +} + +var SelectFilter = { + init: function(field_id, field_name, is_stacked, admin_media_prefix) { + var from_box = document.getElementById(field_id); + from_box.id += '_from'; // change its ID + from_box.className = 'filtered'; + + // Remove <p class="info">, because it just gets in the way. + var ps = from_box.parentNode.getElementsByTagName('p'); + for (var i=0; i<ps.length; i++) { + from_box.parentNode.removeChild(ps[i]); + } + + // <div class="selector"> or <div class="selector stacked"> + var selector_div = quickElement('div', from_box.parentNode); + selector_div.className = is_stacked ? 'selector stacked' : 'selector'; + + // <div class="selector-available"> + var selector_available = quickElement('div', selector_div, ''); + selector_available.className = 'selector-available'; + quickElement('h2', selector_available, interpolate(gettext('Available %s'), [field_name])); + var filter_p = quickElement('p', selector_available, ''); + filter_p.className = 'selector-filter'; + quickElement('img', filter_p, '', 'src', admin_media_prefix + 'img/admin/selector-search.gif'); + filter_p.appendChild(document.createTextNode(' ')); + var filter_input = quickElement('input', filter_p, '', 'type', 'text'); + filter_input.id = field_id + '_input'; + selector_available.appendChild(from_box); + var choose_all = quickElement('a', selector_available, gettext('Choose all'), 'href', 'javascript: (function(){ SelectBox.move_all("' + field_id + '_from", "' + field_id + '_to"); })()'); + choose_all.className = 'selector-chooseall'; + + // <ul class="selector-chooser"> + var selector_chooser = quickElement('ul', selector_div, ''); + selector_chooser.className = 'selector-chooser'; + var add_link = quickElement('a', quickElement('li', selector_chooser, ''), gettext('Add'), 'href', 'javascript: (function(){ SelectBox.move("' + field_id + '_from","' + field_id + '_to");})()'); + add_link.className = 'selector-add'; + var remove_link = quickElement('a', quickElement('li', selector_chooser, ''), gettext('Remove'), 'href', 'javascript: (function(){ SelectBox.move("' + field_id + '_to","' + field_id + '_from");})()'); + remove_link.className = 'selector-remove'; + + // <div class="selector-chosen"> + var selector_chosen = quickElement('div', selector_div, ''); + selector_chosen.className = 'selector-chosen'; + quickElement('h2', selector_chosen, interpolate(gettext('Chosen %s'), [field_name])); + var selector_filter = quickElement('p', selector_chosen, gettext('Select your choice(s) and click ')); + selector_filter.className = 'selector-filter'; + quickElement('img', selector_filter, '', 'src', admin_media_prefix + (is_stacked ? 'img/admin/selector_stacked-add.gif':'img/admin/selector-add.gif'), 'alt', 'Add'); + var to_box = quickElement('select', selector_chosen, '', 'id', field_id + '_to', 'multiple', 'multiple', 'size', from_box.size, 'name', from_box.getAttribute('name')); + to_box.className = 'filtered'; + var clear_all = quickElement('a', selector_chosen, gettext('Clear all'), 'href', 'javascript: (function() { SelectBox.move_all("' + field_id + '_to", "' + field_id + '_from");})()'); + clear_all.className = 'selector-clearall'; + + from_box.setAttribute('name', from_box.getAttribute('name') + '_old'); + + // Set up the JavaScript event handlers for the select box filter interface + addEvent(filter_input, 'keyup', function(e) { SelectFilter.filter_key_up(e, field_id); }); + addEvent(filter_input, 'keydown', function(e) { SelectFilter.filter_key_down(e, field_id); }); + addEvent(from_box, 'dblclick', function() { SelectBox.move(field_id + '_from', field_id + '_to'); }); + addEvent(to_box, 'dblclick', function() { SelectBox.move(field_id + '_to', field_id + '_from'); }); + addEvent(findForm(from_box), 'submit', function() { SelectBox.select_all(field_id + '_to'); }); + SelectBox.init(field_id + '_from'); + SelectBox.init(field_id + '_to'); + // Move selected from_box options to to_box + SelectBox.move(field_id + '_from', field_id + '_to'); + }, + filter_key_up: function(event, field_id) { + from = document.getElementById(field_id + '_from'); + // don't submit form if user pressed Enter + if ((event.which && event.which == 13) || (event.keyCode && event.keyCode == 13)) { + from.selectedIndex = 0; + SelectBox.move(field_id + '_from', field_id + '_to'); + from.selectedIndex = 0; + return false; + } + var temp = from.selectedIndex; + SelectBox.filter(field_id + '_from', document.getElementById(field_id + '_input').value); + from.selectedIndex = temp; + return true; + }, + filter_key_down: function(event, field_id) { + from = document.getElementById(field_id + '_from'); + // right arrow -- move across + if ((event.which && event.which == 39) || (event.keyCode && event.keyCode == 39)) { + var old_index = from.selectedIndex; + SelectBox.move(field_id + '_from', field_id + '_to'); + from.selectedIndex = (old_index == from.length) ? from.length - 1 : old_index; + return false; + } + // down arrow -- wrap around + if ((event.which && event.which == 40) || (event.keyCode && event.keyCode == 40)) { + from.selectedIndex = (from.length == from.selectedIndex + 1) ? 0 : from.selectedIndex + 1; + } + // up arrow -- wrap around + if ((event.which && event.which == 38) || (event.keyCode && event.keyCode == 38)) { + from.selectedIndex = (from.selectedIndex == 0) ? from.length - 1 : from.selectedIndex - 1; + } + return true; + } +} diff --git a/webapp/django/contrib/admin/media/js/admin/CollapsedFieldsets.js b/webapp/django/contrib/admin/media/js/admin/CollapsedFieldsets.js new file mode 100644 index 0000000000..d66bec0d97 --- /dev/null +++ b/webapp/django/contrib/admin/media/js/admin/CollapsedFieldsets.js @@ -0,0 +1,85 @@ +// Finds all fieldsets with class="collapse", collapses them, and gives each +// one a "Show" link that uncollapses it. The "Show" link becomes a "Hide" +// link when the fieldset is visible. + +function findForm(node) { + // returns the node of the form containing the given node + if (node.tagName.toLowerCase() != 'form') { + return findForm(node.parentNode); + } + return node; +} + +var CollapsedFieldsets = { + collapse_re: /\bcollapse\b/, // Class of fieldsets that should be dealt with. + collapsed_re: /\bcollapsed\b/, // Class that fieldsets get when they're hidden. + collapsed_class: 'collapsed', + init: function() { + var fieldsets = document.getElementsByTagName('fieldset'); + var collapsed_seen = false; + for (var i = 0, fs; fs = fieldsets[i]; i++) { + // Collapse this fieldset if it has the correct class, and if it + // doesn't have any errors. (Collapsing shouldn't apply in the case + // of error messages.) + if (fs.className.match(CollapsedFieldsets.collapse_re) && !CollapsedFieldsets.fieldset_has_errors(fs)) { + collapsed_seen = true; + // Give it an additional class, used by CSS to hide it. + fs.className += ' ' + CollapsedFieldsets.collapsed_class; + // (<a id="fieldsetcollapser3" class="collapse-toggle" href="#">Show</a>) + var collapse_link = document.createElement('a'); + collapse_link.className = 'collapse-toggle'; + collapse_link.id = 'fieldsetcollapser' + i; + collapse_link.onclick = new Function('CollapsedFieldsets.show('+i+'); return false;'); + collapse_link.href = '#'; + collapse_link.innerHTML = gettext('Show'); + var h2 = fs.getElementsByTagName('h2')[0]; + h2.appendChild(document.createTextNode(' (')); + h2.appendChild(collapse_link); + h2.appendChild(document.createTextNode(')')); + } + } + if (collapsed_seen) { + // Expand all collapsed fieldsets when form is submitted. + addEvent(findForm(document.getElementsByTagName('fieldset')[0]), 'submit', function() { CollapsedFieldsets.uncollapse_all(); }); + } + }, + fieldset_has_errors: function(fs) { + // Returns true if any fields in the fieldset have validation errors. + var divs = fs.getElementsByTagName('div'); + for (var i=0; i<divs.length; i++) { + if (divs[i].className.match(/\berrors\b/)) { + return true; + } + } + return false; + }, + show: function(fieldset_index) { + var fs = document.getElementsByTagName('fieldset')[fieldset_index]; + // Remove the class name that causes the "display: none". + fs.className = fs.className.replace(CollapsedFieldsets.collapsed_re, ''); + // Toggle the "Show" link to a "Hide" link + var collapse_link = document.getElementById('fieldsetcollapser' + fieldset_index); + collapse_link.onclick = new Function('CollapsedFieldsets.hide('+fieldset_index+'); return false;'); + collapse_link.innerHTML = gettext('Hide'); + }, + hide: function(fieldset_index) { + var fs = document.getElementsByTagName('fieldset')[fieldset_index]; + // Add the class name that causes the "display: none". + fs.className += ' ' + CollapsedFieldsets.collapsed_class; + // Toggle the "Hide" link to a "Show" link + var collapse_link = document.getElementById('fieldsetcollapser' + fieldset_index); + collapse_link.onclick = new Function('CollapsedFieldsets.show('+fieldset_index+'); return false;'); + collapse_link.innerHTML = gettext('Show'); + }, + + uncollapse_all: function() { + var fieldsets = document.getElementsByTagName('fieldset'); + for (var i=0; i<fieldsets.length; i++) { + if (fieldsets[i].className.match(CollapsedFieldsets.collapsed_re)) { + CollapsedFieldsets.show(i); + } + } + } +} + +addEvent(window, 'load', CollapsedFieldsets.init); diff --git a/webapp/django/contrib/admin/media/js/admin/DateTimeShortcuts.js b/webapp/django/contrib/admin/media/js/admin/DateTimeShortcuts.js new file mode 100644 index 0000000000..f57f97e7ee --- /dev/null +++ b/webapp/django/contrib/admin/media/js/admin/DateTimeShortcuts.js @@ -0,0 +1,254 @@ +// Inserts shortcut buttons after all of the following: +// <input type="text" class="vDateField"> +// <input type="text" class="vTimeField"> + +var DateTimeShortcuts = { + calendars: [], + calendarInputs: [], + clockInputs: [], + calendarDivName1: 'calendarbox', // name of calendar <div> that gets toggled + calendarDivName2: 'calendarin', // name of <div> that contains calendar + calendarLinkName: 'calendarlink',// name of the link that is used to toggle + clockDivName: 'clockbox', // name of clock <div> that gets toggled + clockLinkName: 'clocklink', // name of the link that is used to toggle + admin_media_prefix: '', + init: function() { + // Deduce admin_media_prefix by looking at the <script>s in the + // current document and finding the URL of *this* module. + var scripts = document.getElementsByTagName('script'); + for (var i=0; i<scripts.length; i++) { + if (scripts[i].src.match(/DateTimeShortcuts/)) { + var idx = scripts[i].src.indexOf('js/admin/DateTimeShortcuts'); + DateTimeShortcuts.admin_media_prefix = scripts[i].src.substring(0, idx); + break; + } + } + + var inputs = document.getElementsByTagName('input'); + for (i=0; i<inputs.length; i++) { + var inp = inputs[i]; + if (inp.getAttribute('type') == 'text' && inp.className.match(/vTimeField/)) { + DateTimeShortcuts.addClock(inp); + } + else if (inp.getAttribute('type') == 'text' && inp.className.match(/vDateField/)) { + DateTimeShortcuts.addCalendar(inp); + } + } + }, + // Add clock widget to a given field + addClock: function(inp) { + var num = DateTimeShortcuts.clockInputs.length; + DateTimeShortcuts.clockInputs[num] = inp; + + // Shortcut links (clock icon and "Now" link) + var shortcuts_span = document.createElement('span'); + inp.parentNode.insertBefore(shortcuts_span, inp.nextSibling); + var now_link = document.createElement('a'); + now_link.setAttribute('href', "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date().getHourMinuteSecond());"); + now_link.appendChild(document.createTextNode(gettext('Now'))); + var clock_link = document.createElement('a'); + clock_link.setAttribute('href', 'javascript:DateTimeShortcuts.openClock(' + num + ');'); + clock_link.id = DateTimeShortcuts.clockLinkName + num; + quickElement('img', clock_link, '', 'src', DateTimeShortcuts.admin_media_prefix + 'img/admin/icon_clock.gif', 'alt', gettext('Clock')); + shortcuts_span.appendChild(document.createTextNode('\240')); + shortcuts_span.appendChild(now_link); + shortcuts_span.appendChild(document.createTextNode('\240|\240')); + shortcuts_span.appendChild(clock_link); + + // Create clock link div + // + // Markup looks like: + // <div id="clockbox1" class="clockbox module"> + // <h2>Choose a time</h2> + // <ul class="timelist"> + // <li><a href="#">Now</a></li> + // <li><a href="#">Midnight</a></li> + // <li><a href="#">6 a.m.</a></li> + // <li><a href="#">Noon</a></li> + // </ul> + // <p class="calendar-cancel"><a href="#">Cancel</a></p> + // </div> + + var clock_box = document.createElement('div'); + clock_box.style.display = 'none'; + clock_box.style.position = 'absolute'; + clock_box.className = 'clockbox module'; + clock_box.setAttribute('id', DateTimeShortcuts.clockDivName + num); + document.body.appendChild(clock_box); + addEvent(clock_box, 'click', DateTimeShortcuts.cancelEventPropagation); + + quickElement('h2', clock_box, gettext('Choose a time')); + time_list = quickElement('ul', clock_box, ''); + time_list.className = 'timelist'; + quickElement("a", quickElement("li", time_list, ""), gettext("Now"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date().getHourMinuteSecond());") + quickElement("a", quickElement("li", time_list, ""), gettext("Midnight"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", '00:00:00');") + quickElement("a", quickElement("li", time_list, ""), gettext("6 a.m."), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", '06:00:00');") + quickElement("a", quickElement("li", time_list, ""), gettext("Noon"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", '12:00:00');") + + cancel_p = quickElement('p', clock_box, ''); + cancel_p.className = 'calendar-cancel'; + quickElement('a', cancel_p, gettext('Cancel'), 'href', 'javascript:DateTimeShortcuts.dismissClock(' + num + ');'); + }, + openClock: function(num) { + var clock_box = document.getElementById(DateTimeShortcuts.clockDivName+num) + var clock_link = document.getElementById(DateTimeShortcuts.clockLinkName+num) + + // Recalculate the clockbox position + // is it left-to-right or right-to-left layout ? + if (getStyle(document.body,'direction')!='rtl') { + clock_box.style.left = findPosX(clock_link) + 17 + 'px'; + } + else { + // since style's width is in em, it'd be tough to calculate + // px value of it. let's use an estimated px for now + // TODO: IE returns wrong value for findPosX when in rtl mode + // (it returns as it was left aligned), needs to be fixed. + clock_box.style.left = findPosX(clock_link) - 110 + 'px'; + } + clock_box.style.top = findPosY(clock_link) - 30 + 'px'; + + // Show the clock box + clock_box.style.display = 'block'; + addEvent(window, 'click', function() { DateTimeShortcuts.dismissClock(num); return true; }); + }, + dismissClock: function(num) { + document.getElementById(DateTimeShortcuts.clockDivName + num).style.display = 'none'; + window.onclick = null; + }, + handleClockQuicklink: function(num, val) { + DateTimeShortcuts.clockInputs[num].value = val; + DateTimeShortcuts.dismissClock(num); + }, + // Add calendar widget to a given field. + addCalendar: function(inp) { + var num = DateTimeShortcuts.calendars.length; + + DateTimeShortcuts.calendarInputs[num] = inp; + + // Shortcut links (calendar icon and "Today" link) + var shortcuts_span = document.createElement('span'); + inp.parentNode.insertBefore(shortcuts_span, inp.nextSibling); + var today_link = document.createElement('a'); + today_link.setAttribute('href', 'javascript:DateTimeShortcuts.handleCalendarQuickLink(' + num + ', 0);'); + today_link.appendChild(document.createTextNode(gettext('Today'))); + var cal_link = document.createElement('a'); + cal_link.setAttribute('href', 'javascript:DateTimeShortcuts.openCalendar(' + num + ');'); + cal_link.id = DateTimeShortcuts.calendarLinkName + num; + quickElement('img', cal_link, '', 'src', DateTimeShortcuts.admin_media_prefix + 'img/admin/icon_calendar.gif', 'alt', gettext('Calendar')); + shortcuts_span.appendChild(document.createTextNode('\240')); + shortcuts_span.appendChild(today_link); + shortcuts_span.appendChild(document.createTextNode('\240|\240')); + shortcuts_span.appendChild(cal_link); + + // Create calendarbox div. + // + // Markup looks like: + // + // <div id="calendarbox3" class="calendarbox module"> + // <h2> + // <a href="#" class="link-previous">‹</a> + // <a href="#" class="link-next">›</a> February 2003 + // </h2> + // <div class="calendar" id="calendarin3"> + // <!-- (cal) --> + // </div> + // <div class="calendar-shortcuts"> + // <a href="#">Yesterday</a> | <a href="#">Today</a> | <a href="#">Tomorrow</a> + // </div> + // <p class="calendar-cancel"><a href="#">Cancel</a></p> + // </div> + var cal_box = document.createElement('div'); + cal_box.style.display = 'none'; + cal_box.style.position = 'absolute'; + cal_box.className = 'calendarbox module'; + cal_box.setAttribute('id', DateTimeShortcuts.calendarDivName1 + num); + document.body.appendChild(cal_box); + addEvent(cal_box, 'click', DateTimeShortcuts.cancelEventPropagation); + + // next-prev links + var cal_nav = quickElement('div', cal_box, ''); + var cal_nav_prev = quickElement('a', cal_nav, '<', 'href', 'javascript:DateTimeShortcuts.drawPrev('+num+');'); + cal_nav_prev.className = 'calendarnav-previous'; + var cal_nav_next = quickElement('a', cal_nav, '>', 'href', 'javascript:DateTimeShortcuts.drawNext('+num+');'); + cal_nav_next.className = 'calendarnav-next'; + + // main box + var cal_main = quickElement('div', cal_box, '', 'id', DateTimeShortcuts.calendarDivName2 + num); + cal_main.className = 'calendar'; + DateTimeShortcuts.calendars[num] = new Calendar(DateTimeShortcuts.calendarDivName2 + num, DateTimeShortcuts.handleCalendarCallback(num)); + DateTimeShortcuts.calendars[num].drawCurrent(); + + // calendar shortcuts + var shortcuts = quickElement('div', cal_box, ''); + shortcuts.className = 'calendar-shortcuts'; + quickElement('a', shortcuts, gettext('Yesterday'), 'href', 'javascript:DateTimeShortcuts.handleCalendarQuickLink(' + num + ', -1);'); + shortcuts.appendChild(document.createTextNode('\240|\240')); + quickElement('a', shortcuts, gettext('Today'), 'href', 'javascript:DateTimeShortcuts.handleCalendarQuickLink(' + num + ', 0);'); + shortcuts.appendChild(document.createTextNode('\240|\240')); + quickElement('a', shortcuts, gettext('Tomorrow'), 'href', 'javascript:DateTimeShortcuts.handleCalendarQuickLink(' + num + ', +1);'); + + // cancel bar + var cancel_p = quickElement('p', cal_box, ''); + cancel_p.className = 'calendar-cancel'; + quickElement('a', cancel_p, gettext('Cancel'), 'href', 'javascript:DateTimeShortcuts.dismissCalendar(' + num + ');'); + }, + openCalendar: function(num) { + var cal_box = document.getElementById(DateTimeShortcuts.calendarDivName1+num) + var cal_link = document.getElementById(DateTimeShortcuts.calendarLinkName+num) + var inp = DateTimeShortcuts.calendarInputs[num]; + + // Determine if the current value in the input has a valid date. + // If so, draw the calendar with that date's year and month. + if (inp.value) { + var date_parts = inp.value.split('-'); + var year = date_parts[0]; + var month = parseFloat(date_parts[1]); + if (year.match(/\d\d\d\d/) && month >= 1 && month <= 12) { + DateTimeShortcuts.calendars[num].drawDate(month, year); + } + } + + + // Recalculate the clockbox position + // is it left-to-right or right-to-left layout ? + if (getStyle(document.body,'direction')!='rtl') { + cal_box.style.left = findPosX(cal_link) + 17 + 'px'; + } + else { + // since style's width is in em, it'd be tough to calculate + // px value of it. let's use an estimated px for now + // TODO: IE returns wrong value for findPosX when in rtl mode + // (it returns as it was left aligned), needs to be fixed. + cal_box.style.left = findPosX(cal_link) - 180 + 'px'; + } + cal_box.style.top = findPosY(cal_link) - 75 + 'px'; + + cal_box.style.display = 'block'; + addEvent(window, 'click', function() { DateTimeShortcuts.dismissCalendar(num); return true; }); + }, + dismissCalendar: function(num) { + document.getElementById(DateTimeShortcuts.calendarDivName1+num).style.display = 'none'; + }, + drawPrev: function(num) { + DateTimeShortcuts.calendars[num].drawPreviousMonth(); + }, + drawNext: function(num) { + DateTimeShortcuts.calendars[num].drawNextMonth(); + }, + handleCalendarCallback: function(num) { + return "function(y, m, d) { DateTimeShortcuts.calendarInputs["+num+"].value = y+'-'+(m<10?'0':'')+m+'-'+(d<10?'0':'')+d; document.getElementById(DateTimeShortcuts.calendarDivName1+"+num+").style.display='none';}"; + }, + handleCalendarQuickLink: function(num, offset) { + var d = new Date(); + d.setDate(d.getDate() + offset) + DateTimeShortcuts.calendarInputs[num].value = d.getISODate(); + DateTimeShortcuts.dismissCalendar(num); + }, + cancelEventPropagation: function(e) { + if (!e) e = window.event; + e.cancelBubble = true; + if (e.stopPropagation) e.stopPropagation(); + } +} + +addEvent(window, 'load', DateTimeShortcuts.init); diff --git a/webapp/django/contrib/admin/media/js/admin/RelatedObjectLookups.js b/webapp/django/contrib/admin/media/js/admin/RelatedObjectLookups.js new file mode 100644 index 0000000000..ca578cc28a --- /dev/null +++ b/webapp/django/contrib/admin/media/js/admin/RelatedObjectLookups.js @@ -0,0 +1,77 @@ +// Handles related-objects functionality: lookup link for raw_id_fields +// and Add Another links. + +function html_unescape(text) { + // Unescape a string that was escaped using django.utils.html.escape. + text = text.replace(/</g, '<'); + text = text.replace(/>/g, '>'); + text = text.replace(/"/g, '"'); + text = text.replace(/'/g, "'"); + text = text.replace(/&/g, '&'); + return text; +} + +function showRelatedObjectLookupPopup(triggeringLink) { + var name = triggeringLink.id.replace(/^lookup_/, ''); + // IE doesn't like periods in the window name, so convert temporarily. + name = name.replace(/\./g, '___'); + var href; + if (triggeringLink.href.search(/\?/) >= 0) { + href = triggeringLink.href + '&pop=1'; + } else { + href = triggeringLink.href + '?pop=1'; + } + var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes'); + win.focus(); + return false; +} + +function dismissRelatedLookupPopup(win, chosenId) { + var name = win.name.replace(/___/g, '.'); + var elem = document.getElementById(name); + if (elem.className.indexOf('vManyToManyRawIdAdminField') != -1 && elem.value) { + elem.value += ',' + chosenId; + } else { + document.getElementById(name).value = chosenId; + } + win.close(); +} + +function showAddAnotherPopup(triggeringLink) { + var name = triggeringLink.id.replace(/^add_/, ''); + name = name.replace(/\./g, '___'); + href = triggeringLink.href + if (href.indexOf('?') == -1) { + href += '?_popup=1'; + } else { + href += '&_popup=1'; + } + var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes'); + win.focus(); + return false; +} + +function dismissAddAnotherPopup(win, newId, newRepr) { + // newId and newRepr are expected to have previously been escaped by + // django.utils.html.escape. + newId = html_unescape(newId); + newRepr = html_unescape(newRepr); + var name = win.name.replace(/___/g, '.'); + var elem = document.getElementById(name); + if (elem) { + if (elem.nodeName == 'SELECT') { + var o = new Option(newRepr, newId); + elem.options[elem.options.length] = o; + o.selected = true; + } else if (elem.nodeName == 'INPUT') { + elem.value = newId; + } + } else { + var toId = name + "_to"; + elem = document.getElementById(toId); + var o = new Option(newRepr, newId); + SelectBox.add_to_cache(toId, o); + SelectBox.redisplay(toId); + } + win.close(); +} diff --git a/webapp/django/contrib/admin/media/js/admin/ordering.js b/webapp/django/contrib/admin/media/js/admin/ordering.js new file mode 100644 index 0000000000..53c42f3609 --- /dev/null +++ b/webapp/django/contrib/admin/media/js/admin/ordering.js @@ -0,0 +1,137 @@ +addEvent(window, 'load', reorder_init); + +var lis; +var top = 0; +var left = 0; +var height = 30; + +function reorder_init() { + lis = document.getElementsBySelector('ul#orderthese li'); + var input = document.getElementsBySelector('input[name=order_]')[0]; + setOrder(input.value.split(',')); + input.disabled = true; + draw(); + // Now initialise the dragging behaviour + var limit = (lis.length - 1) * height; + for (var i = 0; i < lis.length; i++) { + var li = lis[i]; + var img = document.getElementById('handle'+li.id); + li.style.zIndex = 1; + Drag.init(img, li, left + 10, left + 10, top + 10, top + 10 + limit); + li.onDragStart = startDrag; + li.onDragEnd = endDrag; + img.style.cursor = 'move'; + } +} + +function submitOrderForm() { + var inputOrder = document.getElementsBySelector('input[name=order_]')[0]; + inputOrder.value = getOrder(); + inputOrder.disabled=false; +} + +function startDrag() { + this.style.zIndex = '10'; + this.className = 'dragging'; +} + +function endDrag(x, y) { + this.style.zIndex = '1'; + this.className = ''; + // Work out how far along it has been dropped, using x co-ordinate + var oldIndex = this.index; + var newIndex = Math.round((y - 10 - top) / height); + // 'Snap' to the correct position + this.style.top = (10 + top + newIndex * height) + 'px'; + this.index = newIndex; + moveItem(oldIndex, newIndex); +} + +function moveItem(oldIndex, newIndex) { + // Swaps two items, adjusts the index and left co-ord for all others + if (oldIndex == newIndex) { + return; // Nothing to swap; + } + var direction, lo, hi; + if (newIndex > oldIndex) { + lo = oldIndex; + hi = newIndex; + direction = -1; + } else { + direction = 1; + hi = oldIndex; + lo = newIndex; + } + var lis2 = new Array(); // We will build the new order in this array + for (var i = 0; i < lis.length; i++) { + if (i < lo || i > hi) { + // Position of items not between the indexes is unaffected + lis2[i] = lis[i]; + continue; + } else if (i == newIndex) { + lis2[i] = lis[oldIndex]; + continue; + } else { + // Item is between the two indexes - move it along 1 + lis2[i] = lis[i - direction]; + } + } + // Re-index everything + reIndex(lis2); + lis = lis2; + draw(); +// document.getElementById('hiddenOrder').value = getOrder(); + document.getElementsBySelector('input[name=order_]')[0].value = getOrder(); +} + +function reIndex(lis) { + for (var i = 0; i < lis.length; i++) { + lis[i].index = i; + } +} + +function draw() { + for (var i = 0; i < lis.length; i++) { + var li = lis[i]; + li.index = i; + li.style.position = 'absolute'; + li.style.left = (10 + left) + 'px'; + li.style.top = (10 + top + (i * height)) + 'px'; + } +} + +function getOrder() { + var order = new Array(lis.length); + for (var i = 0; i < lis.length; i++) { + order[i] = lis[i].id.substring(1, 100); + } + return order.join(','); +} + +function setOrder(id_list) { + /* Set the current order to match the lsit of IDs */ + var temp_lis = new Array(); + for (var i = 0; i < id_list.length; i++) { + var id = 'p' + id_list[i]; + temp_lis[temp_lis.length] = document.getElementById(id); + } + reIndex(temp_lis); + lis = temp_lis; + draw(); +} + +function addEvent(elm, evType, fn, useCapture) +// addEvent and removeEvent +// cross-browser event handling for IE5+, NS6 and Mozilla +// By Scott Andrew +{ + if (elm.addEventListener){ + elm.addEventListener(evType, fn, useCapture); + return true; + } else if (elm.attachEvent){ + var r = elm.attachEvent("on"+evType, fn); + return r; + } else { + elm['on'+evType] = fn; + } +} diff --git a/webapp/django/contrib/admin/media/js/calendar.js b/webapp/django/contrib/admin/media/js/calendar.js new file mode 100644 index 0000000000..90351763a7 --- /dev/null +++ b/webapp/django/contrib/admin/media/js/calendar.js @@ -0,0 +1,143 @@ +/* +calendar.js - Calendar functions by Adrian Holovaty +*/ + +function removeChildren(a) { // "a" is reference to an object + while (a.hasChildNodes()) a.removeChild(a.lastChild); +} + +// quickElement(tagType, parentReference, textInChildNode, [, attribute, attributeValue ...]); +function quickElement() { + var obj = document.createElement(arguments[0]); + if (arguments[2] != '' && arguments[2] != null) { + var textNode = document.createTextNode(arguments[2]); + obj.appendChild(textNode); + } + var len = arguments.length; + for (var i = 3; i < len; i += 2) { + obj.setAttribute(arguments[i], arguments[i+1]); + } + arguments[1].appendChild(obj); + return obj; +} + +// CalendarNamespace -- Provides a collection of HTML calendar-related helper functions +var CalendarNamespace = { + monthsOfYear: gettext('January February March April May June July August September October November December').split(' '), + daysOfWeek: gettext('S M T W T F S').split(' '), + isLeapYear: function(year) { + return (((year % 4)==0) && ((year % 100)!=0) || ((year % 400)==0)); + }, + getDaysInMonth: function(month,year) { + var days; + if (month==1 || month==3 || month==5 || month==7 || month==8 || month==10 || month==12) { + days = 31; + } + else if (month==4 || month==6 || month==9 || month==11) { + days = 30; + } + else if (month==2 && CalendarNamespace.isLeapYear(year)) { + days = 29; + } + else { + days = 28; + } + return days; + }, + draw: function(month, year, div_id, callback) { // month = 1-12, year = 1-9999 + month = parseInt(month); + year = parseInt(year); + var calDiv = document.getElementById(div_id); + removeChildren(calDiv); + var calTable = document.createElement('table'); + quickElement('caption', calTable, CalendarNamespace.monthsOfYear[month-1] + ' ' + year); + var tableBody = quickElement('tbody', calTable); + + // Draw days-of-week header + var tableRow = quickElement('tr', tableBody); + for (var i = 0; i < 7; i++) { + quickElement('th', tableRow, CalendarNamespace.daysOfWeek[i]); + } + + var startingPos = new Date(year, month-1, 1).getDay(); + var days = CalendarNamespace.getDaysInMonth(month, year); + + // Draw blanks before first of month + tableRow = quickElement('tr', tableBody); + for (var i = 0; i < startingPos; i++) { + var _cell = quickElement('td', tableRow, ' '); + _cell.style.backgroundColor = '#f3f3f3'; + } + + // Draw days of month + var currentDay = 1; + for (var i = startingPos; currentDay <= days; i++) { + if (i%7 == 0 && currentDay != 1) { + tableRow = quickElement('tr', tableBody); + } + var cell = quickElement('td', tableRow, ''); + quickElement('a', cell, currentDay, 'href', 'javascript:void(' + callback + '('+year+','+month+','+currentDay+'));'); + currentDay++; + } + + // Draw blanks after end of month (optional, but makes for valid code) + while (tableRow.childNodes.length < 7) { + var _cell = quickElement('td', tableRow, ' '); + _cell.style.backgroundColor = '#f3f3f3'; + } + + calDiv.appendChild(calTable); + } +} + +// Calendar -- A calendar instance +function Calendar(div_id, callback) { + // div_id (string) is the ID of the element in which the calendar will + // be displayed + // callback (string) is the name of a JavaScript function that will be + // called with the parameters (year, month, day) when a day in the + // calendar is clicked + this.div_id = div_id; + this.callback = callback; + this.today = new Date(); + this.currentMonth = this.today.getMonth() + 1; + this.currentYear = this.today.getFullYear(); +} +Calendar.prototype = { + drawCurrent: function() { + CalendarNamespace.draw(this.currentMonth, this.currentYear, this.div_id, this.callback); + }, + drawDate: function(month, year) { + this.currentMonth = month; + this.currentYear = year; + this.drawCurrent(); + }, + drawPreviousMonth: function() { + if (this.currentMonth == 1) { + this.currentMonth = 12; + this.currentYear--; + } + else { + this.currentMonth--; + } + this.drawCurrent(); + }, + drawNextMonth: function() { + if (this.currentMonth == 12) { + this.currentMonth = 1; + this.currentYear++; + } + else { + this.currentMonth++; + } + this.drawCurrent(); + }, + drawPreviousYear: function() { + this.currentYear--; + this.drawCurrent(); + }, + drawNextYear: function() { + this.currentYear++; + this.drawCurrent(); + } +} diff --git a/webapp/django/contrib/admin/media/js/core.js b/webapp/django/contrib/admin/media/js/core.js new file mode 100644 index 0000000000..c8d0db6a8d --- /dev/null +++ b/webapp/django/contrib/admin/media/js/core.js @@ -0,0 +1,176 @@ +// Core javascript helper functions + +// basic browser identification & version +var isOpera = (navigator.userAgent.indexOf("Opera")>=0) && parseFloat(navigator.appVersion); +var isIE = ((document.all) && (!isOpera)) && parseFloat(navigator.appVersion.split("MSIE ")[1].split(";")[0]); + +// Cross-browser event handlers. +function addEvent(obj, evType, fn) { + if (obj.addEventListener) { + obj.addEventListener(evType, fn, false); + return true; + } else if (obj.attachEvent) { + var r = obj.attachEvent("on" + evType, fn); + return r; + } else { + return false; + } +} + +function removeEvent(obj, evType, fn) { + if (obj.removeEventListener) { + obj.removeEventListener(evType, fn, false); + return true; + } else if (obj.detachEvent) { + obj.detachEvent("on" + evType, fn); + return true; + } else { + return false; + } +} + +// quickElement(tagType, parentReference, textInChildNode, [, attribute, attributeValue ...]); +function quickElement() { + var obj = document.createElement(arguments[0]); + if (arguments[2] != '' && arguments[2] != null) { + var textNode = document.createTextNode(arguments[2]); + obj.appendChild(textNode); + } + var len = arguments.length; + for (var i = 3; i < len; i += 2) { + obj.setAttribute(arguments[i], arguments[i+1]); + } + arguments[1].appendChild(obj); + return obj; +} + +// ---------------------------------------------------------------------------- +// Cross-browser xmlhttp object +// from http://jibbering.com/2002/4/httprequest.html +// ---------------------------------------------------------------------------- +var xmlhttp; +/*@cc_on @*/ +/*@if (@_jscript_version >= 5) + try { + xmlhttp = new ActiveXObject("Msxml2.XMLHTTP"); + } catch (e) { + try { + xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); + } catch (E) { + xmlhttp = false; + } + } +@else + xmlhttp = false; +@end @*/ +if (!xmlhttp && typeof XMLHttpRequest != 'undefined') { + xmlhttp = new XMLHttpRequest(); +} + +// ---------------------------------------------------------------------------- +// Find-position functions by PPK +// See http://www.quirksmode.org/js/findpos.html +// ---------------------------------------------------------------------------- +function findPosX(obj) { + var curleft = 0; + if (obj.offsetParent) { + while (obj.offsetParent) { + curleft += obj.offsetLeft - ((isOpera) ? 0 : obj.scrollLeft); + obj = obj.offsetParent; + } + // IE offsetParent does not include the top-level + if (isIE && obj.parentElement){ + curleft += obj.offsetLeft - obj.scrollLeft; + } + } else if (obj.x) { + curleft += obj.x; + } + return curleft; +} + +function findPosY(obj) { + var curtop = 0; + if (obj.offsetParent) { + while (obj.offsetParent) { + curtop += obj.offsetTop - ((isOpera) ? 0 : obj.scrollTop); + obj = obj.offsetParent; + } + // IE offsetParent does not include the top-level + if (isIE && obj.parentElement){ + curtop += obj.offsetTop - obj.scrollTop; + } + } else if (obj.y) { + curtop += obj.y; + } + return curtop; +} + +//----------------------------------------------------------------------------- +// Date object extensions +// ---------------------------------------------------------------------------- +Date.prototype.getCorrectYear = function() { + // Date.getYear() is unreliable -- + // see http://www.quirksmode.org/js/introdate.html#year + var y = this.getYear() % 100; + return (y < 38) ? y + 2000 : y + 1900; +} + +Date.prototype.getTwoDigitMonth = function() { + return (this.getMonth() < 9) ? '0' + (this.getMonth()+1) : (this.getMonth()+1); +} + +Date.prototype.getTwoDigitDate = function() { + return (this.getDate() < 10) ? '0' + this.getDate() : this.getDate(); +} + +Date.prototype.getTwoDigitHour = function() { + return (this.getHours() < 10) ? '0' + this.getHours() : this.getHours(); +} + +Date.prototype.getTwoDigitMinute = function() { + return (this.getMinutes() < 10) ? '0' + this.getMinutes() : this.getMinutes(); +} + +Date.prototype.getTwoDigitSecond = function() { + return (this.getSeconds() < 10) ? '0' + this.getSeconds() : this.getSeconds(); +} + +Date.prototype.getISODate = function() { + return this.getCorrectYear() + '-' + this.getTwoDigitMonth() + '-' + this.getTwoDigitDate(); +} + +Date.prototype.getHourMinute = function() { + return this.getTwoDigitHour() + ':' + this.getTwoDigitMinute(); +} + +Date.prototype.getHourMinuteSecond = function() { + return this.getTwoDigitHour() + ':' + this.getTwoDigitMinute() + ':' + this.getTwoDigitSecond(); +} + +// ---------------------------------------------------------------------------- +// String object extensions +// ---------------------------------------------------------------------------- +String.prototype.pad_left = function(pad_length, pad_string) { + var new_string = this; + for (var i = 0; new_string.length < pad_length; i++) { + new_string = pad_string + new_string; + } + return new_string; +} + +// ---------------------------------------------------------------------------- +// Get the computed style for and element +// ---------------------------------------------------------------------------- +function getStyle(oElm, strCssRule){ + var strValue = ""; + if(document.defaultView && document.defaultView.getComputedStyle){ + strValue = document.defaultView.getComputedStyle(oElm, "").getPropertyValue(strCssRule); + } + else if(oElm.currentStyle){ + strCssRule = strCssRule.replace(/\-(\w)/g, function (strMatch, p1){ + return p1.toUpperCase(); + }); + strValue = oElm.currentStyle[strCssRule]; + } + return strValue; +} diff --git a/webapp/django/contrib/admin/media/js/dateparse.js b/webapp/django/contrib/admin/media/js/dateparse.js new file mode 100644 index 0000000000..e1c870e146 --- /dev/null +++ b/webapp/django/contrib/admin/media/js/dateparse.js @@ -0,0 +1,233 @@ +/* 'Magic' date parsing, by Simon Willison (6th October 2003) + http://simon.incutio.com/archive/2003/10/06/betterDateInput + Adapted for 6newslawrence.com, 28th January 2004 +*/ + +/* Finds the index of the first occurence of item in the array, or -1 if not found */ +if (typeof Array.prototype.indexOf == 'undefined') { + Array.prototype.indexOf = function(item) { + var len = this.length; + for (var i = 0; i < len; i++) { + if (this[i] == item) { + return i; + } + } + return -1; + }; +} +/* Returns an array of items judged 'true' by the passed in test function */ +if (typeof Array.prototype.filter == 'undefined') { + Array.prototype.filter = function(test) { + var matches = []; + var len = this.length; + for (var i = 0; i < len; i++) { + if (test(this[i])) { + matches[matches.length] = this[i]; + } + } + return matches; + }; +} + +var monthNames = gettext("January February March April May June July August September October November December").split(" "); +var weekdayNames = gettext("Sunday Monday Tuesday Wednesday Thursday Friday Saturday").split(" "); + +/* Takes a string, returns the index of the month matching that string, throws + an error if 0 or more than 1 matches +*/ +function parseMonth(month) { + var matches = monthNames.filter(function(item) { + return new RegExp("^" + month, "i").test(item); + }); + if (matches.length == 0) { + throw new Error("Invalid month string"); + } + if (matches.length > 1) { + throw new Error("Ambiguous month"); + } + return monthNames.indexOf(matches[0]); +} +/* Same as parseMonth but for days of the week */ +function parseWeekday(weekday) { + var matches = weekdayNames.filter(function(item) { + return new RegExp("^" + weekday, "i").test(item); + }); + if (matches.length == 0) { + throw new Error("Invalid day string"); + } + if (matches.length > 1) { + throw new Error("Ambiguous weekday"); + } + return weekdayNames.indexOf(matches[0]); +} + +/* Array of objects, each has 're', a regular expression and 'handler', a + function for creating a date from something that matches the regular + expression. Handlers may throw errors if string is unparseable. +*/ +var dateParsePatterns = [ + // Today + { re: /^tod/i, + handler: function() { + return new Date(); + } + }, + // Tomorrow + { re: /^tom/i, + handler: function() { + var d = new Date(); + d.setDate(d.getDate() + 1); + return d; + } + }, + // Yesterday + { re: /^yes/i, + handler: function() { + var d = new Date(); + d.setDate(d.getDate() - 1); + return d; + } + }, + // 4th + { re: /^(\d{1,2})(st|nd|rd|th)?$/i, + handler: function(bits) { + var d = new Date(); + d.setDate(parseInt(bits[1], 10)); + return d; + } + }, + // 4th Jan + { re: /^(\d{1,2})(?:st|nd|rd|th)? (\w+)$/i, + handler: function(bits) { + var d = new Date(); + d.setDate(parseInt(bits[1], 10)); + d.setMonth(parseMonth(bits[2])); + return d; + } + }, + // 4th Jan 2003 + { re: /^(\d{1,2})(?:st|nd|rd|th)? (\w+),? (\d{4})$/i, + handler: function(bits) { + var d = new Date(); + d.setDate(parseInt(bits[1], 10)); + d.setMonth(parseMonth(bits[2])); + d.setYear(bits[3]); + return d; + } + }, + // Jan 4th + { re: /^(\w+) (\d{1,2})(?:st|nd|rd|th)?$/i, + handler: function(bits) { + var d = new Date(); + d.setDate(parseInt(bits[2], 10)); + d.setMonth(parseMonth(bits[1])); + return d; + } + }, + // Jan 4th 2003 + { re: /^(\w+) (\d{1,2})(?:st|nd|rd|th)?,? (\d{4})$/i, + handler: function(bits) { + var d = new Date(); + d.setDate(parseInt(bits[2], 10)); + d.setMonth(parseMonth(bits[1])); + d.setYear(bits[3]); + return d; + } + }, + // next Tuesday - this is suspect due to weird meaning of "next" + { re: /^next (\w+)$/i, + handler: function(bits) { + var d = new Date(); + var day = d.getDay(); + var newDay = parseWeekday(bits[1]); + var addDays = newDay - day; + if (newDay <= day) { + addDays += 7; + } + d.setDate(d.getDate() + addDays); + return d; + } + }, + // last Tuesday + { re: /^last (\w+)$/i, + handler: function(bits) { + throw new Error("Not yet implemented"); + } + }, + // mm/dd/yyyy (American style) + { re: /(\d{1,2})\/(\d{1,2})\/(\d{4})/, + handler: function(bits) { + var d = new Date(); + d.setYear(bits[3]); + d.setDate(parseInt(bits[2], 10)); + d.setMonth(parseInt(bits[1], 10) - 1); // Because months indexed from 0 + return d; + } + }, + // yyyy-mm-dd (ISO style) + { re: /(\d{4})-(\d{1,2})-(\d{1,2})/, + handler: function(bits) { + var d = new Date(); + d.setYear(parseInt(bits[1])); + d.setMonth(parseInt(bits[2], 10) - 1); + d.setDate(parseInt(bits[3], 10)); + return d; + } + }, +]; + +function parseDateString(s) { + for (var i = 0; i < dateParsePatterns.length; i++) { + var re = dateParsePatterns[i].re; + var handler = dateParsePatterns[i].handler; + var bits = re.exec(s); + if (bits) { + return handler(bits); + } + } + throw new Error("Invalid date string"); +} + +function fmt00(x) { + // fmt00: Tags leading zero onto numbers 0 - 9. + // Particularly useful for displaying results from Date methods. + // + if (Math.abs(parseInt(x)) < 10){ + x = "0"+ Math.abs(x); + } + return x; +} + +function parseDateStringISO(s) { + try { + var d = parseDateString(s); + return d.getFullYear() + '-' + (fmt00(d.getMonth() + 1)) + '-' + fmt00(d.getDate()) + } + catch (e) { return s; } +} +function magicDate(input) { + var messagespan = input.id + 'Msg'; + try { + var d = parseDateString(input.value); + input.value = d.getFullYear() + '-' + (fmt00(d.getMonth() + 1)) + '-' + + fmt00(d.getDate()); + input.className = ''; + // Human readable date + if (document.getElementById(messagespan)) { + document.getElementById(messagespan).firstChild.nodeValue = d.toDateString(); + document.getElementById(messagespan).className = 'normal'; + } + } + catch (e) { + input.className = 'error'; + var message = e.message; + // Fix for IE6 bug + if (message.indexOf('is null or not an object') > -1) { + message = 'Invalid date string'; + } + if (document.getElementById(messagespan)) { + document.getElementById(messagespan).firstChild.nodeValue = message; + document.getElementById(messagespan).className = 'error'; + } + } +} diff --git a/webapp/django/contrib/admin/media/js/getElementsBySelector.js b/webapp/django/contrib/admin/media/js/getElementsBySelector.js new file mode 100644 index 0000000000..ae6d387a91 --- /dev/null +++ b/webapp/django/contrib/admin/media/js/getElementsBySelector.js @@ -0,0 +1,167 @@ +/* document.getElementsBySelector(selector) + - returns an array of element objects from the current document + matching the CSS selector. Selectors can contain element names, + class names and ids and can be nested. For example: + + elements = document.getElementsBySelect('div#main p a.external') + + Will return an array of all 'a' elements with 'external' in their + class attribute that are contained inside 'p' elements that are + contained inside the 'div' element which has id="main" + + New in version 0.4: Support for CSS2 and CSS3 attribute selectors: + See http://www.w3.org/TR/css3-selectors/#attribute-selectors + + Version 0.4 - Simon Willison, March 25th 2003 + -- Works in Phoenix 0.5, Mozilla 1.3, Opera 7, Internet Explorer 6, Internet Explorer 5 on Windows + -- Opera 7 fails +*/ + +function getAllChildren(e) { + // Returns all children of element. Workaround required for IE5/Windows. Ugh. + return e.all ? e.all : e.getElementsByTagName('*'); +} + +document.getElementsBySelector = function(selector) { + // Attempt to fail gracefully in lesser browsers + if (!document.getElementsByTagName) { + return new Array(); + } + // Split selector in to tokens + var tokens = selector.split(' '); + var currentContext = new Array(document); + for (var i = 0; i < tokens.length; i++) { + token = tokens[i].replace(/^\s+/,'').replace(/\s+$/,'');; + if (token.indexOf('#') > -1) { + // Token is an ID selector + var bits = token.split('#'); + var tagName = bits[0]; + var id = bits[1]; + var element = document.getElementById(id); + if (tagName && element.nodeName.toLowerCase() != tagName) { + // tag with that ID not found, return false + return new Array(); + } + // Set currentContext to contain just this element + currentContext = new Array(element); + continue; // Skip to next token + } + if (token.indexOf('.') > -1) { + // Token contains a class selector + var bits = token.split('.'); + var tagName = bits[0]; + var className = bits[1]; + if (!tagName) { + tagName = '*'; + } + // Get elements matching tag, filter them for class selector + var found = new Array; + var foundCount = 0; + for (var h = 0; h < currentContext.length; h++) { + var elements; + if (tagName == '*') { + elements = getAllChildren(currentContext[h]); + } else { + try { + elements = currentContext[h].getElementsByTagName(tagName); + } + catch(e) { + elements = []; + } + } + for (var j = 0; j < elements.length; j++) { + found[foundCount++] = elements[j]; + } + } + currentContext = new Array; + var currentContextIndex = 0; + for (var k = 0; k < found.length; k++) { + if (found[k].className && found[k].className.match(new RegExp('\\b'+className+'\\b'))) { + currentContext[currentContextIndex++] = found[k]; + } + } + continue; // Skip to next token + } + // Code to deal with attribute selectors + if (token.match(/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/)) { + var tagName = RegExp.$1; + var attrName = RegExp.$2; + var attrOperator = RegExp.$3; + var attrValue = RegExp.$4; + if (!tagName) { + tagName = '*'; + } + // Grab all of the tagName elements within current context + var found = new Array; + var foundCount = 0; + for (var h = 0; h < currentContext.length; h++) { + var elements; + if (tagName == '*') { + elements = getAllChildren(currentContext[h]); + } else { + elements = currentContext[h].getElementsByTagName(tagName); + } + for (var j = 0; j < elements.length; j++) { + found[foundCount++] = elements[j]; + } + } + currentContext = new Array; + var currentContextIndex = 0; + var checkFunction; // This function will be used to filter the elements + switch (attrOperator) { + case '=': // Equality + checkFunction = function(e) { return (e.getAttribute(attrName) == attrValue); }; + break; + case '~': // Match one of space seperated words + checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('\\b'+attrValue+'\\b'))); }; + break; + case '|': // Match start with value followed by optional hyphen + checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('^'+attrValue+'-?'))); }; + break; + case '^': // Match starts with value + checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) == 0); }; + break; + case '$': // Match ends with value - fails with "Warning" in Opera 7 + checkFunction = function(e) { return (e.getAttribute(attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length); }; + break; + case '*': // Match ends with value + checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) > -1); }; + break; + default : + // Just test for existence of attribute + checkFunction = function(e) { return e.getAttribute(attrName); }; + } + currentContext = new Array; + var currentContextIndex = 0; + for (var k = 0; k < found.length; k++) { + if (checkFunction(found[k])) { + currentContext[currentContextIndex++] = found[k]; + } + } + // alert('Attribute Selector: '+tagName+' '+attrName+' '+attrOperator+' '+attrValue); + continue; // Skip to next token + } + // If we get here, token is JUST an element (not a class or ID selector) + tagName = token; + var found = new Array; + var foundCount = 0; + for (var h = 0; h < currentContext.length; h++) { + var elements = currentContext[h].getElementsByTagName(tagName); + for (var j = 0; j < elements.length; j++) { + found[foundCount++] = elements[j]; + } + } + currentContext = found; + } + return currentContext; +} + +/* That revolting regular expression explained +/^(\w+)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/ + \---/ \---/\-------------/ \-------/ + | | | | + | | | The value + | | ~,|,^,$,* or = + | Attribute + Tag +*/ diff --git a/webapp/django/contrib/admin/media/js/timeparse.js b/webapp/django/contrib/admin/media/js/timeparse.js new file mode 100644 index 0000000000..882f41d56e --- /dev/null +++ b/webapp/django/contrib/admin/media/js/timeparse.js @@ -0,0 +1,94 @@ +var timeParsePatterns = [ + // 9 + { re: /^\d{1,2}$/i, + handler: function(bits) { + if (bits[0].length == 1) { + return '0' + bits[0] + ':00'; + } else { + return bits[0] + ':00'; + } + } + }, + // 13:00 + { re: /^\d{2}[:.]\d{2}$/i, + handler: function(bits) { + return bits[0].replace('.', ':'); + } + }, + // 9:00 + { re: /^\d[:.]\d{2}$/i, + handler: function(bits) { + return '0' + bits[0].replace('.', ':'); + } + }, + // 3 am / 3 a.m. / 3am + { re: /^(\d+)\s*([ap])(?:.?m.?)?$/i, + handler: function(bits) { + var hour = parseInt(bits[1]); + if (hour == 12) { + hour = 0; + } + if (bits[2].toLowerCase() == 'p') { + if (hour == 12) { + hour = 0; + } + return (hour + 12) + ':00'; + } else { + if (hour < 10) { + return '0' + hour + ':00'; + } else { + return hour + ':00'; + } + } + } + }, + // 3.30 am / 3:15 a.m. / 3.00am + { re: /^(\d+)[.:](\d{2})\s*([ap]).?m.?$/i, + handler: function(bits) { + var hour = parseInt(bits[1]); + var mins = parseInt(bits[2]); + if (mins < 10) { + mins = '0' + mins; + } + if (hour == 12) { + hour = 0; + } + if (bits[3].toLowerCase() == 'p') { + if (hour == 12) { + hour = 0; + } + return (hour + 12) + ':' + mins; + } else { + if (hour < 10) { + return '0' + hour + ':' + mins; + } else { + return hour + ':' + mins; + } + } + } + }, + // noon + { re: /^no/i, + handler: function(bits) { + return '12:00'; + } + }, + // midnight + { re: /^mid/i, + handler: function(bits) { + return '00:00'; + } + } +]; + +function parseTimeString(s) { + for (var i = 0; i < timeParsePatterns.length; i++) { + var re = timeParsePatterns[i].re; + var handler = timeParsePatterns[i].handler; + var bits = re.exec(s); + if (bits) { + return handler(bits); + } + } + return s; +} diff --git a/webapp/django/contrib/admin/media/js/urlify.js b/webapp/django/contrib/admin/media/js/urlify.js new file mode 100644 index 0000000000..d8f2549e27 --- /dev/null +++ b/webapp/django/contrib/admin/media/js/urlify.js @@ -0,0 +1,140 @@ +var LATIN_MAP = { + 'À': 'A', 'Á': 'A', 'Â': 'A', 'Ã': 'A', 'Ä': 'A', 'Å': 'A', 'Æ': 'AE', 'Ç': + 'C', 'È': 'E', 'É': 'E', 'Ê': 'E', 'Ë': 'E', 'Ì': 'I', 'Í': 'I', 'Î': 'I', + 'Ï': 'I', 'Ð': 'D', 'Ñ': 'N', 'Ò': 'O', 'Ó': 'O', 'Ô': 'O', 'Õ': 'O', 'Ö': + 'O', 'Ő': 'O', 'Ø': 'O', 'Ù': 'U', 'Ú': 'U', 'Û': 'U', 'Ü': 'U', 'Ű': 'U', + 'Ý': 'Y', 'Þ': 'TH', 'ß': 'ss', 'à':'a', 'á':'a', 'â': 'a', 'ã': 'a', 'ä': + 'a', 'å': 'a', 'æ': 'ae', 'ç': 'c', 'è': 'e', 'é': 'e', 'ê': 'e', 'ë': 'e', + 'ì': 'i', 'í': 'i', 'î': 'i', 'ï': 'i', 'ð': 'd', 'ñ': 'n', 'ò': 'o', 'ó': + 'o', 'ô': 'o', 'õ': 'o', 'ö': 'o', 'ő': 'o', 'ø': 'o', 'ù': 'u', 'ú': 'u', + 'û': 'u', 'ü': 'u', 'ű': 'u', 'ý': 'y', 'þ': 'th', 'ÿ': 'y' +} +var LATIN_SYMBOLS_MAP = { + '©':'(c)' +} +var GREEK_MAP = { + 'α':'a', 'β':'b', 'γ':'g', 'δ':'d', 'ε':'e', 'ζ':'z', 'η':'h', 'θ':'8', + 'ι':'i', 'κ':'k', 'λ':'l', 'μ':'m', 'ν':'n', 'ξ':'3', 'ο':'o', 'π':'p', + 'ρ':'r', 'σ':'s', 'τ':'t', 'υ':'y', 'φ':'f', 'χ':'x', 'ψ':'ps', 'ω':'w', + 'ά':'a', 'έ':'e', 'ί':'i', 'ό':'o', 'ύ':'y', 'ή':'h', 'ώ':'w', 'ς':'s', + 'ϊ':'i', 'ΰ':'y', 'ϋ':'y', 'ΐ':'i', + 'Α':'A', 'Β':'B', 'Γ':'G', 'Δ':'D', 'Ε':'E', 'Ζ':'Z', 'Η':'H', 'Θ':'8', + 'Ι':'I', 'Κ':'K', 'Λ':'L', 'Μ':'M', 'Ν':'N', 'Ξ':'3', 'Ο':'O', 'Π':'P', + 'Ρ':'R', 'Σ':'S', 'Τ':'T', 'Υ':'Y', 'Φ':'F', 'Χ':'X', 'Ψ':'PS', 'Ω':'W', + 'Ά':'A', 'Έ':'E', 'Ί':'I', 'Ό':'O', 'Ύ':'Y', 'Ή':'H', 'Ώ':'W', 'Ϊ':'I', + 'Ϋ':'Y' +} +var TURKISH_MAP = { + 'ş':'s', 'Ş':'S', 'ı':'i', 'İ':'I', 'ç':'c', 'Ç':'C', 'ü':'u', 'Ü':'U', + 'ö':'o', 'Ö':'O', 'ğ':'g', 'Ğ':'G' +} +var RUSSIAN_MAP = { + 'а':'a', 'б':'b', 'в':'v', 'г':'g', 'д':'d', 'е':'e', 'ё':'yo', 'ж':'zh', + 'з':'z', 'и':'i', 'й':'j', 'к':'k', 'л':'l', 'м':'m', 'н':'n', 'о':'o', + 'п':'p', 'р':'r', 'с':'s', 'т':'t', 'у':'u', 'ф':'f', 'х':'h', 'ц':'c', + 'ч':'ch', 'ш':'sh', 'щ':'sh', 'ъ':'', 'ы':'y', 'ь':'', 'э':'e', 'ю':'yu', + 'я':'ya', + 'А':'A', 'Б':'B', 'В':'V', 'Г':'G', 'Д':'D', 'Е':'E', 'Ё':'Yo', 'Ж':'Zh', + 'З':'Z', 'И':'I', 'Й':'J', 'К':'K', 'Л':'L', 'М':'M', 'Н':'N', 'О':'O', + 'П':'P', 'Р':'R', 'С':'S', 'Т':'T', 'У':'U', 'Ф':'F', 'Х':'H', 'Ц':'C', + 'Ч':'Ch', 'Ш':'Sh', 'Щ':'Sh', 'Ъ':'', 'Ы':'Y', 'Ь':'', 'Э':'E', 'Ю':'Yu', + 'Я':'Ya' +} +var UKRAINIAN_MAP = { + 'Є':'Ye', 'І':'I', 'Ї':'Yi', 'Ґ':'G', 'є':'ye', 'і':'i', 'ї':'yi', 'ґ':'g' +} +var CZECH_MAP = { + 'č':'c', 'ď':'d', 'ě':'e', 'ň': 'n', 'ř':'r', 'š':'s', 'ť':'t', 'ů':'u', + 'ž':'z', 'Č':'C', 'Ď':'D', 'Ě':'E', 'Ň': 'N', 'Ř':'R', 'Š':'S', 'Ť':'T', + 'Ů':'U', 'Ž':'Z' +} + +var POLISH_MAP = { + 'ą':'a', 'ć':'c', 'ę':'e', 'ł':'l', 'ń':'n', 'ó':'o', 'ś':'s', 'ź':'z', + 'ż':'z', 'Ą':'A', 'Ć':'C', 'Ę':'e', 'Ł':'L', 'Ń':'N', 'Ó':'o', 'Ś':'S', + 'Ź':'Z', 'Ż':'Z' +} + +var LATVIAN_MAP = { + 'ā':'a', 'č':'c', 'ē':'e', 'ģ':'g', 'ī':'i', 'ķ':'k', 'ļ':'l', 'ņ':'n', + 'š':'s', 'ū':'u', 'ž':'z', 'Ā':'A', 'Č':'C', 'Ē':'E', 'Ģ':'G', 'Ī':'i', + 'Ķ':'k', 'Ļ':'L', 'Ņ':'N', 'Š':'S', 'Ū':'u', 'Ž':'Z' +} + +var ALL_DOWNCODE_MAPS=new Array() +ALL_DOWNCODE_MAPS[0]=LATIN_MAP +ALL_DOWNCODE_MAPS[1]=LATIN_SYMBOLS_MAP +ALL_DOWNCODE_MAPS[2]=GREEK_MAP +ALL_DOWNCODE_MAPS[3]=TURKISH_MAP +ALL_DOWNCODE_MAPS[4]=RUSSIAN_MAP +ALL_DOWNCODE_MAPS[5]=UKRAINIAN_MAP +ALL_DOWNCODE_MAPS[6]=CZECH_MAP +ALL_DOWNCODE_MAPS[7]=POLISH_MAP +ALL_DOWNCODE_MAPS[8]=LATVIAN_MAP + +var Downcoder = new Object(); +Downcoder.Initialize = function() +{ + if (Downcoder.map) // already made + return ; + Downcoder.map ={} + Downcoder.chars = '' ; + for(var i in ALL_DOWNCODE_MAPS) + { + var lookup = ALL_DOWNCODE_MAPS[i] + for (var c in lookup) + { + Downcoder.map[c] = lookup[c] ; + Downcoder.chars += c ; + } + } + Downcoder.regex = new RegExp('[' + Downcoder.chars + ']|[^' + Downcoder.chars + ']+','g') ; +} + +downcode= function( slug ) +{ + Downcoder.Initialize() ; + var downcoded ="" + var pieces = slug.match(Downcoder.regex); + if(pieces) + { + for (var i = 0 ; i < pieces.length ; i++) + { + if (pieces[i].length == 1) + { + var mapped = Downcoder.map[pieces[i]] ; + if (mapped != null) + { + downcoded+=mapped; + continue ; + } + } + downcoded+=pieces[i]; + } + } + else + { + downcoded = slug; + } + return downcoded; +} + + +function URLify(s, num_chars) { + // changes, e.g., "Petty theft" to "petty_theft" + // remove all these words from the string before urlifying + s = downcode(s); + removelist = ["a", "an", "as", "at", "before", "but", "by", "for", "from", + "is", "in", "into", "like", "of", "off", "on", "onto", "per", + "since", "than", "the", "this", "that", "to", "up", "via", + "with"]; + r = new RegExp('\\b(' + removelist.join('|') + ')\\b', 'gi'); + s = s.replace(r, ''); + // if downcode doesn't hit, the char will be stripped here + s = s.replace(/[^-\w\s]/g, ''); // remove unneeded chars + s = s.replace(/^\s+|\s+$/g, ''); // trim leading/trailing spaces + s = s.replace(/[-\s]+/g, '-'); // convert spaces to hyphens + s = s.toLowerCase(); // convert to lowercase + return s.substring(0, num_chars);// trim to first num_chars chars +} + diff --git a/webapp/django/contrib/admin/models.py b/webapp/django/contrib/admin/models.py new file mode 100644 index 0000000000..259884faba --- /dev/null +++ b/webapp/django/contrib/admin/models.py @@ -0,0 +1,54 @@ +from django.db import models +from django.contrib.contenttypes.models import ContentType +from django.contrib.auth.models import User +from django.contrib.admin.util import quote +from django.utils.translation import ugettext_lazy as _ +from django.utils.encoding import smart_unicode +from django.utils.safestring import mark_safe + +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, smart_unicode(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'), max_length=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 smart_unicode(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 mark_safe(u"%s/%s/%s/" % (self.content_type.app_label, self.content_type.model, quote(self.object_id))) diff --git a/webapp/django/contrib/admin/options.py b/webapp/django/contrib/admin/options.py new file mode 100644 index 0000000000..1d00683012 --- /dev/null +++ b/webapp/django/contrib/admin/options.py @@ -0,0 +1,790 @@ +from django import forms, template +from django.forms.formsets import all_valid +from django.forms.models import modelform_factory, inlineformset_factory +from django.forms.models import BaseInlineFormSet +from django.contrib.contenttypes.models import ContentType +from django.contrib.admin import widgets +from django.contrib.admin import helpers +from django.contrib.admin.util import quote, unquote, flatten_fieldsets, get_deleted_objects +from django.core.exceptions import PermissionDenied +from django.db import models, transaction +from django.http import Http404, HttpResponse, HttpResponseRedirect +from django.shortcuts import get_object_or_404, render_to_response +from django.utils.html import escape +from django.utils.safestring import mark_safe +from django.utils.text import capfirst, get_text_list +from django.utils.translation import ugettext as _ +from django.utils.encoding import force_unicode +try: + set +except NameError: + from sets import Set as set # Python 2.3 fallback + +HORIZONTAL, VERTICAL = 1, 2 +# returns the <ul> class for a given radio_admin field +get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '') + +class IncorrectLookupParameters(Exception): + pass + +class BaseModelAdmin(object): + """Functionality common to both ModelAdmin and InlineAdmin.""" + raw_id_fields = () + fields = None + fieldsets = None + form = forms.ModelForm + filter_vertical = () + filter_horizontal = () + radio_fields = {} + prepopulated_fields = {} + + def formfield_for_dbfield(self, db_field, **kwargs): + """ + Hook for specifying the form Field instance for a given database Field + instance. + + If kwargs are given, they're passed to the form Field's constructor. + """ + + # If the field specifies choices, we don't need to look for special + # admin widgets - we just need to use a select widget of some kind. + if db_field.choices: + if db_field.name in self.radio_fields: + # If the field is named as a radio_field, use a RadioSelect + kwargs['widget'] = widgets.AdminRadioSelect( + choices=db_field.get_choices(include_blank=db_field.blank, + blank_choice=[('', _('None'))]), + attrs={ + 'class': get_ul_class(self.radio_fields[db_field.name]), + } + ) + return db_field.formfield(**kwargs) + else: + # Otherwise, use the default select widget. + return db_field.formfield(**kwargs) + + # For DateTimeFields, use a special field and widget. + if isinstance(db_field, models.DateTimeField): + kwargs['form_class'] = forms.SplitDateTimeField + kwargs['widget'] = widgets.AdminSplitDateTime() + return db_field.formfield(**kwargs) + + # For DateFields, add a custom CSS class. + if isinstance(db_field, models.DateField): + kwargs['widget'] = widgets.AdminDateWidget + return db_field.formfield(**kwargs) + + # For TimeFields, add a custom CSS class. + if isinstance(db_field, models.TimeField): + kwargs['widget'] = widgets.AdminTimeWidget + return db_field.formfield(**kwargs) + + # For TextFields, add a custom CSS class. + if isinstance(db_field, models.TextField): + kwargs['widget'] = widgets.AdminTextareaWidget + return db_field.formfield(**kwargs) + + # For URLFields, add a custom CSS class. + if isinstance(db_field, models.URLField): + kwargs['widget'] = widgets.AdminURLFieldWidget + return db_field.formfield(**kwargs) + + # For IntegerFields, add a custom CSS class. + if isinstance(db_field, models.IntegerField): + kwargs['widget'] = widgets.AdminIntegerFieldWidget + return db_field.formfield(**kwargs) + + # For TextInputs, add a custom CSS class. + if isinstance(db_field, models.CharField): + kwargs['widget'] = widgets.AdminTextInputWidget + return db_field.formfield(**kwargs) + + # For FileFields and ImageFields add a link to the current file. + if isinstance(db_field, models.ImageField) or isinstance(db_field, models.FileField): + kwargs['widget'] = widgets.AdminFileWidget + return db_field.formfield(**kwargs) + + # For ForeignKey or ManyToManyFields, use a special widget. + if isinstance(db_field, (models.ForeignKey, models.ManyToManyField)): + if isinstance(db_field, models.ForeignKey) and db_field.name in self.raw_id_fields: + kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel) + elif isinstance(db_field, models.ForeignKey) and db_field.name in self.radio_fields: + kwargs['widget'] = widgets.AdminRadioSelect(attrs={ + 'class': get_ul_class(self.radio_fields[db_field.name]), + }) + kwargs['empty_label'] = db_field.blank and _('None') or None + else: + if isinstance(db_field, models.ManyToManyField): + # If it uses an intermediary model, don't show field in admin. + if db_field.rel.through is not None: + return None + elif db_field.name in self.raw_id_fields: + kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel) + kwargs['help_text'] = '' + elif db_field.name in (list(self.filter_vertical) + list(self.filter_horizontal)): + kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical)) + # Wrap the widget's render() method with a method that adds + # extra HTML to the end of the rendered output. + formfield = db_field.formfield(**kwargs) + # Don't wrap raw_id fields. Their add function is in the popup window. + if not db_field.name in self.raw_id_fields: + # formfield can be None if it came from a OneToOneField with + # parent_link=True + if formfield is not None: + formfield.widget = widgets.RelatedFieldWidgetWrapper(formfield.widget, db_field.rel, self.admin_site) + return formfield + + # For any other type of field, just call its formfield() method. + return db_field.formfield(**kwargs) + + def _declared_fieldsets(self): + if self.fieldsets: + return self.fieldsets + elif self.fields: + return [(None, {'fields': self.fields})] + return None + declared_fieldsets = property(_declared_fieldsets) + +class ModelAdmin(BaseModelAdmin): + "Encapsulates all admin options and functionality for a given model." + __metaclass__ = forms.MediaDefiningClass + + list_display = ('__str__',) + list_display_links = () + list_filter = () + list_select_related = False + list_per_page = 100 + search_fields = () + date_hierarchy = None + save_as = False + save_on_top = False + ordering = None + inlines = [] + + # Custom templates (designed to be over-ridden in subclasses) + change_form_template = None + change_list_template = None + delete_confirmation_template = None + object_history_template = None + + def __init__(self, model, admin_site): + self.model = model + self.opts = model._meta + self.admin_site = admin_site + self.inline_instances = [] + for inline_class in self.inlines: + inline_instance = inline_class(self.model, self.admin_site) + self.inline_instances.append(inline_instance) + super(ModelAdmin, self).__init__() + + def __call__(self, request, url): + # Delegate to the appropriate method, based on the URL. + if url is None: + return self.changelist_view(request) + elif url.endswith('add'): + return self.add_view(request) + elif url.endswith('history'): + return self.history_view(request, unquote(url[:-8])) + elif url.endswith('delete'): + return self.delete_view(request, unquote(url[:-7])) + else: + return self.change_view(request, unquote(url)) + + def _media(self): + from django.conf import settings + + js = ['js/core.js', 'js/admin/RelatedObjectLookups.js'] + if self.prepopulated_fields: + js.append('js/urlify.js') + if self.opts.get_ordered_objects(): + js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js']) + if self.filter_vertical or self.filter_horizontal: + js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js']) + + return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js]) + media = property(_media) + + def has_add_permission(self, request): + "Returns True if the given request has permission to add an object." + opts = self.opts + return request.user.has_perm(opts.app_label + '.' + opts.get_add_permission()) + + def has_change_permission(self, request, obj=None): + """ + Returns True if the given request has permission to change the given + Django model instance. + + If `obj` is None, this should return True if the given request has + permission to change *any* object of the given type. + """ + opts = self.opts + return request.user.has_perm(opts.app_label + '.' + opts.get_change_permission()) + + def has_delete_permission(self, request, obj=None): + """ + Returns True if the given request has permission to change the given + Django model instance. + + If `obj` is None, this should return True if the given request has + permission to delete *any* object of the given type. + """ + opts = self.opts + return request.user.has_perm(opts.app_label + '.' + opts.get_delete_permission()) + + def queryset(self, request): + """ + Returns a QuerySet of all model instances that can be edited by the + admin site. This is used by changelist_view. + """ + qs = self.model._default_manager.get_query_set() + # TODO: this should be handled by some parameter to the ChangeList. + ordering = self.ordering or () # otherwise we might try to *None, which is bad ;) + if ordering: + qs = qs.order_by(*ordering) + return qs + + def get_fieldsets(self, request, obj=None): + "Hook for specifying fieldsets for the add form." + if self.declared_fieldsets: + return self.declared_fieldsets + form = self.get_form(request, obj) + return [(None, {'fields': form.base_fields.keys()})] + + def get_form(self, request, obj=None, **kwargs): + """ + Returns a Form class for use in the admin add view. This is used by + add_view and change_view. + """ + if self.declared_fieldsets: + fields = flatten_fieldsets(self.declared_fieldsets) + else: + fields = None + defaults = { + "form": self.form, + "fields": fields, + "formfield_callback": self.formfield_for_dbfield, + } + defaults.update(kwargs) + return modelform_factory(self.model, **defaults) + + def get_formsets(self, request, obj=None): + for inline in self.inline_instances: + yield inline.get_formset(request, obj) + + def log_addition(self, request, object): + """ + Log that an object has been successfully added. + + The default implementation creates an admin LogEntry object. + """ + from django.contrib.admin.models import LogEntry, ADDITION + LogEntry.objects.log_action( + user_id = request.user.pk, + content_type_id = ContentType.objects.get_for_model(object).pk, + object_id = object.pk, + object_repr = force_unicode(object), + action_flag = ADDITION + ) + + def log_change(self, request, object, message): + """ + Log that an object has been successfully changed. + + The default implementation creates an admin LogEntry object. + """ + from django.contrib.admin.models import LogEntry, CHANGE + LogEntry.objects.log_action( + user_id = request.user.pk, + content_type_id = ContentType.objects.get_for_model(object).pk, + object_id = object.pk, + object_repr = force_unicode(object), + action_flag = CHANGE, + change_message = message + ) + + def log_deletion(self, request, object, object_repr): + """ + Log that an object has been successfully deleted. Note that since the + object is deleted, it might no longer be safe to call *any* methods + on the object, hence this method getting object_repr. + + The default implementation creates an admin LogEntry object. + """ + from django.contrib.admin.models import LogEntry, DELETION + LogEntry.objects.log_action( + user_id = request.user.id, + content_type_id = ContentType.objects.get_for_model(self.model).pk, + object_id = object.pk, + object_repr = object_repr, + action_flag = DELETION + ) + + + def construct_change_message(self, request, form, formsets): + """ + Construct a change message from a changed object. + """ + change_message = [] + if form.changed_data: + change_message.append(_('Changed %s.') % get_text_list(form.changed_data, _('and'))) + + if formsets: + for formset in formsets: + for added_object in formset.new_objects: + change_message.append(_('Added %(name)s "%(object)s".') + % {'name': added_object._meta.verbose_name, + 'object': added_object}) + for changed_object, changed_fields in formset.changed_objects: + change_message.append(_('Changed %(list)s for %(name)s "%(object)s".') + % {'list': get_text_list(changed_fields, _('and')), + 'name': changed_object._meta.verbose_name, + 'object': changed_object}) + for deleted_object in formset.deleted_objects: + change_message.append(_('Deleted %(name)s "%(object)s".') + % {'name': deleted_object._meta.verbose_name, + 'object': deleted_object}) + change_message = ' '.join(change_message) + return change_message or _('No fields changed.') + + def message_user(self, request, message): + """ + Send a message to the user. The default implementation + posts a message using the auth Message object. + """ + request.user.message_set.create(message=message) + + def save_form(self, request, form, change): + """ + Given a ModelForm return an unsaved instance. ``change`` is True if + the object is being changed, and False if it's being added. + """ + return form.save(commit=False) + + def save_model(self, request, obj, form, change): + """ + Given a model instance save it to the database. + """ + obj.save() + + def save_formset(self, request, form, formset, change): + """ + Given an inline formset save it to the database. + """ + formset.save() + + def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None): + opts = self.model._meta + app_label = opts.app_label + ordered_objects = opts.get_ordered_objects() + context.update({ + 'add': add, + 'change': change, + 'has_add_permission': self.has_add_permission(request), + 'has_change_permission': self.has_change_permission(request, obj), + 'has_delete_permission': self.has_delete_permission(request, obj), + 'has_file_field': True, # FIXME - this should check if form or formsets have a FileField, + 'has_absolute_url': hasattr(self.model, 'get_absolute_url'), + 'ordered_objects': ordered_objects, + 'form_url': mark_safe(form_url), + 'opts': opts, + 'content_type_id': ContentType.objects.get_for_model(self.model).id, + 'save_as': self.save_as, + 'save_on_top': self.save_on_top, + 'root_path': self.admin_site.root_path, + }) + return render_to_response(self.change_form_template or [ + "admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()), + "admin/%s/change_form.html" % app_label, + "admin/change_form.html" + ], context, context_instance=template.RequestContext(request)) + + def response_add(self, request, obj, post_url_continue='../%s/'): + """ + Determines the HttpResponse for the add_view stage. + """ + opts = obj._meta + pk_value = obj._get_pk_val() + + msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj)} + # Here, we distinguish between different save types by checking for + # the presence of keys in request.POST. + if request.POST.has_key("_continue"): + self.message_user(request, msg + ' ' + _("You may edit it again below.")) + if request.POST.has_key("_popup"): + post_url_continue += "?_popup=1" + return HttpResponseRedirect(post_url_continue % pk_value) + + if request.POST.has_key("_popup"): + return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script>' % \ + # escape() calls force_unicode. + (escape(pk_value), escape(obj))) + elif request.POST.has_key("_addanother"): + self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name))) + return HttpResponseRedirect(request.path) + else: + self.message_user(request, msg) + + # Figure out where to redirect. If the user has change permission, + # redirect to the change-list page for this object. Otherwise, + # redirect to the admin index. + if self.has_change_permission(request, None): + post_url = '../' + else: + post_url = '../../../' + return HttpResponseRedirect(post_url) + + def response_change(self, request, obj): + """ + Determines the HttpResponse for the change_view stage. + """ + opts = obj._meta + pk_value = obj._get_pk_val() + + msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj)} + if request.POST.has_key("_continue"): + self.message_user(request, msg + ' ' + _("You may edit it again below.")) + if request.REQUEST.has_key('_popup'): + return HttpResponseRedirect(request.path + "?_popup=1") + else: + return HttpResponseRedirect(request.path) + elif request.POST.has_key("_saveasnew"): + msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': force_unicode(opts.verbose_name), 'obj': obj} + self.message_user(request, msg) + return HttpResponseRedirect("../%s/" % pk_value) + elif request.POST.has_key("_addanother"): + self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name))) + return HttpResponseRedirect("../add/") + else: + self.message_user(request, msg) + return HttpResponseRedirect("../") + + def add_view(self, request, form_url='', extra_context=None): + "The 'add' admin view for this model." + model = self.model + opts = model._meta + app_label = opts.app_label + + if not self.has_add_permission(request): + raise PermissionDenied + + if self.has_change_permission(request, None): + # redirect to list view + post_url = '../' + else: + # Object list will give 'Permission Denied', so go back to admin home + post_url = '../../../' + + ModelForm = self.get_form(request) + formsets = [] + if request.method == 'POST': + form = ModelForm(request.POST, request.FILES) + if form.is_valid(): + form_validated = True + new_object = self.save_form(request, form, change=False) + else: + form_validated = False + new_object = self.model() + for FormSet in self.get_formsets(request): + formset = FormSet(data=request.POST, files=request.FILES, + instance=new_object, + save_as_new=request.POST.has_key("_saveasnew")) + formsets.append(formset) + if all_valid(formsets) and form_validated: + self.save_model(request, new_object, form, change=False) + form.save_m2m() + for formset in formsets: + self.save_formset(request, form, formset, change=False) + + self.log_addition(request, new_object) + return self.response_add(request, new_object) + else: + form = ModelForm(initial=dict(request.GET.items())) + for FormSet in self.get_formsets(request): + formset = FormSet(instance=self.model()) + formsets.append(formset) + + adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)), self.prepopulated_fields) + media = self.media + adminForm.media + + inline_admin_formsets = [] + for inline, formset in zip(self.inline_instances, formsets): + fieldsets = list(inline.get_fieldsets(request)) + inline_admin_formset = helpers.InlineAdminFormSet(inline, formset, fieldsets) + inline_admin_formsets.append(inline_admin_formset) + media = media + inline_admin_formset.media + + context = { + 'title': _('Add %s') % force_unicode(opts.verbose_name), + 'adminform': adminForm, + 'is_popup': request.REQUEST.has_key('_popup'), + 'show_delete': False, + 'media': mark_safe(media), + 'inline_admin_formsets': inline_admin_formsets, + 'errors': helpers.AdminErrorList(form, formsets), + 'root_path': self.admin_site.root_path, + 'app_label': app_label, + } + context.update(extra_context or {}) + return self.render_change_form(request, context, add=True) + add_view = transaction.commit_on_success(add_view) + + def change_view(self, request, object_id, extra_context=None): + "The 'change' admin view for this model." + model = self.model + opts = model._meta + app_label = opts.app_label + + try: + obj = model._default_manager.get(pk=object_id) + except model.DoesNotExist: + # Don't raise Http404 just yet, because we haven't checked + # permissions yet. We don't want an unauthenticated user to be able + # to determine whether a given object exists. + obj = None + + if not self.has_change_permission(request, obj): + raise PermissionDenied + + if obj is None: + raise Http404('%s object with primary key %r does not exist.' % (force_unicode(opts.verbose_name), escape(object_id))) + + if request.POST and request.POST.has_key("_saveasnew"): + return self.add_view(request, form_url='../../add/') + + ModelForm = self.get_form(request, obj) + formsets = [] + if request.method == 'POST': + form = ModelForm(request.POST, request.FILES, instance=obj) + if form.is_valid(): + form_validated = True + new_object = self.save_form(request, form, change=True) + else: + form_validated = False + new_object = obj + for FormSet in self.get_formsets(request, new_object): + formset = FormSet(request.POST, request.FILES, + instance=new_object) + formsets.append(formset) + + if all_valid(formsets) and form_validated: + self.save_model(request, new_object, form, change=True) + form.save_m2m() + for formset in formsets: + self.save_formset(request, form, formset, change=True) + + change_message = self.construct_change_message(request, form, formsets) + self.log_change(request, new_object, change_message) + return self.response_change(request, new_object) + else: + form = ModelForm(instance=obj) + for FormSet in self.get_formsets(request, obj): + formset = FormSet(instance=obj) + formsets.append(formset) + + adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj), self.prepopulated_fields) + media = self.media + adminForm.media + + inline_admin_formsets = [] + for inline, formset in zip(self.inline_instances, formsets): + fieldsets = list(inline.get_fieldsets(request, obj)) + inline_admin_formset = helpers.InlineAdminFormSet(inline, formset, fieldsets) + inline_admin_formsets.append(inline_admin_formset) + media = media + inline_admin_formset.media + + context = { + 'title': _('Change %s') % force_unicode(opts.verbose_name), + 'adminform': adminForm, + 'object_id': object_id, + 'original': obj, + 'is_popup': request.REQUEST.has_key('_popup'), + 'media': mark_safe(media), + 'inline_admin_formsets': inline_admin_formsets, + 'errors': helpers.AdminErrorList(form, formsets), + 'root_path': self.admin_site.root_path, + 'app_label': app_label, + } + context.update(extra_context or {}) + return self.render_change_form(request, context, change=True, obj=obj) + change_view = transaction.commit_on_success(change_view) + + def changelist_view(self, request, extra_context=None): + "The 'change list' admin view for this model." + from django.contrib.admin.views.main import ChangeList, ERROR_FLAG + opts = self.model._meta + app_label = opts.app_label + if not self.has_change_permission(request, None): + raise PermissionDenied + try: + cl = ChangeList(request, self.model, self.list_display, self.list_display_links, self.list_filter, + self.date_hierarchy, self.search_fields, self.list_select_related, self.list_per_page, self) + except IncorrectLookupParameters: + # Wacky lookup parameters were given, so redirect to the main + # changelist page, without parameters, and pass an 'invalid=1' + # parameter via the query string. If wacky parameters were given and + # the 'invalid=1' parameter was already in the query string, something + # is screwed up with the database, so display an error page. + if ERROR_FLAG in request.GET.keys(): + return render_to_response('admin/invalid_setup.html', {'title': _('Database error')}) + return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1') + + context = { + 'title': cl.title, + 'is_popup': cl.is_popup, + 'cl': cl, + 'has_add_permission': self.has_add_permission(request), + 'root_path': self.admin_site.root_path, + 'app_label': app_label, + } + context.update(extra_context or {}) + return render_to_response(self.change_list_template or [ + 'admin/%s/%s/change_list.html' % (app_label, opts.object_name.lower()), + 'admin/%s/change_list.html' % app_label, + 'admin/change_list.html' + ], context, context_instance=template.RequestContext(request)) + + def delete_view(self, request, object_id, extra_context=None): + "The 'delete' admin view for this model." + opts = self.model._meta + app_label = opts.app_label + + try: + obj = self.model._default_manager.get(pk=object_id) + except self.model.DoesNotExist: + # Don't raise Http404 just yet, because we haven't checked + # permissions yet. We don't want an unauthenticated user to be able + # to determine whether a given object exists. + obj = None + + if not self.has_delete_permission(request, obj): + raise PermissionDenied + + if obj is None: + raise Http404('%s object with primary key %r does not exist.' % (force_unicode(opts.verbose_name), escape(object_id))) + + # Populate deleted_objects, a data structure of all related objects that + # will also be deleted. + deleted_objects = [mark_safe(u'%s: <a href="../../%s/">%s</a>' % (escape(force_unicode(capfirst(opts.verbose_name))), quote(object_id), escape(obj))), []] + perms_needed = set() + get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1, self.admin_site) + + if request.POST: # The user has already confirmed the deletion. + if perms_needed: + raise PermissionDenied + obj_display = str(obj) + obj.delete() + + self.log_deletion(request, obj, obj_display) + self.message_user(request, _('The %(name)s "%(obj)s" was deleted successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj_display)}) + + if not self.has_change_permission(request, None): + return HttpResponseRedirect("../../../../") + return HttpResponseRedirect("../../") + + context = { + "title": _("Are you sure?"), + "object_name": force_unicode(opts.verbose_name), + "object": obj, + "deleted_objects": deleted_objects, + "perms_lacking": perms_needed, + "opts": opts, + "root_path": self.admin_site.root_path, + "app_label": app_label, + } + context.update(extra_context or {}) + return render_to_response(self.delete_confirmation_template or [ + "admin/%s/%s/delete_confirmation.html" % (app_label, opts.object_name.lower()), + "admin/%s/delete_confirmation.html" % app_label, + "admin/delete_confirmation.html" + ], context, context_instance=template.RequestContext(request)) + + def history_view(self, request, object_id, extra_context=None): + "The 'history' admin view for this model." + from django.contrib.admin.models import LogEntry + model = self.model + opts = model._meta + action_list = LogEntry.objects.filter( + object_id = object_id, + content_type__id__exact = ContentType.objects.get_for_model(model).id + ).select_related().order_by('action_time') + # If no history was found, see whether this object even exists. + obj = get_object_or_404(model, pk=object_id) + context = { + 'title': _('Change history: %s') % force_unicode(obj), + 'action_list': action_list, + 'module_name': capfirst(force_unicode(opts.verbose_name_plural)), + 'object': obj, + 'root_path': self.admin_site.root_path, + } + context.update(extra_context or {}) + return render_to_response(self.object_history_template or [ + "admin/%s/%s/object_history.html" % (opts.app_label, opts.object_name.lower()), + "admin/%s/object_history.html" % opts.app_label, + "admin/object_history.html" + ], context, context_instance=template.RequestContext(request)) + +class InlineModelAdmin(BaseModelAdmin): + """ + Options for inline editing of ``model`` instances. + + Provide ``name`` to specify the attribute name of the ``ForeignKey`` from + ``model`` to its parent. This is required if ``model`` has more than one + ``ForeignKey`` to its parent. + """ + model = None + fk_name = None + formset = BaseInlineFormSet + extra = 3 + max_num = 0 + template = None + verbose_name = None + verbose_name_plural = None + + def __init__(self, parent_model, admin_site): + self.admin_site = admin_site + self.parent_model = parent_model + self.opts = self.model._meta + super(InlineModelAdmin, self).__init__() + if self.verbose_name is None: + self.verbose_name = self.model._meta.verbose_name + if self.verbose_name_plural is None: + self.verbose_name_plural = self.model._meta.verbose_name_plural + + def _media(self): + from django.conf import settings + js = [] + if self.prepopulated_fields: + js.append('js/urlify.js') + if self.filter_vertical or self.filter_horizontal: + js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js']) + return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js]) + media = property(_media) + + def get_formset(self, request, obj=None, **kwargs): + """Returns a BaseInlineFormSet class for use in admin add/change views.""" + if self.declared_fieldsets: + fields = flatten_fieldsets(self.declared_fieldsets) + else: + fields = None + defaults = { + "form": self.form, + "formset": self.formset, + "fk_name": self.fk_name, + "fields": fields, + "formfield_callback": self.formfield_for_dbfield, + "extra": self.extra, + "max_num": self.max_num, + } + defaults.update(kwargs) + return inlineformset_factory(self.parent_model, self.model, **defaults) + + def get_fieldsets(self, request, obj=None): + if self.declared_fieldsets: + return self.declared_fieldsets + form = self.get_formset(request).form + return [(None, {'fields': form.base_fields.keys()})] + +class StackedInline(InlineModelAdmin): + template = 'admin/edit_inline/stacked.html' + +class TabularInline(InlineModelAdmin): + template = 'admin/edit_inline/tabular.html' diff --git a/webapp/django/contrib/admin/sites.py b/webapp/django/contrib/admin/sites.py new file mode 100644 index 0000000000..25ce375ea7 --- /dev/null +++ b/webapp/django/contrib/admin/sites.py @@ -0,0 +1,413 @@ +import base64 +import cPickle as pickle +import re + +from django import http, template +from django.contrib.admin import ModelAdmin +from django.contrib.auth import authenticate, login +from django.db.models.base import ModelBase +from django.core.exceptions import ImproperlyConfigured +from django.shortcuts import render_to_response +from django.utils.safestring import mark_safe +from django.utils.text import capfirst +from django.utils.translation import ugettext_lazy, ugettext as _ +from django.views.decorators.cache import never_cache +from django.conf import settings +from django.utils.hashcompat import md5_constructor + +ERROR_MESSAGE = ugettext_lazy("Please enter a correct username and password. Note that both fields are case-sensitive.") +LOGIN_FORM_KEY = 'this_is_the_login_form' + +class AlreadyRegistered(Exception): + pass + +class NotRegistered(Exception): + pass + +def _encode_post_data(post_data): + pickled = pickle.dumps(post_data) + pickled_md5 = md5_constructor(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_constructor(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) + +class AdminSite(object): + """ + An AdminSite object encapsulates an instance of the Django admin application, ready + to be hooked in to your URLConf. Models are registered with the AdminSite using the + register() method, and the root() method can then be used as a Django view function + that presents a full admin interface for the collection of registered models. + """ + + index_template = None + login_template = None + app_index_template = None + + def __init__(self): + self._registry = {} # model_class class -> admin_class instance + + def register(self, model_or_iterable, admin_class=None, **options): + """ + Registers the given model(s) with the given admin class. + + The model(s) should be Model classes, not instances. + + If an admin class isn't given, it will use ModelAdmin (the default + admin options). If keyword arguments are given -- e.g., list_display -- + they'll be applied as options to the admin class. + + If a model is already registered, this will raise AlreadyRegistered. + """ + # Don't import the humongous validation code unless required + if admin_class and settings.DEBUG: + from django.contrib.admin.validation import validate + else: + validate = lambda model, adminclass: None + + if not admin_class: + admin_class = ModelAdmin + if isinstance(model_or_iterable, ModelBase): + model_or_iterable = [model_or_iterable] + for model in model_or_iterable: + if model in self._registry: + raise AlreadyRegistered('The model %s is already registered' % model.__name__) + + # If we got **options then dynamically construct a subclass of + # admin_class with those **options. + if options: + # For reasons I don't quite understand, without a __module__ + # the created class appears to "live" in the wrong place, + # which causes issues later on. + options['__module__'] = __name__ + admin_class = type("%sAdmin" % model.__name__, (admin_class,), options) + + # Validate (which might be a no-op) + validate(admin_class, model) + + # Instantiate the admin class to save in the registry + self._registry[model] = admin_class(model, self) + + def unregister(self, model_or_iterable): + """ + Unregisters the given model(s). + + If a model isn't already registered, this will raise NotRegistered. + """ + if isinstance(model_or_iterable, ModelBase): + model_or_iterable = [model_or_iterable] + for model in model_or_iterable: + if model not in self._registry: + raise NotRegistered('The model %s is not registered' % model.__name__) + del self._registry[model] + + def has_permission(self, request): + """ + Returns True if the given HttpRequest has permission to view + *at least one* page in the admin site. + """ + return request.user.is_authenticated() and request.user.is_staff + + def check_dependencies(self): + """ + Check that all things needed to run the admin have been correctly installed. + + The default implementation checks that LogEntry, ContentType and the + auth context processor are installed. + """ + from django.conf import settings + from django.contrib.admin.models import LogEntry + from django.contrib.contenttypes.models import ContentType + + if not LogEntry._meta.installed: + raise ImproperlyConfigured("Put 'django.contrib.admin' in your INSTALLED_APPS setting in order to use the admin application.") + if not ContentType._meta.installed: + raise ImproperlyConfigured("Put 'django.contrib.contenttypes' in your INSTALLED_APPS setting in order to use the admin application.") + if 'django.core.context_processors.auth' not in settings.TEMPLATE_CONTEXT_PROCESSORS: + raise ImproperlyConfigured("Put 'django.core.context_processors.auth' in your TEMPLATE_CONTEXT_PROCESSORS setting in order to use the admin application.") + + def root(self, request, url): + """ + Handles main URL routing for the admin app. + + `url` is the remainder of the URL -- e.g. 'comments/comment/'. + """ + if request.method == 'GET' and not request.path.endswith('/'): + return http.HttpResponseRedirect(request.path + '/') + + if settings.DEBUG: + self.check_dependencies() + + # Figure out the admin base URL path and stash it for later use + self.root_path = re.sub(re.escape(url) + '$', '', request.path) + + url = url.rstrip('/') # Trim trailing slash, if it exists. + + # The 'logout' view doesn't require that the person is logged in. + if url == 'logout': + return self.logout(request) + + # Check permission to continue or display login form. + if not self.has_permission(request): + return self.login(request) + + if url == '': + return self.index(request) + elif url == 'password_change': + return self.password_change(request) + elif url == 'password_change/done': + return self.password_change_done(request) + elif url == 'jsi18n': + return self.i18n_javascript(request) + # urls starting with 'r/' are for the "show in web" links + elif url.startswith('r/'): + from django.views.defaults import shortcut + return shortcut(request, *url.split('/')[1:]) + else: + if '/' in url: + return self.model_page(request, *url.split('/', 2)) + else: + return self.app_index(request, url) + + raise http.Http404('The requested admin page does not exist.') + + def model_page(self, request, app_label, model_name, rest_of_url=None): + """ + Handles the model-specific functionality of the admin site, delegating + to the appropriate ModelAdmin class. + """ + from django.db import models + model = models.get_model(app_label, model_name) + if model is None: + raise http.Http404("App %r, model %r, not found." % (app_label, model_name)) + try: + admin_obj = self._registry[model] + except KeyError: + raise http.Http404("This model exists but has not been registered with the admin site.") + return admin_obj(request, rest_of_url) + model_page = never_cache(model_page) + + def password_change(self, request): + """ + Handles the "change password" task -- both form display and validation. + """ + from django.contrib.auth.views import password_change + return password_change(request, + post_change_redirect='%spassword_change/done/' % self.root_path) + + def password_change_done(self, request): + """ + Displays the "success" page after a password change. + """ + from django.contrib.auth.views import password_change_done + return password_change_done(request) + + def i18n_javascript(self, request): + """ + Displays the i18n JavaScript that the Django admin requires. + + This takes into account the USE_I18N setting. If it's set to False, the + generated JavaScript will be leaner and faster. + """ + if settings.USE_I18N: + from django.views.i18n import javascript_catalog + else: + from django.views.i18n import null_javascript_catalog as javascript_catalog + return javascript_catalog(request, packages='django.conf') + + def logout(self, request): + """ + Logs out the user for the given HttpRequest. + + This should *not* assume the user is already logged in. + """ + from django.contrib.auth.views import logout + return logout(request) + logout = never_cache(logout) + + def login(self, request): + """ + Displays the login form for the given HttpRequest. + """ + from django.contrib.auth.models import User + + # If this isn't already the login page, display it. + if not request.POST.has_key(LOGIN_FORM_KEY): + if request.POST: + message = _("Please log in again, because your session has expired. Don't worry: Your submission has been saved.") + else: + message = "" + return self.display_login_form(request, message) + + # Check that the user accepts cookies. + if not request.session.test_cookie_worked(): + message = _("Looks like your browser isn't configured to accept cookies. Please enable cookies, reload this page, and try again.") + return self.display_login_form(request, message) + else: + request.session.delete_test_cookie() + + # Check the password. + username = request.POST.get('username', None) + password = request.POST.get('password', None) + user = authenticate(username=username, password=password) + if user is None: + message = ERROR_MESSAGE + if u'@' in username: + # Mistakenly entered e-mail address instead of username? Look it up. + try: + user = User.objects.get(email=username) + except (User.DoesNotExist, User.MultipleObjectsReturned): + message = _("Usernames cannot contain the '@' character.") + else: + if user.check_password(password): + message = _("Your e-mail address is not your username." + " Try '%s' instead.") % user.username + else: + message = _("Usernames cannot contain the '@' character.") + return self.display_login_form(request, message) + + # The user data is correct; log in the user in and continue. + else: + if user.is_active and user.is_staff: + login(request, user) + if request.POST.has_key('post_data'): + post_data = _decode_post_data(request.POST['post_data']) + if post_data and not post_data.has_key(LOGIN_FORM_KEY): + # overwrite request.POST with the saved post_data, and continue + request.POST = post_data + request.user = user + return self.root(request, request.path.split(self.root_path)[-1]) + else: + return http.HttpResponseRedirect(request.get_full_path()) + else: + return self.display_login_form(request, ERROR_MESSAGE) + login = never_cache(login) + + def index(self, request, extra_context=None): + """ + Displays the main admin index page, which lists all of the installed + apps that have been registered in this site. + """ + app_dict = {} + user = request.user + for model, model_admin in self._registry.items(): + app_label = model._meta.app_label + has_module_perms = user.has_module_perms(app_label) + + if has_module_perms: + perms = { + 'add': model_admin.has_add_permission(request), + 'change': model_admin.has_change_permission(request), + 'delete': model_admin.has_delete_permission(request), + } + + # Check whether user has any perm for this module. + # If so, add the module to the model_list. + if True in perms.values(): + model_dict = { + 'name': capfirst(model._meta.verbose_name_plural), + 'admin_url': mark_safe('%s/%s/' % (app_label, model.__name__.lower())), + 'perms': perms, + } + if app_label in app_dict: + app_dict[app_label]['models'].append(model_dict) + else: + app_dict[app_label] = { + 'name': app_label.title(), + 'app_url': app_label, + 'has_module_perms': has_module_perms, + 'models': [model_dict], + } + + # Sort the apps alphabetically. + app_list = app_dict.values() + app_list.sort(lambda x, y: cmp(x['name'], y['name'])) + + # Sort the models alphabetically within each app. + for app in app_list: + app['models'].sort(lambda x, y: cmp(x['name'], y['name'])) + + context = { + 'title': _('Site administration'), + 'app_list': app_list, + 'root_path': self.root_path, + } + context.update(extra_context or {}) + return render_to_response(self.index_template or 'admin/index.html', context, + context_instance=template.RequestContext(request) + ) + index = never_cache(index) + + def display_login_form(self, request, error_message='', extra_context=None): + request.session.set_test_cookie() + if request.POST and request.POST.has_key('post_data'): + # User has failed login BUT has previously saved post data. + post_data = request.POST['post_data'] + elif request.POST: + # User's session must have expired; save their post data. + post_data = _encode_post_data(request.POST) + else: + post_data = _encode_post_data({}) + + context = { + 'title': _('Log in'), + 'app_path': request.get_full_path(), + 'post_data': post_data, + 'error_message': error_message, + 'root_path': self.root_path, + } + context.update(extra_context or {}) + return render_to_response(self.login_template or 'admin/login.html', context, + context_instance=template.RequestContext(request) + ) + + def app_index(self, request, app_label, extra_context=None): + user = request.user + has_module_perms = user.has_module_perms(app_label) + app_dict = {} + for model, model_admin in self._registry.items(): + if app_label == model._meta.app_label: + if has_module_perms: + perms = { + 'add': user.has_perm("%s.%s" % (app_label, model._meta.get_add_permission())), + 'change': user.has_perm("%s.%s" % (app_label, model._meta.get_change_permission())), + 'delete': user.has_perm("%s.%s" % (app_label, model._meta.get_delete_permission())), + } + # Check whether user has any perm for this module. + # If so, add the module to the model_list. + if True in perms.values(): + model_dict = { + 'name': capfirst(model._meta.verbose_name_plural), + 'admin_url': '%s/' % model.__name__.lower(), + 'perms': perms, + } + if app_dict: + app_dict['models'].append(model_dict), + else: + app_dict = { + 'name': app_label.title(), + 'app_url': '', + 'has_module_perms': has_module_perms, + 'models': [model_dict], + } + if not app_dict: + raise http.Http404('The requested admin page does not exist.') + # Sort the models alphabetically within each app. + app_dict['models'].sort(lambda x, y: cmp(x['name'], y['name'])) + context = { + 'title': _('%s administration' % capfirst(app_label)), + 'app_list': [app_dict] + } + context.update(extra_context or {}) + return render_to_response(self.app_index_template or 'admin/app_index.html', context, + context_instance=template.RequestContext(request) + ) + +# This global object represents the default admin site, for the common case. +# You can instantiate AdminSite in your own code to create a custom admin site. +site = AdminSite() diff --git a/webapp/django/contrib/admin/templates/admin/404.html b/webapp/django/contrib/admin/templates/admin/404.html new file mode 100644 index 0000000000..9bf4293e76 --- /dev/null +++ b/webapp/django/contrib/admin/templates/admin/404.html @@ -0,0 +1,12 @@ +{% extends "admin/base_site.html" %} +{% load i18n %} + +{% block title %}{% trans 'Page not found' %}{% endblock %} + +{% block content %} + +<h2>{% trans 'Page not found' %}</h2> + +<p>{% trans "We're sorry, but the requested page could not be found." %}</p> + +{% endblock %} diff --git a/webapp/django/contrib/admin/templates/admin/500.html b/webapp/django/contrib/admin/templates/admin/500.html new file mode 100644 index 0000000000..b30e43170d --- /dev/null +++ b/webapp/django/contrib/admin/templates/admin/500.html @@ -0,0 +1,12 @@ +{% extends "admin/base_site.html" %} +{% load i18n %} + +{% block breadcrumbs %}<div class="breadcrumbs"><a href="/">{% trans "Home" %}</a> › {% trans "Server error" %}</div>{% endblock %} + +{% block title %}{% trans 'Server error (500)' %}{% endblock %} + +{% block content %} +<h1>{% trans 'Server Error <em>(500)</em>' %}</h1> +<p>{% trans "There's been an error. It's been reported to the site administrators via e-mail and should be fixed shortly. Thanks for your patience." %}</p> + +{% endblock %} diff --git a/webapp/django/contrib/admin/templates/admin/app_index.html b/webapp/django/contrib/admin/templates/admin/app_index.html new file mode 100644 index 0000000000..120433d708 --- /dev/null +++ b/webapp/django/contrib/admin/templates/admin/app_index.html @@ -0,0 +1,15 @@ +{% extends "admin/index.html" %} +{% load i18n %} + +{% if not is_popup %} + +{% block breadcrumbs %} +<div class="breadcrumbs"><a href="../"> +{% trans "Home" %}</a> › +{% for app in app_list %} +{% blocktrans with app.name as name %}{{ name }}{% endblocktrans %} +{% endfor %}</div>{% endblock %} + +{% endif %} + +{% block sidebar %}{% endblock %}
\ No newline at end of file diff --git a/webapp/django/contrib/admin/templates/admin/auth/user/add_form.html b/webapp/django/contrib/admin/templates/admin/auth/user/add_form.html new file mode 100644 index 0000000000..65824a6b7d --- /dev/null +++ b/webapp/django/contrib/admin/templates/admin/auth/user/add_form.html @@ -0,0 +1,33 @@ +{% extends "admin/change_form.html" %} +{% load i18n %} + +{% block after_field_sets %} + +<p>{% trans "First, enter a username and password. Then, you'll be able to edit more user options." %}</p> + +<fieldset class="module aligned"> + +<div class="form-row"> + {{ form.username.errors }} + {# TODO: get required class on label_tag #} + <label for="id_username" class="required">{% trans 'Username' %}:</label> {{ form.username }} + <p class="help">{{ form.username.help_text }}</p> +</div> + +<div class="form-row"> + {{ form.password1.errors }} + {# TODO: get required class on label_tag #} + <label for="id_password1" class="required">{% trans 'Password' %}:</label> {{ form.password1 }} +</div> + +<div class="form-row"> + {{ form.password2.errors }} + {# TODO: get required class on label_tag #} + <label for="id_password2" class="required">{% trans 'Password (again)' %}:</label> {{ form.password2 }} + <p class="help">{% trans 'Enter the same password as above, for verification.' %}</p> +</div> + +<script type="text/javascript">document.getElementById("id_username").focus();</script> + +</fieldset> +{% endblock %} diff --git a/webapp/django/contrib/admin/templates/admin/auth/user/change_password.html b/webapp/django/contrib/admin/templates/admin/auth/user/change_password.html new file mode 100644 index 0000000000..f1c4a8d34a --- /dev/null +++ b/webapp/django/contrib/admin/templates/admin/auth/user/change_password.html @@ -0,0 +1,52 @@ +{% extends "admin/base_site.html" %} +{% load i18n admin_modify adminmedia %} +{% block extrahead %}{{ block.super }} +<script type="text/javascript" src="../../../../jsi18n/"></script> +{% endblock %} +{% block stylesheet %}{% admin_media_prefix %}css/forms.css{% endblock %} +{% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %} +{% block breadcrumbs %}{% if not is_popup %} +<div class="breadcrumbs"> + <a href="../../../../">{% trans "Home" %}</a> › + <a href="../../">{{ opts.verbose_name_plural|capfirst|escape }}</a> › + <a href="../">{{ original|truncatewords:"18"|escape }}</a> › + {% trans 'Change password' %} +</div> +{% endif %}{% endblock %} +{% block content %}<div id="content-main"> +<form action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% block form_top %}{% endblock %} +<div> +{% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %} +{% if form.errors %} + <p class="errornote"> + {% blocktrans count form.errors.items|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %} + </p> +{% endif %} + +<p>{% blocktrans with original.username|escape as username %}Enter a new password for the user <strong>{{ username }}</strong>.{% endblocktrans %}</p> + +<fieldset class="module aligned"> + +<div class="form-row"> + {{ form.password1.errors }} + {# TODO: get required class on label_tag #} + <label for="id_password1" class="required">{% trans 'Password' %}:</label> {{ form.password1 }} +</div> + +<div class="form-row"> + {{ form.password2.errors }} + {# TODO: get required class on label_tag #} + <label for="id_password2" class="required">{% trans 'Password (again)' %}:</label> {{ form.password2 }} + <p class="help">{% trans 'Enter the same password as above, for verification.' %}</p> +</div> + +</fieldset> + +<div class="submit-row"> +<input type="submit" value="{% trans 'Change password' %}" class="default" /> +</div> + +<script type="text/javascript">document.getElementById("id_password1").focus();</script> +</div> +</form></div> +{% endblock %} diff --git a/webapp/django/contrib/admin/templates/admin/base.html b/webapp/django/contrib/admin/templates/admin/base.html new file mode 100644 index 0000000000..479e18b2ee --- /dev/null +++ b/webapp/django/contrib/admin/templates/admin/base.html @@ -0,0 +1,55 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="{{ LANGUAGE_CODE }}" xml:lang="{{ LANGUAGE_CODE }}" {% if LANGUAGE_BIDI %}dir="rtl"{% endif %}> +<head> +<title>{% block title %}{% endblock %}</title> +<link rel="stylesheet" type="text/css" href="{% block stylesheet %}{% load adminmedia %}{% admin_media_prefix %}css/base.css{% endblock %}" /> +{% if LANGUAGE_BIDI %}<link rel="stylesheet" type="text/css" href="{% block stylesheet_rtl %}{% admin_media_prefix %}css/rtl.css{% endblock %}" />{% endif %} +{% block extrastyle %}{% endblock %} +{% block extrahead %}{% endblock %} +{% block blockbots %}<meta name="robots" content="NONE,NOARCHIVE" />{% endblock %} +</head> +{% load i18n %} + +<body class="{% if is_popup %}popup {% endif %}{% block bodyclass %}{% endblock %}"> + +<!-- Container --> +<div id="container"> + + {% if not is_popup %} + <!-- Header --> + <div id="header"> + <div id="branding"> + {% block branding %}{% endblock %} + </div> + {% if user.is_authenticated and user.is_staff %} + <div id="user-tools">{% trans 'Welcome,' %} <strong>{% if user.first_name %}{{ user.first_name|escape }}{% else %}{{ user.username }}{% endif %}</strong>. {% block userlinks %}<a href="{{ root_path }}doc/">{% trans 'Documentation' %}</a> / <a href="{{ root_path }}password_change/">{% trans 'Change password' %}</a> / <a href="{{ root_path }}logout/">{% trans 'Log out' %}</a>{% endblock %}</div> + {% endif %} + {% block nav-global %}{% endblock %} + </div> + <!-- END Header --> + {% block breadcrumbs %}<div class="breadcrumbs"><a href="/">{% trans 'Home' %}</a>{% if title %} › {{ title|escape }}{% endif %}</div>{% endblock %} + {% endif %} + + {% if messages %} + <ul class="messagelist">{% for message in messages %}<li>{{ message|escape }}</li>{% endfor %}</ul> + {% endif %} + + <!-- Content --> + <div id="content" class="{% block coltype %}colM{% endblock %}"> + {% block pretitle %}{% endblock %} + {% block content_title %}{% if title %}<h1>{{ title|escape }}</h1>{% endif %}{% endblock %} + {% block content %} + {% block object-tools %}{% endblock %} + {{ content }} + {% endblock %} + {% block sidebar %}{% endblock %} + <br class="clear" /> + </div> + <!-- END Content --> + + {% block footer %}<div id="footer"></div>{% endblock %} +</div> +<!-- END Container --> + +</body> +</html> diff --git a/webapp/django/contrib/admin/templates/admin/base_site.html b/webapp/django/contrib/admin/templates/admin/base_site.html new file mode 100644 index 0000000000..b867bd29bd --- /dev/null +++ b/webapp/django/contrib/admin/templates/admin/base_site.html @@ -0,0 +1,10 @@ +{% extends "admin/base.html" %} +{% load i18n %} + +{% block title %}{{ title }} | {% trans 'Django site admin' %}{% endblock %} + +{% block branding %} +<h1 id="site-name">{% trans 'Django administration' %}</h1> +{% endblock %} + +{% block nav-global %}{% endblock %} diff --git a/webapp/django/contrib/admin/templates/admin/change_form.html b/webapp/django/contrib/admin/templates/admin/change_form.html new file mode 100644 index 0000000000..f367973820 --- /dev/null +++ b/webapp/django/contrib/admin/templates/admin/change_form.html @@ -0,0 +1,66 @@ +{% extends "admin/base_site.html" %} +{% load i18n admin_modify adminmedia %} + +{% block extrahead %}{{ block.super }} +<script type="text/javascript" src="../../../jsi18n/"></script> +{{ media }} +{% 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 breadcrumbs %}{% if not is_popup %} +<div class="breadcrumbs"> + <a href="../../../">{% trans "Home" %}</a> › + <a href="../../">{{ app_label|capfirst|escape }}</a> › + <a href="../">{{ opts.verbose_name_plural|capfirst }}</a> › + {% if add %}{% trans "Add" %} {{ opts.verbose_name }}{% else %}{{ original|truncatewords:"18" }}{% endif %} +</div> +{% endif %}{% endblock %} + +{% block content %}<div id="content-main"> +{% block object-tools %} +{% if change %}{% if not is_popup %} + <ul class="object-tools"><li><a href="history/" class="historylink">{% trans "History" %}</a></li> + {% 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 %} +{% endblock %} +<form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% block form_top %}{% endblock %} +<div> +{% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %} +{% if save_on_top %}{% submit_row %}{% endif %} +{% if errors %} + <p class="errornote"> + {% blocktrans count errors.items|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %} + </p> + <ul class="errorlist">{% for error in adminform.form.non_field_errors %}<li>{{ error }}</li>{% endfor %}</ul> +{% endif %} + +{% for fieldset in adminform %} + {% include "admin/includes/fieldset.html" %} +{% endfor %} + +{% block after_field_sets %}{% endblock %} + +{% for inline_admin_formset in inline_admin_formsets %} + {% include inline_admin_formset.opts.template %} +{% endfor %} + +{% block after_related_objects %}{% endblock %} + +{% submit_row %} + +{% if add %} + <script type="text/javascript">document.getElementById("{{ adminform.first_field.auto_id }}").focus();</script> +{% endif %} + +{# JavaScript for prepopulated fields #} +{% prepopulated_fields_js %} + +</div> +</form></div> +{% endblock %} diff --git a/webapp/django/contrib/admin/templates/admin/change_list.html b/webapp/django/contrib/admin/templates/admin/change_list.html new file mode 100644 index 0000000000..b66f67edf6 --- /dev/null +++ b/webapp/django/contrib/admin/templates/admin/change_list.html @@ -0,0 +1,38 @@ +{% extends "admin/base_site.html" %} +{% load adminmedia admin_list i18n %} + +{% block stylesheet %}{% admin_media_prefix %}css/changelists.css{% endblock %} + +{% block bodyclass %}change-list{% endblock %} + +{% if not is_popup %}{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans "Home" %}</a> › <a href="../">{{ app_label|capfirst|escape }}</a> › {{ cl.opts.verbose_name_plural|capfirst|escape }}</div>{% endblock %}{% endif %} + +{% block coltype %}flex{% endblock %} + +{% block content %} +<div id="content-main"> +{% block object-tools %} +{% if has_add_permission %} +<ul class="object-tools"><li><a href="add/{% if is_popup %}?_popup=1{% endif %}" class="addlink">{% blocktrans with cl.opts.verbose_name|escape as name %}Add {{ name }}{% endblocktrans %}</a></li></ul> +{% endif %} +{% endblock %} +<div class="module{% if cl.has_filters %} filtered{% endif %}" id="changelist"> +{% block search %}{% search_form cl %}{% endblock %} +{% block date_hierarchy %}{% date_hierarchy cl %}{% endblock %} + +{% block filters %} +{% if cl.has_filters %} +<div id="changelist-filter"> +<h2>{% trans 'Filter' %}</h2> +{% for spec in cl.filter_specs %} + {% admin_list_filter cl spec %} +{% endfor %} +</div> +{% endif %} +{% endblock %} + +{% block result_list %}{% result_list cl %}{% endblock %} +{% block pagination %}{% pagination cl %}{% endblock %} +</div> +</div> +{% endblock %} diff --git a/webapp/django/contrib/admin/templates/admin/change_list_results.html b/webapp/django/contrib/admin/templates/admin/change_list_results.html new file mode 100644 index 0000000000..381dcb5d5d --- /dev/null +++ b/webapp/django/contrib/admin/templates/admin/change_list_results.html @@ -0,0 +1,17 @@ +{% if results %} +<table cellspacing="0"> +<thead> +<tr> +{% for header in result_headers %}<th{{ header.class_attrib }}> +{% if header.sortable %}<a href="{{ header.url }}">{% endif %} +{{ header.text|capfirst }} +{% if header.sortable %}</a>{% endif %}</th>{% endfor %} +</tr> +</thead> +<tbody> +{% for result in results %} +<tr class="{% cycle 'row1' 'row2' %}">{% for item in result %}{{ item }}{% endfor %}</tr> +{% endfor %} +</tbody> +</table> +{% endif %} diff --git a/webapp/django/contrib/admin/templates/admin/date_hierarchy.html b/webapp/django/contrib/admin/templates/admin/date_hierarchy.html new file mode 100644 index 0000000000..005851051c --- /dev/null +++ b/webapp/django/contrib/admin/templates/admin/date_hierarchy.html @@ -0,0 +1,10 @@ +{% if show %} +<div class="xfull"> +<ul class="toplinks"> +{% if back %}<li class="date-back"><a href="{{ back.link }}">‹ {{ back.title }}</a></li>{% endif %} +{% for choice in choices %} +<li> {% if choice.link %}<a href="{{ choice.link }}">{% endif %}{{ choice.title }}{% if choice.link %}</a>{% endif %}</li> +{% endfor %} +</ul><br class="clear" /> +</div> +{% endif %} diff --git a/webapp/django/contrib/admin/templates/admin/delete_confirmation.html b/webapp/django/contrib/admin/templates/admin/delete_confirmation.html new file mode 100644 index 0000000000..2a6c4b4ad5 --- /dev/null +++ b/webapp/django/contrib/admin/templates/admin/delete_confirmation.html @@ -0,0 +1,32 @@ +{% extends "admin/base_site.html" %} +{% load i18n %} + +{% block breadcrumbs %} +<div class="breadcrumbs"> + <a href="../../../../">{% trans "Home" %}</a> › + <a href="../../../">{{ app_label|capfirst|escape }}</a> › + <a href="../../">{{ opts.verbose_name_plural|capfirst }}</a> › + <a href="../">{{ object|escape|truncatewords:"18" }}</a> › + {% trans 'Delete' %} +</div> +{% endblock %} + +{% block content %} +{% if perms_lacking %} + <p>{% blocktrans with object|escape as escaped_object %}Deleting the {{ object_name }} '{{ escaped_object }}' would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:{% endblocktrans %}</p> + <ul> + {% for obj in perms_lacking %} + <li>{{ obj }}</li> + {% endfor %} + </ul> +{% else %} + <p>{% blocktrans with object|escape as escaped_object %}Are you sure you want to delete the {{ object_name }} "{{ escaped_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 %} diff --git a/webapp/django/contrib/admin/templates/admin/edit_inline/stacked.html b/webapp/django/contrib/admin/templates/admin/edit_inline/stacked.html new file mode 100644 index 0000000000..217791f658 --- /dev/null +++ b/webapp/django/contrib/admin/templates/admin/edit_inline/stacked.html @@ -0,0 +1,26 @@ +{% load i18n %} +<div class="inline-group"> + <h2>{{ inline_admin_formset.opts.verbose_name_plural|title }}</h2> +{{ inline_admin_formset.formset.management_form }} +{{ inline_admin_formset.formset.non_form_errors }} + +{% for inline_admin_form in inline_admin_formset %} +<div class="inline-related {% if forloop.last %}last-related{% endif %}"> + <h3><b>{{ inline_admin_formset.opts.verbose_name|title }}:</b> {% if inline_admin_form.original %}{{ inline_admin_form.original }}{% else %} #{{ forloop.counter }}{% endif %} + {% if inline_admin_formset.formset.can_delete and inline_admin_form.original %}<span class="delete">{{ inline_admin_form.deletion_field.field }} {{ inline_admin_form.deletion_field.label_tag }}</span>{% endif %} + </h3> + {% if inline_admin_form.show_url %} + <p><a href="/r/{{ inline_admin_form.original.content_type_id }}/{{ inline_admin_form.original.id }}/">View on site</a></p> + {% endif %} + + {% for fieldset in inline_admin_form %} + {% include "admin/includes/fieldset.html" %} + {% endfor %} + {{ inline_admin_form.pk_field.field }} +</div> +{% endfor %} + +{# <ul class="tools"> #} +{# <li><a class="add" href="">Add another {{ inline_admin_formset.opts.verbose_name|title }}</a></li> #} +{# </ul> #} +</div> diff --git a/webapp/django/contrib/admin/templates/admin/edit_inline/tabular.html b/webapp/django/contrib/admin/templates/admin/edit_inline/tabular.html new file mode 100644 index 0000000000..f3fe378934 --- /dev/null +++ b/webapp/django/contrib/admin/templates/admin/edit_inline/tabular.html @@ -0,0 +1,67 @@ +{% load i18n %} +<div class="inline-group"> + <div class="tabular inline-related {% if forloop.last %}last-related{% endif %}"> +{{ inline_admin_formset.formset.management_form }} +<fieldset class="module"> + <h2>{{ inline_admin_formset.opts.verbose_name_plural|capfirst|escape }}</h2> + {{ inline_admin_formset.formset.non_form_errors }} + <table> + <thead><tr> + {% for field in inline_admin_formset.fields %} + {% if not field.is_hidden %} + <th {% if forloop.first %}colspan="2"{% endif %}>{{ field.label|capfirst|escape }}</th> + {% endif %} + {% endfor %} + {% if inline_admin_formset.formset.can_delete %}<th>{% trans "Delete" %}?</th>{% endif %} + </tr></thead> + + {% for inline_admin_form in inline_admin_formset %} + + <tr class="{% cycle row1,row2 %} {% if inline_admin_form.original or inline_admin_form.show_url %}has_original{% endif %}"> + + <td class="original"> + {% if inline_admin_form.original or inline_admin_form.show_url %}<p> + {% if inline_admin_form.original %} {{ inline_admin_form.original }}{% endif %} + {% if inline_admin_form.show_url %}<a href="/r/{{ inline_admin_form.original.content_type_id }}/{{ inline_admin_form.original.id }}/">View on site</a>{% endif %} + </p>{% endif %} + {{ inline_admin_form.pk_field.field }} + {% spaceless %} + {% for fieldset in inline_admin_form %} + {% for line in fieldset %} + {% for field in line %} + {% if field.is_hidden %} {{ field.field }} {% endif %} + {% endfor %} + {% endfor %} + {% endfor %} + {% endspaceless %} + </td> + + {% for fieldset in inline_admin_form %} + {% for line in fieldset %} + {% for field in line %} + <td class="{{ field.field.name }}"> + {{ field.field.errors.as_ul }} + {{ field.field }} + </td> + {% endfor %} + {% endfor %} + {% endfor %} + + {% if inline_admin_formset.formset.can_delete %} + <td class="delete">{% if inline_admin_form.original %}{{ inline_admin_form.deletion_field.field }}{% endif %}</td> + {% endif %} + + </tr> + + {% endfor %} + + </table> + +</fieldset> + </div> + + {# <ul class="tools"> #} + {# <li><a class="add" href="">Add another {{ inline_admin_formset.opts.verbose_name|title }}</a></li> #} + {# </ul> #} + +</div> diff --git a/webapp/django/contrib/admin/templates/admin/filter.html b/webapp/django/contrib/admin/templates/admin/filter.html new file mode 100644 index 0000000000..bcc64ac7e4 --- /dev/null +++ b/webapp/django/contrib/admin/templates/admin/filter.html @@ -0,0 +1,8 @@ +{% load i18n %} +<h3>{% blocktrans with title|escape as filter_title %} By {{ filter_title }} {% endblocktrans %}</h3> +<ul> +{% for choice in choices %} + <li{% if choice.selected %} class="selected"{% endif %}> + <a href="{{ choice.query_string|iriencode }}">{{ choice.display|escape }}</a></li> +{% endfor %} +</ul> diff --git a/webapp/django/contrib/admin/templates/admin/includes/fieldset.html b/webapp/django/contrib/admin/templates/admin/includes/fieldset.html new file mode 100644 index 0000000000..27e54c75d3 --- /dev/null +++ b/webapp/django/contrib/admin/templates/admin/includes/fieldset.html @@ -0,0 +1,17 @@ +<fieldset class="module aligned {{ fieldset.classes }}"> + {% if fieldset.name %}<h2>{{ fieldset.name }}</h2>{% endif %} + {% if fieldset.description %}<div class="description">{{ fieldset.description|safe }}</div>{% endif %} + {% for line in fieldset %} + <div class="form-row{% if line.errors %} errors{% endif %} {% for field in line %}{{ field.field.name }} {% endfor %} "> + {{ line.errors }} + {% for field in line %} + {% if field.is_checkbox %} + {{ field.field }}{{ field.label_tag }} + {% else %} + {{ field.label_tag }}{{ field.field }} + {% endif %} + {% if field.field.field.help_text %}<p class="help">{{ field.field.field.help_text|safe }}</p>{% endif %} + {% endfor %} + </div> + {% endfor %} +</fieldset> diff --git a/webapp/django/contrib/admin/templates/admin/index.html b/webapp/django/contrib/admin/templates/admin/index.html new file mode 100644 index 0000000000..ba0c381fad --- /dev/null +++ b/webapp/django/contrib/admin/templates/admin/index.html @@ -0,0 +1,68 @@ +{% 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 %} + +{% block content %} +<div id="content-main"> + +{% if app_list %} + {% for app in app_list %} + <div class="module"> + <table summary="{% blocktrans with app.name as name %}Models available in the {{ name }} application.{% endblocktrans %}"> + <caption><a href="{{ app.app_url }}" class="section">{% blocktrans with app.name as name %}{{ name }}{% endblocktrans %}</a></caption> + {% for model in app.models %} + <tr> + {% if model.perms.change %} + <th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th> + {% else %} + <th scope="row">{{ model.name }}</th> + {% endif %} + + {% if model.perms.add %} + <td><a href="{{ model.admin_url }}add/" class="addlink">{% trans 'Add' %}</a></td> + {% else %} + <td> </td> + {% endif %} + + {% if model.perms.change %} + <td><a href="{{ model.admin_url }}" class="changelink">{% trans 'Change' %}</a></td> + {% else %} + <td> </td> + {% endif %} + </tr> + {% endfor %} + </table> + </div> + {% endfor %} +{% else %} + <p>{% trans "You don't have permission to edit anything." %}</p> +{% endif %} +</div> +{% endblock %} + +{% block sidebar %} +<div id="content-related"> + <div class="module" id="recent-actions-module"> + <h2>{% trans 'Recent Actions' %}</h2> + <h3>{% trans 'My Actions' %}</h3> + {% load log %} + {% get_admin_log 10 as admin_log for_user user %} + {% if not admin_log %} + <p>{% trans 'None available' %}</p> + {% 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">{% filter capfirst %}{% trans entry.content_type.name %}{% endfilter %}</span></li> + {% endfor %} + </ul> + {% endif %} + </div> +</div> +{% endblock %} diff --git a/webapp/django/contrib/admin/templates/admin/invalid_setup.html b/webapp/django/contrib/admin/templates/admin/invalid_setup.html new file mode 100644 index 0000000000..f09b316b06 --- /dev/null +++ b/webapp/django/contrib/admin/templates/admin/invalid_setup.html @@ -0,0 +1,8 @@ +{% extends "admin/base_site.html" %} +{% load i18n %} + +{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans 'Home' %}</a> › {{ title }}</div>{% endblock %} + +{% block content %} +<p>{% trans "Something's wrong with your database installation. Make sure the appropriate database tables have been created, and make sure the database is readable by the appropriate user." %}</p> +{% endblock %} diff --git a/webapp/django/contrib/admin/templates/admin/login.html b/webapp/django/contrib/admin/templates/admin/login.html new file mode 100644 index 0000000000..5dd953bc23 --- /dev/null +++ b/webapp/django/contrib/admin/templates/admin/login.html @@ -0,0 +1,35 @@ +{% 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 %} +{% if error_message %} +<p class="errornote">{{ error_message }}</p> +{% endif %} +<div id="content-main"> +<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 }}" /> {#<span class="help">{% trans 'Have you <a href="/password_reset/">forgotten your password</a>?' %}</span>#} + </div> + <div class="submit-row"> + <label> </label><input type="submit" value="{% trans 'Log in' %}" /> + </div> +</form> + +<script type="text/javascript"> +document.getElementById('id_username').focus() +</script> +</div> +{% endblock %} diff --git a/webapp/django/contrib/admin/templates/admin/object_history.html b/webapp/django/contrib/admin/templates/admin/object_history.html new file mode 100644 index 0000000000..c37012b296 --- /dev/null +++ b/webapp/django/contrib/admin/templates/admin/object_history.html @@ -0,0 +1,36 @@ +{% extends "admin/base_site.html" %} +{% load i18n %} + +{% block breadcrumbs %} +<div class="breadcrumbs"><a href="../../../../">{% trans 'Home' %}</a> › <a href="../../">{{ module_name }}</a> › <a href="../">{{ object|truncatewords:"18" }}</a> › {% trans 'History' %}</div> +{% endblock %} + +{% block content %} +<div id="content-main"> +<div class="module"> + +{% if action_list %} + <table id="change-history"> + <thead> + <tr> + <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 scope="row">{{ action.action_time|date:_("DATETIME_FORMAT") }}</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 %} + </tbody> + </table> +{% else %} + <p>{% trans "This object doesn't have a change history. It probably wasn't added via this admin site." %}</p> +{% endif %} +</div> +</div> +{% endblock %} diff --git a/webapp/django/contrib/admin/templates/admin/pagination.html b/webapp/django/contrib/admin/templates/admin/pagination.html new file mode 100644 index 0000000000..0640a4690b --- /dev/null +++ b/webapp/django/contrib/admin/templates/admin/pagination.html @@ -0,0 +1,11 @@ +{% load admin_list %} +{% load i18n %} +<p class="paginator"> +{% if pagination_required %} +{% for i in page_range %} + {% paginator_number cl i %} +{% endfor %} +{% endif %} +{{ cl.result_count }} {% ifequal cl.result_count 1 %}{{ cl.opts.verbose_name|escape }}{% else %}{{ cl.opts.verbose_name_plural }}{% endifequal %} +{% if show_all_url %} <a href="{{ show_all_url }}" class="showall">{% trans 'Show all' %}</a>{% endif %} +</p> diff --git a/webapp/django/contrib/admin/templates/admin/prepopulated_fields_js.html b/webapp/django/contrib/admin/templates/admin/prepopulated_fields_js.html new file mode 100644 index 0000000000..e1cdb9614e --- /dev/null +++ b/webapp/django/contrib/admin/templates/admin/prepopulated_fields_js.html @@ -0,0 +1,11 @@ +<script type="text/javascript"> +{% for field in prepopulated_fields %} + document.getElementById("{{ field.field.auto_id }}").onchange = function() { this._changed = true; }; + {% for dependency in field.dependencies %} + document.getElementById("{{ dependency.auto_id }}").onkeyup = function() { + var e = document.getElementById("{{ field.field.auto_id }}"); + if (!e._changed) { e.value = URLify({% for innerdep in field.dependencies %}document.getElementById("{{ innerdep.auto_id }}").value{% if not forloop.last %} + ' ' + {% endif %}{% endfor %}, {{ field.field.field.max_length|default_if_none:"50" }}); } + } + {% endfor %} +{% endfor %} +</script> diff --git a/webapp/django/contrib/admin/templates/admin/search_form.html b/webapp/django/contrib/admin/templates/admin/search_form.html new file mode 100644 index 0000000000..b232aa917d --- /dev/null +++ b/webapp/django/contrib/admin/templates/admin/search_form.html @@ -0,0 +1,18 @@ +{% load adminmedia %} +{% load i18n %} +{% if cl.search_fields %} +<div id="toolbar"><form id="changelist-search" action="" method="get"> +<div><!-- DIV needed for valid HTML --> +<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 %} + <span class="small quiet">{% blocktrans count cl.result_count as counter %}1 result{% plural %}{{ counter }} results{% endblocktrans %} (<a href="?{% if cl.is_popup %}pop=1{% endif %}">{% blocktrans with cl.full_result_count as full_result_count %}{{ full_result_count }} total{% endblocktrans %}</a>)</span> +{% endif %} +{% for pair in cl.params.items %} + {% ifnotequal pair.0 search_var %}<input type="hidden" name="{{ pair.0|escape }}" value="{{ pair.1|escape }}"/>{% endifnotequal %} +{% endfor %} +</div> +</form></div> +<script type="text/javascript">document.getElementById("searchbar").focus();</script> +{% endif %} diff --git a/webapp/django/contrib/admin/templates/admin/submit_line.html b/webapp/django/contrib/admin/templates/admin/submit_line.html new file mode 100644 index 0000000000..6eccbce45b --- /dev/null +++ b/webapp/django/contrib/admin/templates/admin/submit_line.html @@ -0,0 +1,8 @@ +{% load i18n %} +<div class="submit-row"> +{% if show_save %}<input type="submit" value="{% trans 'Save' %}" class="default" name="_save" {{ onclick_attrib }}/>{% endif %} +{% if show_delete_link %}<p class="float-left"><a href="delete/" class="deletelink">{% trans "Delete" %}</a></p>{% endif %} +{% if show_save_as_new %}<input type="submit" value="{% trans 'Save as new' %}" name="_saveasnew" {{ onclick_attrib }}/>{%endif%} +{% if show_save_and_add_another %}<input type="submit" value="{% trans 'Save and add another' %}" name="_addanother" {{ onclick_attrib }} />{% endif %} +{% if show_save_and_continue %}<input type="submit" value="{% trans 'Save and continue editing' %}" name="_continue" {{ onclick_attrib }}/>{% endif %} +</div> diff --git a/webapp/django/contrib/admin/templates/admin/template_validator.html b/webapp/django/contrib/admin/templates/admin/template_validator.html new file mode 100644 index 0000000000..510614386c --- /dev/null +++ b/webapp/django/contrib/admin/templates/admin/template_validator.html @@ -0,0 +1,31 @@ +{% extends "admin/base_site.html" %} + +{% block content %} + +<div id="content-main"> + +<form action="" method="post"> + +{% if form.errors %} +<p class="errornote">Your template had {{ form.errors|length }} error{{ form.errors|pluralize }}:</p> +{% endif %} + +<fieldset class="module aligned"> +<div class="form-row{% if form.errors.site %} error{% endif %} required"> + {% if form.errors.site %}{{ form.errors.site }}{% endif %} + <h4><label for="id_site">{{ form.site.label }}:</label> {{ form.site }}</h4> +</div> +<div class="form-row{% if form.errors.template %} error{% endif %} required"> + {% if form.errors.template %}{{ form.errors.template }}{% endif %} + <h4><label for="id_template">{{ form.template.label }}:</label> {{ form.template }}</h4> +</div> +</fieldset> + +<div class="submit-row"> + <input type="submit" value="Check for errors" class="default" /> +</div> + +</form> +</div> + +{% endblock %} diff --git a/webapp/django/contrib/admin/templates/registration/logged_out.html b/webapp/django/contrib/admin/templates/registration/logged_out.html new file mode 100644 index 0000000000..d339ef0a49 --- /dev/null +++ b/webapp/django/contrib/admin/templates/registration/logged_out.html @@ -0,0 +1,12 @@ +{% extends "admin/base_site.html" %} +{% load i18n %} + +{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a></div>{% endblock %} + +{% block content %} + +<p>{% trans "Thanks for spending some quality time with the Web site today." %}</p> + +<p><a href="../">{% trans 'Log in again' %}</a></p> + +{% endblock %} diff --git a/webapp/django/contrib/admin/templates/registration/password_change_done.html b/webapp/django/contrib/admin/templates/registration/password_change_done.html new file mode 100644 index 0000000000..252572001d --- /dev/null +++ b/webapp/django/contrib/admin/templates/registration/password_change_done.html @@ -0,0 +1,14 @@ +{% 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 %} + +{% block title %}{% trans 'Password change successful' %}{% endblock %} + +{% block content %} + +<h1>{% trans 'Password change successful' %}</h1> + +<p>{% trans 'Your password was changed.' %}</p> + +{% endblock %} diff --git a/webapp/django/contrib/admin/templates/registration/password_change_form.html b/webapp/django/contrib/admin/templates/registration/password_change_form.html new file mode 100644 index 0000000000..4facc0ec01 --- /dev/null +++ b/webapp/django/contrib/admin/templates/registration/password_change_form.html @@ -0,0 +1,26 @@ +{% 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 %} + +{% block title %}{% trans 'Password change' %}{% endblock %} + +{% block content %} + +<h1>{% trans 'Password change' %}</h1> + +<p>{% trans "Please enter your old password, for security's sake, and then enter your new password twice so we can verify you typed it in correctly." %}</p> + +<form action="" method="post"> + +{% if form.old_password.errors %}{{ form.old_password.errors }}{% endif %} +<p class="aligned wide"><label for="id_old_password">{% trans 'Old password:' %}</label>{{ form.old_password }}</p> +{% if form.new_password1.errors %}{{ form.new_password1.errors }}{% endif %} +<p class="aligned wide"><label for="id_new_password1">{% trans 'New password:' %}</label>{{ form.new_password1 }}</p> +{% if form.new_password2.errors %}{{ form.new_password2.errors }}{% endif %} +<p class="aligned wide"><label for="id_new_password2">{% trans 'Confirm password:' %}</label>{{ form.new_password2 }}</p> + +<p><input type="submit" value="{% trans 'Change my password' %}" /></p> +</form> + +{% endblock %} diff --git a/webapp/django/contrib/admin/templates/registration/password_reset_complete.html b/webapp/django/contrib/admin/templates/registration/password_reset_complete.html new file mode 100644 index 0000000000..fceb167a88 --- /dev/null +++ b/webapp/django/contrib/admin/templates/registration/password_reset_complete.html @@ -0,0 +1,16 @@ +{% extends "admin/base_site.html" %} +{% load i18n %} + +{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans 'Home' %}</a> › {% trans 'Password reset' %}</div>{% endblock %} + +{% block title %}{% trans 'Password reset complete' %}{% endblock %} + +{% block content %} + +<h1>{% trans 'Password reset complete' %}</h1> + +<p>{% trans "Your password has been set. You may go ahead and log in now." %}</p> + +<p><a href="{{ login_url }}">{% trans 'Log in' %}</a></p> + +{% endblock %} diff --git a/webapp/django/contrib/admin/templates/registration/password_reset_confirm.html b/webapp/django/contrib/admin/templates/registration/password_reset_confirm.html new file mode 100644 index 0000000000..9ba0e5af27 --- /dev/null +++ b/webapp/django/contrib/admin/templates/registration/password_reset_confirm.html @@ -0,0 +1,32 @@ +{% extends "admin/base_site.html" %} +{% load i18n %} + +{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> › {% trans 'Password reset confirmation' %}</div>{% endblock %} + +{% block title %}{% trans 'Password reset' %}{% endblock %} + +{% block content %} + +{% if validlink %} + +<h1>{% trans 'Enter new password' %}</h1> + +<p>{% trans "Please enter your new password twice so we can verify you typed it in correctly." %}</p> + +<form action="" method="post"> +{% if form.new_password1.errors %}{{ form.new_password1.errors }}{% endif %} +<p class="aligned wide"><label for="id_new_password1">{% trans 'New password:' %}</label>{{ form.new_password1 }}</p> +{% if form.new_password2.errors %}{{ form.new_password2.errors }}{% endif %} +<p class="aligned wide"><label for="id_new_password2">{% trans 'Confirm password:' %}</label>{{ form.new_password2 }}</p> +<p><input type="submit" value="{% trans 'Change my password' %}" /></p> +</form> + +{% else %} + +<h1>{% trans 'Password reset unsuccessful' %}</h1> + +<p>{% trans "The password reset link was invalid, possibly because it has already been used. Please request a new password reset." %} + +{% endif %} + +{% endblock %} diff --git a/webapp/django/contrib/admin/templates/registration/password_reset_done.html b/webapp/django/contrib/admin/templates/registration/password_reset_done.html new file mode 100644 index 0000000000..e223bdb9de --- /dev/null +++ b/webapp/django/contrib/admin/templates/registration/password_reset_done.html @@ -0,0 +1,14 @@ +{% extends "admin/base_site.html" %} +{% load i18n %} + +{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> › {% trans 'Password reset' %}</div>{% endblock %} + +{% block title %}{% trans 'Password reset successful' %}{% endblock %} + +{% block content %} + +<h1>{% trans 'Password reset successful' %}</h1> + +<p>{% trans "We've e-mailed you instructions for setting your password to the e-mail address you submitted. You should be receiving it shortly." %}</p> + +{% endblock %} diff --git a/webapp/django/contrib/admin/templates/registration/password_reset_email.html b/webapp/django/contrib/admin/templates/registration/password_reset_email.html new file mode 100644 index 0000000000..4e4bd6d1b2 --- /dev/null +++ b/webapp/django/contrib/admin/templates/registration/password_reset_email.html @@ -0,0 +1,15 @@ +{% load i18n %}{% autoescape off %} +{% trans "You're receiving this e-mail because you requested a password reset" %} +{% blocktrans %}for your user account at {{ site_name }}{% endblocktrans %}. + +{% trans "Please go to the following page and choose a new password:" %} +{% block reset_link %} +{{ protocol }}://{{ domain }}{% url django.contrib.auth.views.password_reset_confirm uidb36=uid, token=token %} +{% endblock %} +{% trans "Your username, in case you've forgotten:" %} {{ user.username }} + +{% trans "Thanks for using our site!" %} + +{% blocktrans %}The {{ site_name }} team{% endblocktrans %} + +{% endautoescape %} diff --git a/webapp/django/contrib/admin/templates/registration/password_reset_form.html b/webapp/django/contrib/admin/templates/registration/password_reset_form.html new file mode 100644 index 0000000000..4ecebc77a1 --- /dev/null +++ b/webapp/django/contrib/admin/templates/registration/password_reset_form.html @@ -0,0 +1,19 @@ +{% extends "admin/base_site.html" %} +{% load i18n %} + +{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> › {% trans 'Password reset' %}</div>{% endblock %} + +{% block title %}{% trans "Password reset" %}{% endblock %} + +{% block content %} + +<h1>{% trans "Password reset" %}</h1> + +<p>{% trans "Forgotten your password? Enter your e-mail address below, and we'll e-mail instructions for setting a new one." %}</p> + +<form action="" method="post"> +{% if form.email.errors %}{{ form.email.errors }}{% endif %} +<p><label for="id_email">{% trans 'E-mail address:' %}</label> {{ form.email }} <input type="submit" value="{% trans 'Reset my password' %}" /></p> +</form> + +{% endblock %} diff --git a/webapp/django/contrib/admin/templatetags/__init__.py b/webapp/django/contrib/admin/templatetags/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/webapp/django/contrib/admin/templatetags/__init__.py diff --git a/webapp/django/contrib/admin/templatetags/admin_list.py b/webapp/django/contrib/admin/templatetags/admin_list.py new file mode 100644 index 0000000000..cae33cc20e --- /dev/null +++ b/webapp/django/contrib/admin/templatetags/admin_list.py @@ -0,0 +1,312 @@ +from django.conf import settings +from django.contrib.admin.views.main import ALL_VAR, EMPTY_CHANGELIST_VALUE +from django.contrib.admin.views.main import ORDER_VAR, ORDER_TYPE_VAR, PAGE_VAR, SEARCH_VAR +from django.core.exceptions import ObjectDoesNotExist +from django.db import models +from django.utils import dateformat +from django.utils.html import escape, conditional_escape +from django.utils.text import capfirst +from django.utils.safestring import mark_safe +from django.utils.translation import get_date_formats, get_partial_date_formats, ugettext as _ +from django.utils.encoding import smart_unicode, smart_str, force_unicode +from django.template import Library +import datetime + +register = Library() + +DOT = '.' + +def paginator_number(cl,i): + if i == DOT: + return u'... ' + elif i == cl.page_num: + return mark_safe(u'<span class="this-page">%d</span> ' % (i+1)) + else: + return mark_safe(u'<a href="%s"%s>%d</a> ' % (cl.get_query_string({PAGE_VAR: i}), (i == cl.paginator.num_pages-1 and ' class="end"' or ''), i+1)) +paginator_number = register.simple_tag(paginator_number) + +def pagination(cl): + paginator, page_num = cl.paginator, cl.page_num + + pagination_required = (not cl.show_all or not cl.can_show_all) and cl.multi_page + if not pagination_required: + page_range = [] + else: + ON_EACH_SIDE = 3 + ON_ENDS = 2 + + # If there are 10 or fewer pages, display links to every page. + # Otherwise, do some fancy + if paginator.num_pages <= 10: + page_range = range(paginator.num_pages) + else: + # Insert "smart" pagination links, so that there are always ON_ENDS + # links at either end of the list of pages, and there are always + # ON_EACH_SIDE links at either end of the "current page" link. + page_range = [] + if page_num > (ON_EACH_SIDE + ON_ENDS): + page_range.extend(range(0, ON_EACH_SIDE - 1)) + page_range.append(DOT) + page_range.extend(range(page_num - ON_EACH_SIDE, page_num + 1)) + else: + page_range.extend(range(0, page_num + 1)) + if page_num < (paginator.num_pages - ON_EACH_SIDE - ON_ENDS - 1): + page_range.extend(range(page_num + 1, page_num + ON_EACH_SIDE + 1)) + page_range.append(DOT) + page_range.extend(range(paginator.num_pages - ON_ENDS, paginator.num_pages)) + else: + page_range.extend(range(page_num + 1, paginator.num_pages)) + + need_show_all_link = cl.can_show_all and not cl.show_all and cl.multi_page + return { + 'cl': cl, + 'pagination_required': pagination_required, + 'show_all_url': need_show_all_link and cl.get_query_string({ALL_VAR: ''}), + 'page_range': page_range, + 'ALL_VAR': ALL_VAR, + '1': 1, + } +pagination = register.inclusion_tag('admin/pagination.html')(pagination) + +def result_headers(cl): + lookup_opts = cl.lookup_opts + + for i, field_name in enumerate(cl.list_display): + attr = None + try: + f = lookup_opts.get_field(field_name) + admin_order_field = None + 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 __str__ and __unicode__ are special-cases. + if field_name == '__unicode__': + header = force_unicode(lookup_opts.verbose_name) + elif field_name == '__str__': + header = smart_str(lookup_opts.verbose_name) + else: + if callable(field_name): + attr = field_name # field_name can be a callable + else: + try: + attr = getattr(cl.model_admin, field_name) + except AttributeError: + try: + attr = getattr(cl.model, field_name) + except AttributeError: + raise AttributeError, \ + "'%s' model or '%s' objects have no attribute '%s'" % \ + (lookup_opts.object_name, cl.model_admin.__class__, field_name) + + try: + header = attr.short_description + except AttributeError: + if callable(field_name): + header = field_name.__name__ + else: + header = field_name + header = header.replace('_', ' ') + + # It is a non-field, but perhaps one that is sortable + admin_order_field = getattr(attr, "admin_order_field", None) + if not admin_order_field: + yield {"text": header} + continue + + # So this _is_ a sortable non-field. Go to the yield + # after the else clause. + else: + if isinstance(f.rel, models.ManyToOneRel) and f.null: + yield {"text": f.verbose_name} + continue + else: + header = f.verbose_name + + th_classes = [] + new_order_type = 'asc' + if field_name == cl.order_field or admin_order_field == cl.order_field: + th_classes.append('sorted %sending' % cl.order_type.lower()) + new_order_type = {'asc': 'desc', 'desc': 'asc'}[cl.order_type.lower()] + + yield {"text": header, + "sortable": True, + "url": cl.get_query_string({ORDER_VAR: i, ORDER_TYPE_VAR: new_order_type}), + "class_attrib": mark_safe(th_classes and ' class="%s"' % ' '.join(th_classes) or '')} + +def _boolean_icon(field_val): + BOOLEAN_MAPPING = {True: 'yes', False: 'no', None: 'unknown'} + return mark_safe(u'<img src="%simg/admin/icon-%s.gif" alt="%s" />' % (settings.ADMIN_MEDIA_PREFIX, BOOLEAN_MAPPING[field_val], field_val)) + +def items_for_result(cl, result): + first = True + pk = cl.lookup_opts.pk.attname + for field_name in cl.list_display: + row_class = '' + try: + f = cl.lookup_opts.get_field(field_name) + except models.FieldDoesNotExist: + # For non-field list_display values, the value is either a method, + # property or returned via a callable. + try: + if callable(field_name): + attr = field_name + value = attr(result) + elif hasattr(cl.model_admin, field_name) and \ + not field_name == '__str__' and not field_name == '__unicode__': + attr = getattr(cl.model_admin, field_name) + value = attr(result) + else: + attr = getattr(result, field_name) + if callable(attr): + value = attr() + else: + value = attr + allow_tags = getattr(attr, 'allow_tags', False) + boolean = getattr(attr, 'boolean', False) + if boolean: + allow_tags = True + result_repr = _boolean_icon(value) + else: + result_repr = smart_unicode(value) + 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 allow_tags: + result_repr = escape(result_repr) + else: + result_repr = mark_safe(result_repr) + else: + field_val = getattr(result, f.attname) + + if isinstance(f.rel, models.ManyToOneRel): + if field_val is not None: + result_repr = escape(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, models.DateField) or isinstance(f, models.TimeField): + if field_val: + (date_format, datetime_format, time_format) = get_date_formats() + if isinstance(f, models.DateTimeField): + result_repr = capfirst(dateformat.format(field_val, datetime_format)) + 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)) + else: + result_repr = EMPTY_CHANGELIST_VALUE + row_class = ' class="nowrap"' + # Booleans are special: We use images. + elif isinstance(f, models.BooleanField) or isinstance(f, models.NullBooleanField): + result_repr = _boolean_icon(field_val) + # DecimalFields are special: Zero-pad the decimals. + elif isinstance(f, models.DecimalField): + if field_val is not None: + result_repr = ('%%.%sf' % f.decimal_places) % field_val + else: + result_repr = EMPTY_CHANGELIST_VALUE + # Fields with choices are special: Use the representation + # of the choice. + elif f.choices: + result_repr = dict(f.choices).get(field_val, EMPTY_CHANGELIST_VALUE) + else: + result_repr = escape(field_val) + if force_unicode(result_repr) == '': + result_repr = mark_safe(' ') + # If list_display_links not defined, add the link tag to the first field + if (first and not cl.list_display_links) or field_name in cl.list_display_links: + table_tag = {True:'th', False:'td'}[first] + first = False + url = cl.url_for_result(result) + # Convert the pk to something that can be used in Javascript. + # Problem cases are long ints (23L) and non-ASCII strings. + result_id = repr(force_unicode(getattr(result, pk)))[1:] + yield mark_safe(u'<%s%s><a href="%s"%s>%s</a></%s>' % \ + (table_tag, row_class, url, (cl.is_popup and ' onclick="opener.dismissRelatedLookupPopup(window, %s); return false;"' % result_id or ''), conditional_escape(result_repr), table_tag)) + else: + yield mark_safe(u'<td%s>%s</td>' % (row_class, conditional_escape(result_repr))) + +def results(cl): + for res in cl.result_list: + yield list(items_for_result(cl,res)) + +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.html")(result_list) + +def date_hierarchy(cl): + if cl.date_hierarchy: + field_name = cl.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 = cl.params.get(year_field) + month_lookup = cl.params.get(month_field) + day_lookup = cl.params.get(day_field) + year_month_format, month_day_format = get_partial_date_formats() + + link = lambda d: mark_safe(cl.get_query_string(d, [field_generic])) + + if year_lookup and month_lookup and day_lookup: + day = datetime.date(int(year_lookup), int(month_lookup), int(day_lookup)) + return { + 'show': True, + 'back': { + 'link': link({year_field: year_lookup, month_field: month_lookup}), + 'title': dateformat.format(day, year_month_format) + }, + 'choices': [{'title': dateformat.format(day, month_day_format)}] + } + elif year_lookup and month_lookup: + days = cl.query_set.filter(**{year_field: year_lookup, month_field: month_lookup}).dates(field_name, 'day') + return { + 'show': True, + 'back': { + 'link': link({year_field: year_lookup}), + 'title': year_lookup + }, + 'choices': [{ + 'link': link({year_field: year_lookup, month_field: month_lookup, day_field: day.day}), + 'title': dateformat.format(day, month_day_format) + } for day in days] + } + elif year_lookup: + months = cl.query_set.filter(**{year_field: year_lookup}).dates(field_name, 'month') + return { + 'show' : True, + 'back': { + 'link' : link({}), + 'title': _('All dates') + }, + 'choices': [{ + 'link': link({year_field: year_lookup, month_field: month.month}), + 'title': dateformat.format(month, year_month_format) + } for month in months] + } + else: + 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] + } +date_hierarchy = register.inclusion_tag('admin/date_hierarchy.html')(date_hierarchy) + +def search_form(cl): + return { + 'cl': cl, + 'show_result_count': cl.result_count != cl.full_result_count, + 'search_var': SEARCH_VAR + } +search_form = register.inclusion_tag('admin/search_form.html')(search_form) + +def admin_list_filter(cl, spec): + return {'title': spec.title(), 'choices' : list(spec.choices(cl))} +admin_list_filter = register.inclusion_tag('admin/filter.html')(admin_list_filter) diff --git a/webapp/django/contrib/admin/templatetags/admin_modify.py b/webapp/django/contrib/admin/templatetags/admin_modify.py new file mode 100644 index 0000000000..850127c8ce --- /dev/null +++ b/webapp/django/contrib/admin/templatetags/admin_modify.py @@ -0,0 +1,38 @@ +from django import template + +register = template.Library() + +def prepopulated_fields_js(context): + """ + Creates a list of prepopulated_fields that should render Javascript for + the prepopulated fields for both the admin form and inlines. + """ + prepopulated_fields = [] + if context['add'] and 'adminform' in context: + prepopulated_fields.extend(context['adminform'].prepopulated_fields) + if 'inline_admin_formsets' in context: + for inline_admin_formset in context['inline_admin_formsets']: + for inline_admin_form in inline_admin_formset: + if inline_admin_form.original is None: + prepopulated_fields.extend(inline_admin_form.prepopulated_fields) + context.update({'prepopulated_fields': prepopulated_fields}) + return context +prepopulated_fields_js = register.inclusion_tag('admin/prepopulated_fields_js.html', takes_context=True)(prepopulated_fields_js) + +def submit_row(context): + opts = context['opts'] + change = context['change'] + is_popup = context['is_popup'] + save_as = context['save_as'] + return { + 'onclick_attrib': (opts.get_ordered_objects() and change + and 'onclick="submitOrderForm();"' or ''), + '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 save_as, + 'show_save_and_add_another': context['has_add_permission'] and + not is_popup and (not 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.html', takes_context=True)(submit_row) diff --git a/webapp/django/contrib/admin/templatetags/adminmedia.py b/webapp/django/contrib/admin/templatetags/adminmedia.py new file mode 100644 index 0000000000..77863435d4 --- /dev/null +++ b/webapp/django/contrib/admin/templatetags/adminmedia.py @@ -0,0 +1,14 @@ +from django.template import Library + +register = Library() + +def admin_media_prefix(): + """ + Returns the string contained in the setting ADMIN_MEDIA_PREFIX. + """ + try: + from django.conf import settings + except ImportError: + return '' + return settings.ADMIN_MEDIA_PREFIX +admin_media_prefix = register.simple_tag(admin_media_prefix) diff --git a/webapp/django/contrib/admin/templatetags/log.py b/webapp/django/contrib/admin/templatetags/log.py new file mode 100644 index 0000000000..8d52d2e944 --- /dev/null +++ b/webapp/django/contrib/admin/templatetags/log.py @@ -0,0 +1,56 @@ +from django import template +from django.contrib.admin.models import LogEntry + +register = template.Library() + +class AdminLogNode(template.Node): + def __init__(self, limit, varname, user): + self.limit, self.varname, self.user = limit, varname, user + + def __repr__(self): + return "<GetAdminLog Node>" + + def render(self, context): + if self.user is None: + context[self.varname] = LogEntry.objects.all().select_related()[:self.limit] + else: + if not self.user.isdigit(): + self.user = context[self.user].id + context[self.varname] = LogEntry.objects.filter(user__id__exact=self.user).select_related()[:self.limit] + return '' + +class DoGetAdminLog: + """ + Populates a template variable with the admin log for the given criteria. + + Usage:: + + {% get_admin_log [limit] as [varname] for_user [context_var_containing_user_obj] %} + + Examples:: + + {% get_admin_log 10 as admin_log for_user 23 %} + {% get_admin_log 10 as admin_log for_user user %} + {% get_admin_log 10 as admin_log %} + + Note that ``context_var_containing_user_obj`` can be a hard-coded integer + (user ID) or the name of a template context variable containing the user + object whose ID you want. + """ + def __init__(self, tag_name): + self.tag_name = tag_name + + def __call__(self, parser, token): + tokens = token.contents.split() + if len(tokens) < 4: + raise template.TemplateSyntaxError, "'%s' statements require two arguments" % self.tag_name + if not tokens[1].isdigit(): + raise template.TemplateSyntaxError, "First argument in '%s' must be an integer" % self.tag_name + if tokens[2] != 'as': + raise template.TemplateSyntaxError, "Second argument in '%s' must be 'as'" % self.tag_name + if len(tokens) > 4: + if tokens[4] != 'for_user': + raise template.TemplateSyntaxError, "Fourth argument in '%s' must be 'for_user'" % self.tag_name + return AdminLogNode(limit=tokens[1], varname=tokens[3], user=(len(tokens) > 5 and tokens[5] or None)) + +register.tag('get_admin_log', DoGetAdminLog('get_admin_log')) diff --git a/webapp/django/contrib/admin/util.py b/webapp/django/contrib/admin/util.py new file mode 100644 index 0000000000..c3f567d9dd --- /dev/null +++ b/webapp/django/contrib/admin/util.py @@ -0,0 +1,151 @@ +from django.core.exceptions import ObjectDoesNotExist +from django.db import models +from django.utils.html import escape +from django.utils.safestring import mark_safe +from django.utils.text import capfirst +from django.utils.encoding import force_unicode +from django.utils.translation import ugettext as _ + + +def quote(s): + """ + Ensure that primary key values do not confuse the admin URLs by escaping + any '/', '_' and ':' characters. Similar to urllib.quote, except that the + quoting is slightly different so that it doesn't get automatically + unquoted by the Web browser. + """ + if not isinstance(s, basestring): + return s + res = list(s) + for i in range(len(res)): + c = res[i] + if c in """:/_#?;@&=+$,"<>%\\""": + res[i] = '_%02X' % ord(c) + return ''.join(res) + +def unquote(s): + """ + Undo the effects of quote(). Based heavily on urllib.unquote(). + """ + mychr = chr + myatoi = int + list = s.split('_') + res = [list[0]] + myappend = res.append + del list[0] + for item in list: + if item[1:2]: + try: + myappend(mychr(myatoi(item[:2], 16)) + item[2:]) + except ValueError: + myappend('_' + item) + else: + myappend('_' + item) + return "".join(res) + +def flatten_fieldsets(fieldsets): + """Returns a list of field names from an admin fieldsets structure.""" + field_names = [] + for name, opts in fieldsets: + for field in opts['fields']: + # type checking feels dirty, but it seems like the best way here + if type(field) == tuple: + field_names.extend(field) + else: + field_names.append(field) + return field_names + +def _nest_help(obj, depth, val): + current = obj + for i in range(depth): + current = current[-1] + current.append(val) + +def get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current_depth, admin_site): + "Helper function that recursively populates deleted_objects." + nh = _nest_help # Bind to local variable for performance + if current_depth > 16: + return # Avoid recursing too deep. + opts_seen = [] + for related in opts.get_all_related_objects(): + has_admin = related.model in admin_site._registry + if related.opts in opts_seen: + continue + opts_seen.append(related.opts) + rel_opts_name = related.get_accessor_name() + if isinstance(related.field.rel, models.OneToOneRel): + try: + sub_obj = getattr(obj, rel_opts_name) + except ObjectDoesNotExist: + pass + else: + if has_admin: + p = '%s.%s' % (related.opts.app_label, related.opts.get_delete_permission()) + if not user.has_perm(p): + perms_needed.add(related.opts.verbose_name) + # We don't care about populating deleted_objects now. + continue + if related.field.rel.edit_inline or not has_admin: + # Don't display link to edit, because it either has no + # admin or is edited inline. + nh(deleted_objects, current_depth, [mark_safe(u'%s: %s' % (force_unicode(capfirst(related.opts.verbose_name)), sub_obj)), []]) + else: + # Display a link to the admin page. + nh(deleted_objects, current_depth, [mark_safe(u'%s: <a href="../../../../%s/%s/%s/">%s</a>' % + (escape(force_unicode(capfirst(related.opts.verbose_name))), + related.opts.app_label, + related.opts.object_name.lower(), + sub_obj._get_pk_val(), sub_obj)), []]) + get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2, admin_site) + else: + has_related_objs = False + for sub_obj in getattr(obj, rel_opts_name).all(): + has_related_objs = True + if related.field.rel.edit_inline or not has_admin: + # Don't display link to edit, because it either has no + # admin or is edited inline. + nh(deleted_objects, current_depth, [u'%s: %s' % (force_unicode(capfirst(related.opts.verbose_name)), escape(sub_obj)), []]) + else: + # Display a link to the admin page. + nh(deleted_objects, current_depth, [mark_safe(u'%s: <a href="../../../../%s/%s/%s/">%s</a>' % \ + (escape(force_unicode(capfirst(related.opts.verbose_name))), related.opts.app_label, related.opts.object_name.lower(), sub_obj._get_pk_val(), escape(sub_obj))), []]) + get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2, admin_site) + # If there were related objects, and the user doesn't have + # permission to delete them, add the missing perm to perms_needed. + if has_admin and has_related_objs: + p = '%s.%s' % (related.opts.app_label, related.opts.get_delete_permission()) + if not user.has_perm(p): + perms_needed.add(related.opts.verbose_name) + for related in opts.get_all_related_many_to_many_objects(): + has_admin = related.model in admin_site._registry + if related.opts in opts_seen: + continue + opts_seen.append(related.opts) + rel_opts_name = related.get_accessor_name() + has_related_objs = False + + # related.get_accessor_name() could return None for symmetrical relationships + if rel_opts_name: + rel_objs = getattr(obj, rel_opts_name, None) + if rel_objs: + has_related_objs = True + + if has_related_objs: + for sub_obj in rel_objs.all(): + if related.field.rel.edit_inline or not has_admin: + # Don't display link to edit, because it either has no + # admin or is edited inline. + nh(deleted_objects, current_depth, [_('One or more %(fieldname)s in %(name)s: %(obj)s') % \ + {'fieldname': force_unicode(related.field.verbose_name), 'name': force_unicode(related.opts.verbose_name), 'obj': escape(sub_obj)}, []]) + else: + # Display a link to the admin page. + nh(deleted_objects, current_depth, [ + mark_safe((_('One or more %(fieldname)s in %(name)s:') % {'fieldname': escape(force_unicode(related.field.verbose_name)), 'name': escape(force_unicode(related.opts.verbose_name))}) + \ + (u' <a href="../../../../%s/%s/%s/">%s</a>' % \ + (related.opts.app_label, related.opts.module_name, sub_obj._get_pk_val(), escape(sub_obj)))), []]) + # If there were related objects, and the user doesn't have + # permission to change them, add the missing perm to perms_needed. + if has_admin and has_related_objs: + p = u'%s.%s' % (related.opts.app_label, related.opts.get_change_permission()) + if not user.has_perm(p): + perms_needed.add(related.opts.verbose_name) diff --git a/webapp/django/contrib/admin/validation.py b/webapp/django/contrib/admin/validation.py new file mode 100644 index 0000000000..9b9cefab98 --- /dev/null +++ b/webapp/django/contrib/admin/validation.py @@ -0,0 +1,298 @@ +try: + set +except NameError: + from sets import Set as set # Python 2.3 fallback + +from django.core.exceptions import ImproperlyConfigured +from django.db import models +from django.forms.models import BaseModelForm, BaseModelFormSet, fields_for_model +from django.contrib.admin.options import flatten_fieldsets, BaseModelAdmin +from django.contrib.admin.options import HORIZONTAL, VERTICAL + +def validate(cls, model): + """ + Does basic ModelAdmin option validation. Calls custom validation + classmethod in the end if it is provided in cls. The signature of the + custom validation classmethod should be: def validate(cls, model). + """ + opts = model._meta + _validate_base(cls, model) + + # currying is expensive, use wrappers instead + def _check_istuplew(label, obj): + _check_istuple(cls, label, obj) + + def _check_isdictw(label, obj): + _check_isdict(cls, label, obj) + + def _check_field_existsw(label, field): + return _check_field_exists(cls, model, opts, label, field) + + def _check_attr_existsw(label, field): + return _check_attr_exists(cls, model, opts, label, field) + + # list_display + if hasattr(cls, 'list_display'): + _check_istuplew('list_display', cls.list_display) + for idx, field in enumerate(cls.list_display): + if not callable(field): + if not hasattr(cls, field): + if not hasattr(model, field): + try: + opts.get_field(field) + except models.FieldDoesNotExist: + raise ImproperlyConfigured("%s.list_display[%d], %r is " + "not a callable or an attribute of %r or found in the model %r." + % (cls.__name__, idx, field, cls.__name__, model._meta.object_name)) + f = _check_attr_existsw("list_display[%d]" % idx, field) + if isinstance(f, models.ManyToManyField): + raise ImproperlyConfigured("`%s.list_display[%d]`, `%s` is a " + "ManyToManyField which is not supported." + % (cls.__name__, idx, field)) + + # list_display_links + if hasattr(cls, 'list_display_links'): + _check_istuplew('list_display_links', cls.list_display_links) + for idx, field in enumerate(cls.list_display_links): + _check_attr_existsw('list_display_links[%d]' % idx, field) + if field not in cls.list_display: + raise ImproperlyConfigured("`%s.list_display_links[%d]`" + "refers to `%s` which is not defined in `list_display`." + % (cls.__name__, idx, field)) + + # list_filter + if hasattr(cls, 'list_filter'): + _check_istuplew('list_filter', cls.list_filter) + for idx, field in enumerate(cls.list_filter): + _check_field_existsw('list_filter[%d]' % idx, field) + + # list_per_page = 100 + if hasattr(cls, 'list_per_page') and not isinstance(cls.list_per_page, int): + raise ImproperlyConfigured("`%s.list_per_page` should be a integer." + % cls.__name__) + + # search_fields = () + if hasattr(cls, 'search_fields'): + _check_istuplew('search_fields', cls.search_fields) + + # date_hierarchy = None + if cls.date_hierarchy: + f = _check_field_existsw('date_hierarchy', cls.date_hierarchy) + if not isinstance(f, (models.DateField, models.DateTimeField)): + raise ImproperlyConfigured("`%s.date_hierarchy is " + "neither an instance of DateField nor DateTimeField." + % cls.__name__) + + # ordering = None + if cls.ordering: + _check_istuplew('ordering', cls.ordering) + for idx, field in enumerate(cls.ordering): + if field == '?' and len(cls.ordering) != 1: + raise ImproperlyConfigured("`%s.ordering` has the random " + "ordering marker `?`, but contains other fields as " + "well. Please either remove `?` or the other fields." + % cls.__name__) + if field == '?': + continue + if field.startswith('-'): + field = field[1:] + # Skip ordering in the format field1__field2 (FIXME: checking + # this format would be nice, but it's a little fiddly). + if '__' in field: + continue + _check_field_existsw('ordering[%d]' % idx, field) + + # list_select_related = False + # save_as = False + # save_on_top = False + for attr in ('list_select_related', 'save_as', 'save_on_top'): + if not isinstance(getattr(cls, attr), bool): + raise ImproperlyConfigured("`%s.%s` should be a boolean." + % (cls.__name__, attr)) + + # inlines = [] + if hasattr(cls, 'inlines'): + _check_istuplew('inlines', cls.inlines) + for idx, inline in enumerate(cls.inlines): + if not issubclass(inline, BaseModelAdmin): + raise ImproperlyConfigured("`%s.inlines[%d]` does not inherit " + "from BaseModelAdmin." % (cls.__name__, idx)) + if not inline.model: + raise ImproperlyConfigured("`model` is a required attribute " + "of `%s.inlines[%d]`." % (cls.__name__, idx)) + if not issubclass(inline.model, models.Model): + raise ImproperlyConfigured("`%s.inlines[%d].model` does not " + "inherit from models.Model." % (cls.__name__, idx)) + _validate_base(inline, inline.model) + _validate_inline(inline) + +def _validate_inline(cls): + # model is already verified to exist and be a Model + if cls.fk_name: # default value is None + f = _check_field_exists(cls, cls.model, cls.model._meta, + 'fk_name', cls.fk_name) + if not isinstance(f, models.ForeignKey): + raise ImproperlyConfigured("`%s.fk_name is not an instance of " + "models.ForeignKey." % cls.__name__) + # extra = 3 + # max_num = 0 + for attr in ('extra', 'max_num'): + if not isinstance(getattr(cls, attr), int): + raise ImproperlyConfigured("`%s.%s` should be a integer." + % (cls.__name__, attr)) + + # formset + if hasattr(cls, 'formset') and not issubclass(cls.formset, BaseModelFormSet): + raise ImproperlyConfigured("`%s.formset` does not inherit from " + "BaseModelFormSet." % cls.__name__) + +def _validate_base(cls, model): + opts = model._meta + # currying is expensive, use wrappers instead + def _check_istuplew(label, obj): + _check_istuple(cls, label, obj) + + def _check_isdictw(label, obj): + _check_isdict(cls, label, obj) + + def _check_field_existsw(label, field): + return _check_field_exists(cls, model, opts, label, field) + + def _check_form_field_existsw(label, field): + return _check_form_field_exists(cls, model, opts, label, field) + + # raw_id_fields + if hasattr(cls, 'raw_id_fields'): + _check_istuplew('raw_id_fields', cls.raw_id_fields) + for idx, field in enumerate(cls.raw_id_fields): + f = _check_field_existsw('raw_id_fields', field) + if not isinstance(f, (models.ForeignKey, models.ManyToManyField)): + raise ImproperlyConfigured("`%s.raw_id_fields[%d]`, `%s` must " + "be either a ForeignKey or ManyToManyField." + % (cls.__name__, idx, field)) + + # fields + if cls.fields: # default value is None + _check_istuplew('fields', cls.fields) + for field in cls.fields: + _check_form_field_existsw('fields', field) + if cls.fieldsets: + raise ImproperlyConfigured('Both fieldsets and fields are specified in %s.' % cls.__name__) + if len(cls.fields) > len(set(cls.fields)): + raise ImproperlyConfigured('There are duplicate field(s) in %s.fields' % cls.__name__) + + # fieldsets + if cls.fieldsets: # default value is None + _check_istuplew('fieldsets', cls.fieldsets) + for idx, fieldset in enumerate(cls.fieldsets): + _check_istuplew('fieldsets[%d]' % idx, fieldset) + if len(fieldset) != 2: + raise ImproperlyConfigured("`%s.fieldsets[%d]` does not " + "have exactly two elements." % (cls.__name__, idx)) + _check_isdictw('fieldsets[%d][1]' % idx, fieldset[1]) + if 'fields' not in fieldset[1]: + raise ImproperlyConfigured("`fields` key is required in " + "%s.fieldsets[%d][1] field options dict." + % (cls.__name__, idx)) + flattened_fieldsets = flatten_fieldsets(cls.fieldsets) + if len(flattened_fieldsets) > len(set(flattened_fieldsets)): + raise ImproperlyConfigured('There are duplicate field(s) in %s.fieldsets' % cls.__name__) + for field in flattened_fieldsets: + _check_form_field_existsw("fieldsets[%d][1]['fields']" % idx, field) + + # form + if hasattr(cls, 'form') and not issubclass(cls.form, BaseModelForm): + raise ImproperlyConfigured("%s.form does not inherit from " + "BaseModelForm." % cls.__name__) + + # filter_vertical + if hasattr(cls, 'filter_vertical'): + _check_istuplew('filter_vertical', cls.filter_vertical) + for idx, field in enumerate(cls.filter_vertical): + f = _check_field_existsw('filter_vertical', field) + if not isinstance(f, models.ManyToManyField): + raise ImproperlyConfigured("`%s.filter_vertical[%d]` must be " + "a ManyToManyField." % (cls.__name__, idx)) + + # filter_horizontal + if hasattr(cls, 'filter_horizontal'): + _check_istuplew('filter_horizontal', cls.filter_horizontal) + for idx, field in enumerate(cls.filter_horizontal): + f = _check_field_existsw('filter_horizontal', field) + if not isinstance(f, models.ManyToManyField): + raise ImproperlyConfigured("`%s.filter_horizontal[%d]` must be " + "a ManyToManyField." % (cls.__name__, idx)) + + # radio_fields + if hasattr(cls, 'radio_fields'): + _check_isdictw('radio_fields', cls.radio_fields) + for field, val in cls.radio_fields.items(): + f = _check_field_existsw('radio_fields', field) + if not (isinstance(f, models.ForeignKey) or f.choices): + raise ImproperlyConfigured("`%s.radio_fields['%s']` " + "is neither an instance of ForeignKey nor does " + "have choices set." % (cls.__name__, field)) + if not val in (HORIZONTAL, VERTICAL): + raise ImproperlyConfigured("`%s.radio_fields['%s']` " + "is neither admin.HORIZONTAL nor admin.VERTICAL." + % (cls.__name__, field)) + + # prepopulated_fields + if hasattr(cls, 'prepopulated_fields'): + _check_isdictw('prepopulated_fields', cls.prepopulated_fields) + for field, val in cls.prepopulated_fields.items(): + f = _check_field_existsw('prepopulated_fields', field) + if isinstance(f, (models.DateTimeField, models.ForeignKey, + models.ManyToManyField)): + raise ImproperlyConfigured("`%s.prepopulated_fields['%s']` " + "is either a DateTimeField, ForeignKey or " + "ManyToManyField. This isn't allowed." + % (cls.__name__, field)) + _check_istuplew("prepopulated_fields['%s']" % field, val) + for idx, f in enumerate(val): + _check_field_existsw("prepopulated_fields['%s'][%d]" + % (f, idx), f) + +def _check_istuple(cls, label, obj): + if not isinstance(obj, (list, tuple)): + raise ImproperlyConfigured("`%s.%s` must be a " + "list or tuple." % (cls.__name__, label)) + +def _check_isdict(cls, label, obj): + if not isinstance(obj, dict): + raise ImproperlyConfigured("`%s.%s` must be a dictionary." + % (cls.__name__, label)) + +def _check_field_exists(cls, model, opts, label, field): + try: + return opts.get_field(field) + except models.FieldDoesNotExist: + raise ImproperlyConfigured("`%s.%s` refers to " + "field `%s` that is missing from model `%s`." + % (cls.__name__, label, field, model.__name__)) + +def _check_form_field_exists(cls, model, opts, label, field): + if hasattr(cls.form, 'base_fields'): + try: + cls.form.base_fields[field] + except KeyError: + raise ImproperlyConfigured("`%s.%s` refers to field `%s` that " + "is missing from the form." % (cls.__name__, label, field)) + else: + fields = fields_for_model(model) + try: + fields[field] + except KeyError: + raise ImproperlyConfigured("`%s.%s` refers to field `%s` that " + "is missing from the form." % (cls.__name__, label, field)) + +def _check_attr_exists(cls, model, opts, label, field): + try: + return opts.get_field(field) + except models.FieldDoesNotExist: + if not hasattr(model, field): + raise ImproperlyConfigured("`%s.%s` refers to " + "`%s` that is neither a field, method or property " + "of model `%s`." + % (cls.__name__, label, field, model.__name__)) + return getattr(model, field) diff --git a/webapp/django/contrib/admin/views/__init__.py b/webapp/django/contrib/admin/views/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/webapp/django/contrib/admin/views/__init__.py diff --git a/webapp/django/contrib/admin/views/decorators.py b/webapp/django/contrib/admin/views/decorators.py new file mode 100644 index 0000000000..f3c63ff70c --- /dev/null +++ b/webapp/django/contrib/admin/views/decorators.py @@ -0,0 +1,114 @@ +import base64 +import cPickle as pickle +try: + from functools import wraps +except ImportError: + from django.utils.functional import wraps # Python 2.3, 2.4 fallback. + +from django import http, template +from django.conf import settings +from django.contrib.auth.models import User +from django.contrib.auth import authenticate, login +from django.shortcuts import render_to_response +from django.utils.translation import ugettext_lazy, ugettext as _ +from django.utils.hashcompat import md5_constructor + +ERROR_MESSAGE = ugettext_lazy("Please enter a correct username and password. Note that both fields are case-sensitive.") +LOGIN_FORM_KEY = 'this_is_the_login_form' + +def _display_login_form(request, error_message=''): + request.session.set_test_cookie() + if request.POST and 'post_data' in request.POST: + # User has failed login BUT has previously saved post data. + post_data = request.POST['post_data'] + elif request.POST: + # User's session must have expired; save their post data. + post_data = _encode_post_data(request.POST) + else: + post_data = _encode_post_data({}) + return render_to_response('admin/login.html', { + 'title': _('Log in'), + 'app_path': request.get_full_path(), + 'post_data': post_data, + 'error_message': error_message + }, context_instance=template.RequestContext(request)) + +def _encode_post_data(post_data): + pickled = pickle.dumps(post_data) + pickled_md5 = md5_constructor(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_constructor(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) + +def staff_member_required(view_func): + """ + Decorator for views that checks that the user is logged in and is a staff + member, displaying the login page if necessary. + """ + def _checklogin(request, *args, **kwargs): + if request.user.is_authenticated() and request.user.is_staff: + # The user is valid. Continue to the admin page. + if 'post_data' in request.POST: + # User must have re-authenticated through a different window + # or tab. + 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.contrib.sessions.middleware.SessionMiddleware'." + + # If this isn't already the login page, display it. + if LOGIN_FORM_KEY not in request.POST: + if request.POST: + message = _("Please log in again, because your session has expired. Don't worry: Your submission has been saved.") + else: + message = "" + return _display_login_form(request, message) + + # Check that the user accepts cookies. + if not request.session.test_cookie_worked(): + message = _("Looks like your browser isn't configured to accept cookies. Please enable cookies, reload this page, and try again.") + return _display_login_form(request, message) + else: + request.session.delete_test_cookie() + + # Check the password. + username = request.POST.get('username', None) + password = request.POST.get('password', None) + user = authenticate(username=username, password=password) + if user is None: + message = ERROR_MESSAGE + if '@' in username: + # Mistakenly entered e-mail address instead of username? Look it up. + users = list(User.objects.filter(email=username)) + if len(users) == 1 and users[0].check_password(password): + message = _("Your e-mail address is not your username. Try '%s' instead.") % users[0].username + else: + # Either we cannot find the user, or if more than 1 + # we cannot guess which user is the correct one. + message = _("Usernames cannot contain the '@' character.") + return _display_login_form(request, message) + + # The user data is correct; log in the user in and continue. + else: + if user.is_active and user.is_staff: + login(request, user) + # TODO: set last_login with an event. + if 'post_data' in request.POST: + post_data = _decode_post_data(request.POST['post_data']) + if post_data and LOGIN_FORM_KEY not in post_data: + # overwrite request.POST with the saved post_data, and continue + request.POST = post_data + request.user = user + return view_func(request, *args, **kwargs) + else: + return http.HttpResponseRedirect(request.get_full_path()) + else: + return _display_login_form(request, ERROR_MESSAGE) + + return wraps(view_func)(_checklogin) diff --git a/webapp/django/contrib/admin/views/main.py b/webapp/django/contrib/admin/views/main.py new file mode 100644 index 0000000000..50b015352d --- /dev/null +++ b/webapp/django/contrib/admin/views/main.py @@ -0,0 +1,233 @@ +from django.contrib.admin.filterspecs import FilterSpec +from django.contrib.admin.options import IncorrectLookupParameters +from django.contrib.admin.util import quote +from django.core.paginator import Paginator, InvalidPage +from django.db import models +from django.db.models.query import QuerySet +from django.utils.encoding import force_unicode, smart_str +from django.utils.translation import ugettext +from django.utils.http import urlencode +import operator + +try: + set +except NameError: + from sets import Set as set # Python 2.3 fallback + +# The system will display a "Show all" link on the change list only if the +# total result count is less than or equal to this setting. +MAX_SHOW_ALL_ALLOWED = 200 + +# Changelist settings +ALL_VAR = 'all' +ORDER_VAR = 'o' +ORDER_TYPE_VAR = 'ot' +PAGE_VAR = 'p' +SEARCH_VAR = 'q' +IS_POPUP_VAR = 'pop' +ERROR_FLAG = 'e' + +# Text to display within change-list table cells if the value is blank. +EMPTY_CHANGELIST_VALUE = '(None)' + +class ChangeList(object): + def __init__(self, request, model, list_display, list_display_links, list_filter, date_hierarchy, search_fields, list_select_related, list_per_page, model_admin): + self.model = model + self.opts = model._meta + self.lookup_opts = self.opts + self.root_query_set = model_admin.queryset(request) + self.list_display = list_display + self.list_display_links = list_display_links + self.list_filter = list_filter + self.date_hierarchy = date_hierarchy + self.search_fields = search_fields + self.list_select_related = list_select_related + self.list_per_page = list_per_page + self.model_admin = model_admin + + # Get search parameters from the query string. + try: + self.page_num = int(request.GET.get(PAGE_VAR, 0)) + except ValueError: + self.page_num = 0 + self.show_all = ALL_VAR in request.GET + self.is_popup = IS_POPUP_VAR in request.GET + self.params = dict(request.GET.items()) + if PAGE_VAR in self.params: + del self.params[PAGE_VAR] + if ERROR_FLAG in self.params: + del self.params[ERROR_FLAG] + + self.order_field, self.order_type = self.get_ordering() + self.query = request.GET.get(SEARCH_VAR, '') + self.query_set = self.get_query_set() + self.get_results(request) + self.title = (self.is_popup and ugettext('Select %s') % force_unicode(self.opts.verbose_name) or ugettext('Select %s to change') % force_unicode(self.opts.verbose_name)) + self.filter_specs, self.has_filters = self.get_filters(request) + self.pk_attname = self.lookup_opts.pk.attname + + def get_filters(self, request): + filter_specs = [] + if self.list_filter: + filter_fields = [self.lookup_opts.get_field(field_name) for field_name in self.list_filter] + for f in filter_fields: + spec = FilterSpec.create(f, request, self.params, self.model, self.model_admin) + if spec and spec.has_output(): + filter_specs.append(spec) + return filter_specs, bool(filter_specs) + + def get_query_string(self, new_params=None, remove=None): + if new_params is None: new_params = {} + if remove is None: remove = [] + p = self.params.copy() + for r in remove: + for k in p.keys(): + if k.startswith(r): + del p[k] + for k, v in new_params.items(): + if v is None: + if k in p: + del p[k] + else: + p[k] = v + return '?%s' % urlencode(p) + + def get_results(self, request): + paginator = Paginator(self.query_set, self.list_per_page) + # Get the number of objects, with admin filters applied. + try: + result_count = paginator.count + # Naked except! Because we don't have any other way of validating + # "params". They might be invalid if the keyword arguments are + # incorrect, or if the values are not in the correct type (which would + # result in a database error). + except: + raise IncorrectLookupParameters + + # Get the total number of objects, with no admin filters applied. + # Perform a slight optimization: Check to see whether any filters were + # given. If not, use paginator.hits to calculate the number of objects, + # because we've already done paginator.hits and the value is cached. + if not self.query_set.query.where: + full_result_count = result_count + else: + full_result_count = self.root_query_set.count() + + can_show_all = result_count <= MAX_SHOW_ALL_ALLOWED + multi_page = result_count > self.list_per_page + + # Get the list of objects to display on this page. + if (self.show_all and can_show_all) or not multi_page: + result_list = list(self.query_set) + else: + try: + result_list = paginator.page(self.page_num+1).object_list + except InvalidPage: + result_list = () + + self.result_count = result_count + self.full_result_count = full_result_count + self.result_list = result_list + self.can_show_all = can_show_all + self.multi_page = multi_page + self.paginator = paginator + + def get_ordering(self): + lookup_opts, params = self.lookup_opts, self.params + # For ordering, first check the "ordering" parameter in the admin + # options, then check the object's default ordering. If neither of + # those exist, order descending by ID by default. Finally, look for + # manually-specified ordering from the query string. + ordering = self.model_admin.ordering or lookup_opts.ordering or ['-' + lookup_opts.pk.name] + + if ordering[0].startswith('-'): + order_field, order_type = ordering[0][1:], 'desc' + else: + order_field, order_type = ordering[0], 'asc' + if ORDER_VAR in params: + try: + field_name = self.list_display[int(params[ORDER_VAR])] + try: + f = lookup_opts.get_field(field_name) + except models.FieldDoesNotExist: + # See whether field_name is a name of a non-field + # that allows sorting. + try: + attr = getattr(self.model, field_name) + order_field = attr.admin_order_field + except AttributeError: + pass + else: + if not isinstance(f.rel, models.ManyToOneRel) or not f.null: + order_field = f.name + except (IndexError, ValueError): + pass # Invalid ordering specified. Just use the default. + if ORDER_TYPE_VAR in params and params[ORDER_TYPE_VAR] in ('asc', 'desc'): + order_type = params[ORDER_TYPE_VAR] + return order_field, order_type + + def get_query_set(self): + qs = self.root_query_set + lookup_params = self.params.copy() # a dictionary of the query string + for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR): + if i in lookup_params: + del lookup_params[i] + for key, value in lookup_params.items(): + if not isinstance(key, str): + # 'key' will be used as a keyword argument later, so Python + # requires it to be a string. + del lookup_params[key] + lookup_params[smart_str(key)] = value + + # Apply lookup parameters from the query string. + qs = qs.filter(**lookup_params) + + # Use select_related() if one of the list_display options is a field + # with a relationship. + if self.list_select_related: + qs = qs.select_related() + else: + for field_name in self.list_display: + try: + f = self.lookup_opts.get_field(field_name) + except models.FieldDoesNotExist: + pass + else: + if isinstance(f.rel, models.ManyToOneRel): + qs = qs.select_related() + break + + # Set ordering. + if self.order_field: + qs = qs.order_by('%s%s' % ((self.order_type == 'desc' and '-' or ''), self.order_field)) + + # Apply keyword searches. + def construct_search(field_name): + if field_name.startswith('^'): + return "%s__istartswith" % field_name[1:] + elif field_name.startswith('='): + return "%s__iexact" % field_name[1:] + elif field_name.startswith('@'): + return "%s__search" % field_name[1:] + else: + return "%s__icontains" % field_name + + if self.search_fields and self.query: + for bit in self.query.split(): + or_queries = [models.Q(**{construct_search(field_name): bit}) for field_name in self.search_fields] + other_qs = QuerySet(self.model) + other_qs.dup_select_related(qs) + other_qs = other_qs.filter(reduce(operator.or_, or_queries)) + qs = qs & other_qs + for field_name in self.search_fields: + if '__' in field_name: + qs = qs.distinct() + break + + if self.opts.one_to_one_field: + qs = qs.complex_filter(self.opts.one_to_one_field.rel.limit_choices_to) + + return qs + + def url_for_result(self, result): + return "%s/" % quote(getattr(result, self.pk_attname)) diff --git a/webapp/django/contrib/admin/views/template.py b/webapp/django/contrib/admin/views/template.py new file mode 100644 index 0000000000..de9320bfc2 --- /dev/null +++ b/webapp/django/contrib/admin/views/template.py @@ -0,0 +1,77 @@ +from django import template, forms +from django.contrib.admin.views.decorators import staff_member_required +from django.template import loader +from django.shortcuts import render_to_response +from django.contrib.sites.models import Site +from django.conf import settings +from django.utils.translation import ugettext_lazy as _ + + +def template_validator(request): + """ + Displays the template validator form, which finds and displays template + syntax errors. + """ + # get a dict of {site_id : settings_module} for the validator + settings_modules = {} + for mod in settings.ADMIN_FOR: + settings_module = __import__(mod, {}, {}, ['']) + settings_modules[settings_module.SITE_ID] = settings_module + site_list = Site.objects.in_bulk(settings_modules.keys()).values() + if request.POST: + form = TemplateValidatorForm(settings_modules, site_list, + data=request.POST) + if form.is_valid(): + request.user.message_set.create(message='The template is valid.') + else: + form = TemplateValidatorForm(settings_modules, site_list) + return render_to_response('admin/template_validator.html', { + 'title': 'Template validator', + 'form': form, + }, context_instance=template.RequestContext(request)) +template_validator = staff_member_required(template_validator) + + +class TemplateValidatorForm(forms.Form): + site = forms.ChoiceField(_('site')) + template = forms.CharField( + _('template'), widget=forms.Textarea({'rows': 25, 'cols': 80})) + + def __init__(self, settings_modules, site_list, *args, **kwargs): + self.settings_modules = settings_modules + super(TemplateValidatorForm, self).__init__(*args, **kwargs) + self.fields['site'].choices = [(s.id, s.name) for s in site_list] + + def clean_template(self): + # Get the settings module. If the site isn't set, we don't raise an + # error since the site field will. + try: + site_id = int(self.cleaned_data.get('site', None)) + except (ValueError, TypeError): + return + settings_module = self.settings_modules.get(site_id, None) + if settings_module is None: + return + + # So that inheritance works in the site's context, register a new + # function for "extends" that uses the site's TEMPLATE_DIRS instead. + def new_do_extends(parser, token): + node = loader.do_extends(parser, token) + node.template_dirs = settings_module.TEMPLATE_DIRS + return node + register = template.Library() + register.tag('extends', new_do_extends) + template.builtins.append(register) + + # Now validate the template using the new TEMPLATE_DIRS, making sure to + # reset the extends function in any case. + error = None + template_string = self.cleaned_data['template'] + try: + tmpl = loader.get_template_from_string(template_string) + tmpl.render(template.Context({})) + except template.TemplateSyntaxError, e: + error = e + template.builtins.remove(register) + if error: + raise forms.ValidationError, e.args diff --git a/webapp/django/contrib/admin/widgets.py b/webapp/django/contrib/admin/widgets.py new file mode 100644 index 0000000000..2bfbc9715d --- /dev/null +++ b/webapp/django/contrib/admin/widgets.py @@ -0,0 +1,238 @@ +""" +Form Widget classes specific to the Django admin site. +""" + +import copy + +from django import forms +from django.forms.widgets import RadioFieldRenderer +from django.forms.util import flatatt +from django.utils.text import truncate_words +from django.utils.translation import ugettext as _ +from django.utils.safestring import mark_safe +from django.utils.encoding import force_unicode +from django.conf import settings + +class FilteredSelectMultiple(forms.SelectMultiple): + """ + A SelectMultiple with a JavaScript filter interface. + + Note that the resulting JavaScript assumes that the SelectFilter2.js + library and its dependencies have been loaded in the HTML page. + """ + def __init__(self, verbose_name, is_stacked, attrs=None, choices=()): + self.verbose_name = verbose_name + self.is_stacked = is_stacked + super(FilteredSelectMultiple, self).__init__(attrs, choices) + + def render(self, name, value, attrs=None, choices=()): + output = [super(FilteredSelectMultiple, self).render(name, value, attrs, choices)] + output.append(u'<script type="text/javascript">addEvent(window, "load", function(e) {') + # TODO: "id_" is hard-coded here. This should instead use the correct + # API to determine the ID dynamically. + output.append(u'SelectFilter.init("id_%s", "%s", %s, "%s"); });</script>\n' % \ + (name, self.verbose_name.replace('"', '\\"'), int(self.is_stacked), settings.ADMIN_MEDIA_PREFIX)) + return mark_safe(u''.join(output)) + +class AdminDateWidget(forms.TextInput): + class Media: + js = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js", + settings.ADMIN_MEDIA_PREFIX + "js/admin/DateTimeShortcuts.js") + + def __init__(self, attrs={}): + super(AdminDateWidget, self).__init__(attrs={'class': 'vDateField', 'size': '10'}) + +class AdminTimeWidget(forms.TextInput): + class Media: + js = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js", + settings.ADMIN_MEDIA_PREFIX + "js/admin/DateTimeShortcuts.js") + + def __init__(self, attrs={}): + super(AdminTimeWidget, self).__init__(attrs={'class': 'vTimeField', 'size': '8'}) + +class AdminSplitDateTime(forms.SplitDateTimeWidget): + """ + A SplitDateTime Widget that has some admin-specific styling. + """ + def __init__(self, attrs=None): + widgets = [AdminDateWidget, AdminTimeWidget] + # Note that we're calling MultiWidget, not SplitDateTimeWidget, because + # we want to define widgets. + forms.MultiWidget.__init__(self, widgets, attrs) + + def format_output(self, rendered_widgets): + return mark_safe(u'<p class="datetime">%s %s<br />%s %s</p>' % \ + (_('Date:'), rendered_widgets[0], _('Time:'), rendered_widgets[1])) + +class AdminRadioFieldRenderer(RadioFieldRenderer): + def render(self): + """Outputs a <ul> for this set of radio fields.""" + return mark_safe(u'<ul%s>\n%s\n</ul>' % ( + flatatt(self.attrs), + u'\n'.join([u'<li>%s</li>' % force_unicode(w) for w in self])) + ) + +class AdminRadioSelect(forms.RadioSelect): + renderer = AdminRadioFieldRenderer + +class AdminFileWidget(forms.FileInput): + """ + A FileField Widget that shows its current value if it has one. + """ + def __init__(self, attrs={}): + super(AdminFileWidget, self).__init__(attrs) + + def render(self, name, value, attrs=None): + output = [] + if value and hasattr(value, "url"): + output.append('%s <a target="_blank" href="%s">%s</a> <br />%s ' % \ + (_('Currently:'), value.url, value, _('Change:'))) + output.append(super(AdminFileWidget, self).render(name, value, attrs)) + return mark_safe(u''.join(output)) + +class ForeignKeyRawIdWidget(forms.TextInput): + """ + A Widget for displaying ForeignKeys in the "raw_id" interface rather than + in a <select> box. + """ + def __init__(self, rel, attrs=None): + self.rel = rel + super(ForeignKeyRawIdWidget, self).__init__(attrs) + + def render(self, name, value, attrs=None): + related_url = '../../../%s/%s/' % (self.rel.to._meta.app_label, self.rel.to._meta.object_name.lower()) + if self.rel.limit_choices_to: + url = '?' + '&'.join(['%s=%s' % (k, v) for k, v in self.rel.limit_choices_to.items()]) + else: + url = '' + if not attrs.has_key('class'): + attrs['class'] = 'vForeignKeyRawIdAdminField' # The JavaScript looks for this hook. + output = [super(ForeignKeyRawIdWidget, self).render(name, value, attrs)] + # TODO: "id_" is hard-coded here. This should instead use the correct + # API to determine the ID dynamically. + output.append('<a href="%s%s" class="related-lookup" id="lookup_id_%s" onclick="return showRelatedObjectLookupPopup(this);"> ' % \ + (related_url, url, name)) + output.append('<img src="%simg/admin/selector-search.gif" width="16" height="16" alt="Lookup" /></a>' % settings.ADMIN_MEDIA_PREFIX) + if value: + output.append(self.label_for_value(value)) + return mark_safe(u''.join(output)) + + def label_for_value(self, value): + return ' <strong>%s</strong>' % \ + truncate_words(self.rel.to.objects.get(pk=value), 14) + +class ManyToManyRawIdWidget(ForeignKeyRawIdWidget): + """ + A Widget for displaying ManyToMany ids in the "raw_id" interface rather than + in a <select multiple> box. + """ + def __init__(self, rel, attrs=None): + super(ManyToManyRawIdWidget, self).__init__(rel, attrs) + + def render(self, name, value, attrs=None): + attrs['class'] = 'vManyToManyRawIdAdminField' + if value: + value = ','.join([str(v) for v in value]) + else: + value = '' + return super(ManyToManyRawIdWidget, self).render(name, value, attrs) + + def label_for_value(self, value): + return '' + + def value_from_datadict(self, data, files, name): + value = data.get(name, None) + if value and ',' in value: + return data[name].split(',') + if value: + return [value] + return None + + def _has_changed(self, initial, data): + if initial is None: + initial = [] + if data is None: + data = [] + if len(initial) != len(data): + return True + for pk1, pk2 in zip(initial, data): + if force_unicode(pk1) != force_unicode(pk2): + return True + return False + +class RelatedFieldWidgetWrapper(forms.Widget): + """ + This class is a wrapper to a given widget to add the add icon for the + admin interface. + """ + def __init__(self, widget, rel, admin_site): + self.is_hidden = widget.is_hidden + self.needs_multipart_form = widget.needs_multipart_form + self.attrs = widget.attrs + self.choices = widget.choices + self.widget = widget + self.rel = rel + # so we can check if the related object is registered with this AdminSite + self.admin_site = admin_site + + def __deepcopy__(self, memo): + obj = copy.copy(self) + obj.widget = copy.deepcopy(self.widget, memo) + obj.attrs = self.widget.attrs + memo[id(self)] = obj + return obj + + def render(self, name, value, *args, **kwargs): + rel_to = self.rel.to + related_url = '../../../%s/%s/' % (rel_to._meta.app_label, rel_to._meta.object_name.lower()) + self.widget.choices = self.choices + output = [self.widget.render(name, value, *args, **kwargs)] + if rel_to in self.admin_site._registry: # If the related object has an admin interface: + # TODO: "id_" is hard-coded here. This should instead use the correct + # API to determine the ID dynamically. + output.append(u'<a href="%sadd/" class="add-another" id="add_id_%s" onclick="return showAddAnotherPopup(this);"> ' % \ + (related_url, name)) + output.append(u'<img src="%simg/admin/icon_addlink.gif" width="10" height="10" alt="Add Another"/></a>' % settings.ADMIN_MEDIA_PREFIX) + return mark_safe(u''.join(output)) + + def build_attrs(self, extra_attrs=None, **kwargs): + "Helper function for building an attribute dictionary." + self.attrs = self.widget.build_attrs(extra_attrs=None, **kwargs) + return self.attrs + + def value_from_datadict(self, data, files, name): + return self.widget.value_from_datadict(data, files, name) + + def _has_changed(self, initial, data): + return self.widget._has_changed(initial, data) + + def id_for_label(self, id_): + return self.widget.id_for_label(id_) + +class AdminTextareaWidget(forms.Textarea): + def __init__(self, attrs=None): + final_attrs = {'class': 'vLargeTextField'} + if attrs is not None: + final_attrs.update(attrs) + super(AdminTextareaWidget, self).__init__(attrs=final_attrs) + +class AdminTextInputWidget(forms.TextInput): + def __init__(self, attrs=None): + final_attrs = {'class': 'vTextField'} + if attrs is not None: + final_attrs.update(attrs) + super(AdminTextInputWidget, self).__init__(attrs=final_attrs) + +class AdminURLFieldWidget(forms.TextInput): + def __init__(self, attrs=None): + final_attrs = {'class': 'vURLField'} + if attrs is not None: + final_attrs.update(attrs) + super(AdminURLFieldWidget, self).__init__(attrs=final_attrs) + +class AdminIntegerFieldWidget(forms.TextInput): + def __init__(self, attrs=None): + final_attrs = {'class': 'vIntegerField'} + if attrs is not None: + final_attrs.update(attrs) + super(AdminIntegerFieldWidget, self).__init__(attrs=final_attrs) |