summaryrefslogtreecommitdiffstats
path: root/webapp/django/contrib/admin
diff options
context:
space:
mode:
Diffstat (limited to 'webapp/django/contrib/admin')
-rw-r--r--webapp/django/contrib/admin/__init__.py19
-rw-r--r--webapp/django/contrib/admin/filterspecs.py179
-rw-r--r--webapp/django/contrib/admin/helpers.py141
-rw-r--r--webapp/django/contrib/admin/media/css/base.css14
-rw-r--r--webapp/django/contrib/admin/media/css/changelists.css50
-rw-r--r--webapp/django/contrib/admin/media/css/dashboard.css10
-rw-r--r--webapp/django/contrib/admin/media/css/forms.css83
-rw-r--r--webapp/django/contrib/admin/media/css/global.css142
-rw-r--r--webapp/django/contrib/admin/media/css/layout.css29
-rw-r--r--webapp/django/contrib/admin/media/css/login.css13
-rw-r--r--webapp/django/contrib/admin/media/css/null.css1
-rw-r--r--webapp/django/contrib/admin/media/css/patch-iewin.css8
-rw-r--r--webapp/django/contrib/admin/media/css/rtl.css51
-rw-r--r--webapp/django/contrib/admin/media/css/widgets.css101
-rw-r--r--webapp/django/contrib/admin/media/img/admin/arrow-down.gifbin0 -> 80 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/arrow-up.gifbin0 -> 838 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/changelist-bg.gifbin0 -> 58 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/changelist-bg_rtl.gifbin0 -> 75 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/chooser-bg.gifbin0 -> 199 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/chooser_stacked-bg.gifbin0 -> 212 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/default-bg-reverse.gifbin0 -> 843 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/default-bg.gifbin0 -> 844 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/deleted-overlay.gifbin0 -> 45 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/icon-no.gifbin0 -> 176 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/icon-unknown.gifbin0 -> 130 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/icon-yes.gifbin0 -> 299 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/icon_addlink.gifbin0 -> 119 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/icon_alert.gifbin0 -> 145 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/icon_calendar.gifbin0 -> 192 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/icon_changelink.gifbin0 -> 119 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/icon_clock.gifbin0 -> 390 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/icon_deletelink.gifbin0 -> 181 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/icon_error.gifbin0 -> 319 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/icon_searchbox.pngbin0 -> 667 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/icon_success.gifbin0 -> 341 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/inline-delete-8bit.pngbin0 -> 477 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/inline-delete.pngbin0 -> 781 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/inline-restore-8bit.pngbin0 -> 447 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/inline-restore.pngbin0 -> 623 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/inline-splitter-bg.gifbin0 -> 102 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/nav-bg-grabber.gifbin0 -> 116 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/nav-bg-reverse.gifbin0 -> 186 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/nav-bg.gifbin0 -> 273 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/selector-add.gifbin0 -> 606 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/selector-addall.gifbin0 -> 358 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/selector-remove.gifbin0 -> 398 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/selector-removeall.gifbin0 -> 355 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/selector-search.gifbin0 -> 552 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/selector_stacked-add.gifbin0 -> 612 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/selector_stacked-remove.gifbin0 -> 401 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/tool-left.gifbin0 -> 197 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/tool-left_over.gifbin0 -> 203 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/tool-right.gifbin0 -> 198 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/tool-right_over.gifbin0 -> 200 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/tooltag-add.gifbin0 -> 932 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/tooltag-add_over.gifbin0 -> 336 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/tooltag-arrowright.gifbin0 -> 351 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/admin/tooltag-arrowright_over.gifbin0 -> 354 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/gis/move_vertex_off.pngbin0 -> 711 bytes
-rw-r--r--webapp/django/contrib/admin/media/img/gis/move_vertex_on.pngbin0 -> 506 bytes
-rw-r--r--webapp/django/contrib/admin/media/js/SelectBox.js111
-rw-r--r--webapp/django/contrib/admin/media/js/SelectFilter2.js113
-rw-r--r--webapp/django/contrib/admin/media/js/admin/CollapsedFieldsets.js85
-rw-r--r--webapp/django/contrib/admin/media/js/admin/DateTimeShortcuts.js254
-rw-r--r--webapp/django/contrib/admin/media/js/admin/RelatedObjectLookups.js77
-rw-r--r--webapp/django/contrib/admin/media/js/admin/ordering.js137
-rw-r--r--webapp/django/contrib/admin/media/js/calendar.js143
-rw-r--r--webapp/django/contrib/admin/media/js/core.js176
-rw-r--r--webapp/django/contrib/admin/media/js/dateparse.js233
-rw-r--r--webapp/django/contrib/admin/media/js/getElementsBySelector.js167
-rw-r--r--webapp/django/contrib/admin/media/js/timeparse.js94
-rw-r--r--webapp/django/contrib/admin/media/js/urlify.js140
-rw-r--r--webapp/django/contrib/admin/models.py54
-rw-r--r--webapp/django/contrib/admin/options.py790
-rw-r--r--webapp/django/contrib/admin/sites.py413
-rw-r--r--webapp/django/contrib/admin/templates/admin/404.html12
-rw-r--r--webapp/django/contrib/admin/templates/admin/500.html12
-rw-r--r--webapp/django/contrib/admin/templates/admin/app_index.html15
-rw-r--r--webapp/django/contrib/admin/templates/admin/auth/user/add_form.html33
-rw-r--r--webapp/django/contrib/admin/templates/admin/auth/user/change_password.html52
-rw-r--r--webapp/django/contrib/admin/templates/admin/base.html55
-rw-r--r--webapp/django/contrib/admin/templates/admin/base_site.html10
-rw-r--r--webapp/django/contrib/admin/templates/admin/change_form.html66
-rw-r--r--webapp/django/contrib/admin/templates/admin/change_list.html38
-rw-r--r--webapp/django/contrib/admin/templates/admin/change_list_results.html17
-rw-r--r--webapp/django/contrib/admin/templates/admin/date_hierarchy.html10
-rw-r--r--webapp/django/contrib/admin/templates/admin/delete_confirmation.html32
-rw-r--r--webapp/django/contrib/admin/templates/admin/edit_inline/stacked.html26
-rw-r--r--webapp/django/contrib/admin/templates/admin/edit_inline/tabular.html67
-rw-r--r--webapp/django/contrib/admin/templates/admin/filter.html8
-rw-r--r--webapp/django/contrib/admin/templates/admin/includes/fieldset.html17
-rw-r--r--webapp/django/contrib/admin/templates/admin/index.html68
-rw-r--r--webapp/django/contrib/admin/templates/admin/invalid_setup.html8
-rw-r--r--webapp/django/contrib/admin/templates/admin/login.html35
-rw-r--r--webapp/django/contrib/admin/templates/admin/object_history.html36
-rw-r--r--webapp/django/contrib/admin/templates/admin/pagination.html11
-rw-r--r--webapp/django/contrib/admin/templates/admin/prepopulated_fields_js.html11
-rw-r--r--webapp/django/contrib/admin/templates/admin/search_form.html18
-rw-r--r--webapp/django/contrib/admin/templates/admin/submit_line.html8
-rw-r--r--webapp/django/contrib/admin/templates/admin/template_validator.html31
-rw-r--r--webapp/django/contrib/admin/templates/registration/logged_out.html12
-rw-r--r--webapp/django/contrib/admin/templates/registration/password_change_done.html14
-rw-r--r--webapp/django/contrib/admin/templates/registration/password_change_form.html26
-rw-r--r--webapp/django/contrib/admin/templates/registration/password_reset_complete.html16
-rw-r--r--webapp/django/contrib/admin/templates/registration/password_reset_confirm.html32
-rw-r--r--webapp/django/contrib/admin/templates/registration/password_reset_done.html14
-rw-r--r--webapp/django/contrib/admin/templates/registration/password_reset_email.html15
-rw-r--r--webapp/django/contrib/admin/templates/registration/password_reset_form.html19
-rw-r--r--webapp/django/contrib/admin/templatetags/__init__.py0
-rw-r--r--webapp/django/contrib/admin/templatetags/admin_list.py312
-rw-r--r--webapp/django/contrib/admin/templatetags/admin_modify.py38
-rw-r--r--webapp/django/contrib/admin/templatetags/adminmedia.py14
-rw-r--r--webapp/django/contrib/admin/templatetags/log.py56
-rw-r--r--webapp/django/contrib/admin/util.py151
-rw-r--r--webapp/django/contrib/admin/validation.py298
-rw-r--r--webapp/django/contrib/admin/views/__init__.py0
-rw-r--r--webapp/django/contrib/admin/views/decorators.py114
-rw-r--r--webapp/django/contrib/admin/views/main.py233
-rw-r--r--webapp/django/contrib/admin/views/template.py77
-rw-r--r--webapp/django/contrib/admin/widgets.py238
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
new file mode 100644
index 0000000000..a967b9fd55
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/arrow-down.gif
Binary files differ
diff --git a/webapp/django/contrib/admin/media/img/admin/arrow-up.gif b/webapp/django/contrib/admin/media/img/admin/arrow-up.gif
new file mode 100644
index 0000000000..3fe4851399
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/arrow-up.gif
Binary files differ
diff --git a/webapp/django/contrib/admin/media/img/admin/changelist-bg.gif b/webapp/django/contrib/admin/media/img/admin/changelist-bg.gif
new file mode 100644
index 0000000000..7f4699470a
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/changelist-bg.gif
Binary files differ
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
new file mode 100644
index 0000000000..237971257f
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/changelist-bg_rtl.gif
Binary files differ
diff --git a/webapp/django/contrib/admin/media/img/admin/chooser-bg.gif b/webapp/django/contrib/admin/media/img/admin/chooser-bg.gif
new file mode 100644
index 0000000000..30e83c2518
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/chooser-bg.gif
Binary files differ
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
new file mode 100644
index 0000000000..5d104b6d98
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/chooser_stacked-bg.gif
Binary files differ
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
new file mode 100644
index 0000000000..0873281e51
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/default-bg-reverse.gif
Binary files differ
diff --git a/webapp/django/contrib/admin/media/img/admin/default-bg.gif b/webapp/django/contrib/admin/media/img/admin/default-bg.gif
new file mode 100644
index 0000000000..003aeca59f
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/default-bg.gif
Binary files differ
diff --git a/webapp/django/contrib/admin/media/img/admin/deleted-overlay.gif b/webapp/django/contrib/admin/media/img/admin/deleted-overlay.gif
new file mode 100644
index 0000000000..dc3828fe06
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/deleted-overlay.gif
Binary files differ
diff --git a/webapp/django/contrib/admin/media/img/admin/icon-no.gif b/webapp/django/contrib/admin/media/img/admin/icon-no.gif
new file mode 100644
index 0000000000..1b4ee58145
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/icon-no.gif
Binary files differ
diff --git a/webapp/django/contrib/admin/media/img/admin/icon-unknown.gif b/webapp/django/contrib/admin/media/img/admin/icon-unknown.gif
new file mode 100644
index 0000000000..cfd2b02ad9
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/icon-unknown.gif
Binary files differ
diff --git a/webapp/django/contrib/admin/media/img/admin/icon-yes.gif b/webapp/django/contrib/admin/media/img/admin/icon-yes.gif
new file mode 100644
index 0000000000..7399282740
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/icon-yes.gif
Binary files differ
diff --git a/webapp/django/contrib/admin/media/img/admin/icon_addlink.gif b/webapp/django/contrib/admin/media/img/admin/icon_addlink.gif
new file mode 100644
index 0000000000..ee70e1adba
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/icon_addlink.gif
Binary files differ
diff --git a/webapp/django/contrib/admin/media/img/admin/icon_alert.gif b/webapp/django/contrib/admin/media/img/admin/icon_alert.gif
new file mode 100644
index 0000000000..a1dde26254
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/icon_alert.gif
Binary files differ
diff --git a/webapp/django/contrib/admin/media/img/admin/icon_calendar.gif b/webapp/django/contrib/admin/media/img/admin/icon_calendar.gif
new file mode 100644
index 0000000000..7587b305a4
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/icon_calendar.gif
Binary files differ
diff --git a/webapp/django/contrib/admin/media/img/admin/icon_changelink.gif b/webapp/django/contrib/admin/media/img/admin/icon_changelink.gif
new file mode 100644
index 0000000000..e1b9afde65
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/icon_changelink.gif
Binary files differ
diff --git a/webapp/django/contrib/admin/media/img/admin/icon_clock.gif b/webapp/django/contrib/admin/media/img/admin/icon_clock.gif
new file mode 100644
index 0000000000..ff2d57e0a3
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/icon_clock.gif
Binary files differ
diff --git a/webapp/django/contrib/admin/media/img/admin/icon_deletelink.gif b/webapp/django/contrib/admin/media/img/admin/icon_deletelink.gif
new file mode 100644
index 0000000000..72523e3a3b
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/icon_deletelink.gif
Binary files differ
diff --git a/webapp/django/contrib/admin/media/img/admin/icon_error.gif b/webapp/django/contrib/admin/media/img/admin/icon_error.gif
new file mode 100644
index 0000000000..3730a00b26
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/icon_error.gif
Binary files differ
diff --git a/webapp/django/contrib/admin/media/img/admin/icon_searchbox.png b/webapp/django/contrib/admin/media/img/admin/icon_searchbox.png
new file mode 100644
index 0000000000..8ab579e526
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/icon_searchbox.png
Binary files differ
diff --git a/webapp/django/contrib/admin/media/img/admin/icon_success.gif b/webapp/django/contrib/admin/media/img/admin/icon_success.gif
new file mode 100644
index 0000000000..5cf90a15aa
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/icon_success.gif
Binary files differ
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
new file mode 100644
index 0000000000..95caf59a8d
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/inline-delete-8bit.png
Binary files differ
diff --git a/webapp/django/contrib/admin/media/img/admin/inline-delete.png b/webapp/django/contrib/admin/media/img/admin/inline-delete.png
new file mode 100644
index 0000000000..d59bcd2444
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/inline-delete.png
Binary files differ
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
new file mode 100644
index 0000000000..e087c8ead3
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/inline-restore-8bit.png
Binary files differ
diff --git a/webapp/django/contrib/admin/media/img/admin/inline-restore.png b/webapp/django/contrib/admin/media/img/admin/inline-restore.png
new file mode 100644
index 0000000000..efdd92ac39
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/inline-restore.png
Binary files differ
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
new file mode 100644
index 0000000000..32ac5b3498
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/inline-splitter-bg.gif
Binary files differ
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
new file mode 100644
index 0000000000..0a784fa769
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/nav-bg-grabber.gif
Binary files differ
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
new file mode 100644
index 0000000000..f11029f90f
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/nav-bg-reverse.gif
Binary files differ
diff --git a/webapp/django/contrib/admin/media/img/admin/nav-bg.gif b/webapp/django/contrib/admin/media/img/admin/nav-bg.gif
new file mode 100644
index 0000000000..f8402b809d
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/nav-bg.gif
Binary files differ
diff --git a/webapp/django/contrib/admin/media/img/admin/selector-add.gif b/webapp/django/contrib/admin/media/img/admin/selector-add.gif
new file mode 100644
index 0000000000..50132d1c43
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/selector-add.gif
Binary files differ
diff --git a/webapp/django/contrib/admin/media/img/admin/selector-addall.gif b/webapp/django/contrib/admin/media/img/admin/selector-addall.gif
new file mode 100644
index 0000000000..d6e7c639bb
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/selector-addall.gif
Binary files differ
diff --git a/webapp/django/contrib/admin/media/img/admin/selector-remove.gif b/webapp/django/contrib/admin/media/img/admin/selector-remove.gif
new file mode 100644
index 0000000000..2b9b0a2ac3
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/selector-remove.gif
Binary files differ
diff --git a/webapp/django/contrib/admin/media/img/admin/selector-removeall.gif b/webapp/django/contrib/admin/media/img/admin/selector-removeall.gif
new file mode 100644
index 0000000000..5a4421926d
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/selector-removeall.gif
Binary files differ
diff --git a/webapp/django/contrib/admin/media/img/admin/selector-search.gif b/webapp/django/contrib/admin/media/img/admin/selector-search.gif
new file mode 100644
index 0000000000..6d5f4c7492
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/selector-search.gif
Binary files differ
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
new file mode 100644
index 0000000000..7426169652
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/selector_stacked-add.gif
Binary files differ
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
new file mode 100644
index 0000000000..60412cee19
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/selector_stacked-remove.gif
Binary files differ
diff --git a/webapp/django/contrib/admin/media/img/admin/tool-left.gif b/webapp/django/contrib/admin/media/img/admin/tool-left.gif
new file mode 100644
index 0000000000..011490ff3a
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/tool-left.gif
Binary files differ
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
new file mode 100644
index 0000000000..937e07bb1a
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/tool-left_over.gif
Binary files differ
diff --git a/webapp/django/contrib/admin/media/img/admin/tool-right.gif b/webapp/django/contrib/admin/media/img/admin/tool-right.gif
new file mode 100644
index 0000000000..cdc140cc59
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/tool-right.gif
Binary files differ
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
new file mode 100644
index 0000000000..4db977e838
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/tool-right_over.gif
Binary files differ
diff --git a/webapp/django/contrib/admin/media/img/admin/tooltag-add.gif b/webapp/django/contrib/admin/media/img/admin/tooltag-add.gif
new file mode 100644
index 0000000000..8b53d49ae5
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/tooltag-add.gif
Binary files differ
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
new file mode 100644
index 0000000000..bfc52f10de
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/tooltag-add_over.gif
Binary files differ
diff --git a/webapp/django/contrib/admin/media/img/admin/tooltag-arrowright.gif b/webapp/django/contrib/admin/media/img/admin/tooltag-arrowright.gif
new file mode 100644
index 0000000000..cdaaae77ed
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/tooltag-arrowright.gif
Binary files differ
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
new file mode 100644
index 0000000000..7163189604
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/admin/tooltag-arrowright_over.gif
Binary files differ
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
new file mode 100644
index 0000000000..296b2e29c9
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/gis/move_vertex_off.png
Binary files differ
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
new file mode 100644
index 0000000000..21f4758d9c
--- /dev/null
+++ b/webapp/django/contrib/admin/media/img/gis/move_vertex_on.png
Binary files differ
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">&lsaquo;</a>
+ // <a href="#" class="link-next">&rsaquo;</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(/&lt;/g, '<');
+ text = text.replace(/&gt;/g, '>');
+ text = text.replace(/&quot;/g, '"');
+ text = text.replace(/&#39;/g, "'");
+ text = text.replace(/&amp;/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> &rsaquo; {% 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> &rsaquo;
+{% 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> &rsaquo;
+ <a href="../../">{{ opts.verbose_name_plural|capfirst|escape }}</a> &rsaquo;
+ <a href="../">{{ original|truncatewords:"18"|escape }}</a> &rsaquo;
+ {% 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 %} &rsaquo; {{ 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> &rsaquo;
+ <a href="../../">{{ app_label|capfirst|escape }}</a> &rsaquo;
+ <a href="../">{{ opts.verbose_name_plural|capfirst }}</a> &rsaquo;
+ {% 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> &rsaquo; <a href="../">{{ app_label|capfirst|escape }}</a> &rsaquo; {{ 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 }}">&lsaquo; {{ 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> &rsaquo;
+ <a href="../../../">{{ app_label|capfirst|escape }}</a> &rsaquo;
+ <a href="../../">{{ opts.verbose_name_plural|capfirst }}</a> &rsaquo;
+ <a href="../">{{ object|escape|truncatewords:"18" }}</a> &rsaquo;
+ {% 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>&nbsp;{% 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>&nbsp;</td>
+ {% endif %}
+
+ {% if model.perms.change %}
+ <td><a href="{{ model.admin_url }}" class="changelink">{% trans 'Change' %}</a></td>
+ {% else %}
+ <td>&nbsp;</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> &rsaquo; {{ 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>&nbsp;</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> &rsaquo; <a href="../../">{{ module_name }}</a> &rsaquo; <a href="../">{{ object|truncatewords:"18" }}</a> &rsaquo; {% 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 %}&nbsp;&nbsp;<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> &rsaquo; {% 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> &rsaquo; {% 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> &rsaquo; {% 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> &rsaquo; {% 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> &rsaquo; {% 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> &rsaquo; {% 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('&nbsp;')
+ # 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 = '?' + '&amp;'.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 '&nbsp;<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)