summaryrefslogtreecommitdiff
path: root/tbc/static/admin/widgets.py
diff options
context:
space:
mode:
Diffstat (limited to 'tbc/static/admin/widgets.py')
-rw-r--r--tbc/static/admin/widgets.py391
1 files changed, 391 insertions, 0 deletions
diff --git a/tbc/static/admin/widgets.py b/tbc/static/admin/widgets.py
new file mode 100644
index 0000000..491c788
--- /dev/null
+++ b/tbc/static/admin/widgets.py
@@ -0,0 +1,391 @@
+"""
+Form Widget classes specific to the Django admin site.
+"""
+from __future__ import unicode_literals
+
+import copy
+
+from django import forms
+from django.contrib.admin.templatetags.admin_static import static
+from django.core.urlresolvers import reverse
+from django.db.models.deletion import CASCADE
+from django.forms.utils import flatatt
+from django.forms.widgets import RadioFieldRenderer
+from django.template.loader import render_to_string
+from django.utils import six
+from django.utils.encoding import force_text
+from django.utils.html import (
+ escape, escapejs, format_html, format_html_join, smart_urlquote,
+)
+from django.utils.safestring import mark_safe
+from django.utils.text import Truncator
+from django.utils.translation import ugettext as _
+
+
+class FilteredSelectMultiple(forms.SelectMultiple):
+ """
+ A SelectMultiple with a JavaScript filter interface.
+
+ Note that the resulting JavaScript assumes that the jsi18n
+ catalog has been loaded in the page
+ """
+ @property
+ def media(self):
+ js = ["core.js", "SelectBox.js", "SelectFilter2.js"]
+ return forms.Media(js=[static("admin/js/%s" % path) for path in js])
+
+ 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=()):
+ if attrs is None:
+ attrs = {}
+ attrs['class'] = 'selectfilter'
+ if self.is_stacked:
+ attrs['class'] += 'stacked'
+ output = [super(FilteredSelectMultiple, self).render(name, value, attrs, choices)]
+ output.append('<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('SelectFilter.init("id_%s", "%s", %s); });</script>\n'
+ % (name, escapejs(self.verbose_name), int(self.is_stacked)))
+ return mark_safe(''.join(output))
+
+
+class AdminDateWidget(forms.DateInput):
+ @property
+ def media(self):
+ js = ["calendar.js", "admin/DateTimeShortcuts.js"]
+ return forms.Media(js=[static("admin/js/%s" % path) for path in js])
+
+ def __init__(self, attrs=None, format=None):
+ final_attrs = {'class': 'vDateField', 'size': '10'}
+ if attrs is not None:
+ final_attrs.update(attrs)
+ super(AdminDateWidget, self).__init__(attrs=final_attrs, format=format)
+
+
+class AdminTimeWidget(forms.TimeInput):
+ @property
+ def media(self):
+ js = ["calendar.js", "admin/DateTimeShortcuts.js"]
+ return forms.Media(js=[static("admin/js/%s" % path) for path in js])
+
+ def __init__(self, attrs=None, format=None):
+ final_attrs = {'class': 'vTimeField', 'size': '8'}
+ if attrs is not None:
+ final_attrs.update(attrs)
+ super(AdminTimeWidget, self).__init__(attrs=final_attrs, format=format)
+
+
+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 format_html('<p class="datetime">{} {}<br />{} {}</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 format_html('<ul{}>\n{}\n</ul>',
+ flatatt(self.attrs),
+ format_html_join('\n', '<li>{}</li>',
+ ((force_text(w),) for w in self)))
+
+
+class AdminRadioSelect(forms.RadioSelect):
+ renderer = AdminRadioFieldRenderer
+
+
+class AdminFileWidget(forms.ClearableFileInput):
+ template_with_initial = ('<p class="file-upload">%s</p>'
+ % forms.ClearableFileInput.template_with_initial)
+ template_with_clear = ('<span class="clearable-file-input">%s</span>'
+ % forms.ClearableFileInput.template_with_clear)
+
+
+def url_params_from_lookup_dict(lookups):
+ """
+ Converts the type of lookups specified in a ForeignKey limit_choices_to
+ attribute to a dictionary of query parameters
+ """
+ params = {}
+ if lookups and hasattr(lookups, 'items'):
+ items = []
+ for k, v in lookups.items():
+ if callable(v):
+ v = v()
+ if isinstance(v, (tuple, list)):
+ v = ','.join(str(x) for x in v)
+ elif isinstance(v, bool):
+ # See django.db.fields.BooleanField.get_prep_lookup
+ v = ('0', '1')[v]
+ else:
+ v = six.text_type(v)
+ items.append((k, v))
+ params.update(dict(items))
+ return params
+
+
+class ForeignKeyRawIdWidget(forms.TextInput):
+ """
+ A Widget for displaying ForeignKeys in the "raw_id" interface rather than
+ in a <select> box.
+ """
+ def __init__(self, rel, admin_site, attrs=None, using=None):
+ self.rel = rel
+ self.admin_site = admin_site
+ self.db = using
+ super(ForeignKeyRawIdWidget, self).__init__(attrs)
+
+ def render(self, name, value, attrs=None):
+ rel_to = self.rel.model
+ if attrs is None:
+ attrs = {}
+ extra = []
+ if rel_to in self.admin_site._registry:
+ # The related object is registered with the same AdminSite
+ related_url = reverse(
+ 'admin:%s_%s_changelist' % (
+ rel_to._meta.app_label,
+ rel_to._meta.model_name,
+ ),
+ current_app=self.admin_site.name,
+ )
+
+ params = self.url_parameters()
+ if params:
+ url = '?' + '&amp;'.join('%s=%s' % (k, v) for k, v in params.items())
+ else:
+ url = ''
+ if "class" not in attrs:
+ attrs['class'] = 'vForeignKeyRawIdAdminField' # The JavaScript code looks for this hook.
+ # TODO: "lookup_id_" is hard-coded here. This should instead use
+ # the correct API to determine the ID dynamically.
+ extra.append('<a href="%s%s" class="related-lookup" id="lookup_id_%s" title="%s"></a>' %
+ (related_url, url, name, _('Lookup')))
+ output = [super(ForeignKeyRawIdWidget, self).render(name, value, attrs)] + extra
+ if value:
+ output.append(self.label_for_value(value))
+ return mark_safe(''.join(output))
+
+ def base_url_parameters(self):
+ limit_choices_to = self.rel.limit_choices_to
+ if callable(limit_choices_to):
+ limit_choices_to = limit_choices_to()
+ return url_params_from_lookup_dict(limit_choices_to)
+
+ def url_parameters(self):
+ from django.contrib.admin.views.main import TO_FIELD_VAR
+ params = self.base_url_parameters()
+ params.update({TO_FIELD_VAR: self.rel.get_related_field().name})
+ return params
+
+ def label_for_value(self, value):
+ key = self.rel.get_related_field().name
+ try:
+ obj = self.rel.model._default_manager.using(self.db).get(**{key: value})
+ return '&nbsp;<strong>%s</strong>' % escape(Truncator(obj).words(14, truncate='...'))
+ except (ValueError, self.rel.model.DoesNotExist):
+ return ''
+
+
+class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):
+ """
+ A Widget for displaying ManyToMany ids in the "raw_id" interface rather than
+ in a <select multiple> box.
+ """
+ def render(self, name, value, attrs=None):
+ if attrs is None:
+ attrs = {}
+ if self.rel.model in self.admin_site._registry:
+ # The related object is registered with the same AdminSite
+ attrs['class'] = 'vManyToManyRawIdAdminField'
+ if value:
+ value = ','.join(force_text(v) for v in value)
+ else:
+ value = ''
+ return super(ManyToManyRawIdWidget, self).render(name, value, attrs)
+
+ def url_parameters(self):
+ return self.base_url_parameters()
+
+ def label_for_value(self, value):
+ return ''
+
+ def value_from_datadict(self, data, files, name):
+ value = data.get(name)
+ if value:
+ return value.split(',')
+
+
+class RelatedFieldWidgetWrapper(forms.Widget):
+ """
+ This class is a wrapper to a given widget to add the add icon for the
+ admin interface.
+ """
+ template = 'admin/related_widget_wrapper.html'
+
+ def __init__(self, widget, rel, admin_site, can_add_related=None,
+ can_change_related=False, can_delete_related=False):
+ self.needs_multipart_form = widget.needs_multipart_form
+ self.attrs = widget.attrs
+ self.choices = widget.choices
+ self.widget = widget
+ self.rel = rel
+ # Backwards compatible check for whether a user can add related
+ # objects.
+ if can_add_related is None:
+ can_add_related = rel.model in admin_site._registry
+ self.can_add_related = can_add_related
+ # XXX: The UX does not support multiple selected values.
+ multiple = getattr(widget, 'allow_multiple_selected', False)
+ self.can_change_related = not multiple and can_change_related
+ # XXX: The deletion UX can be confusing when dealing with cascading deletion.
+ cascade = getattr(rel, 'on_delete', None) is CASCADE
+ self.can_delete_related = not multiple and not cascade and can_delete_related
+ # 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
+
+ @property
+ def is_hidden(self):
+ return self.widget.is_hidden
+
+ @property
+ def media(self):
+ return self.widget.media
+
+ def get_related_url(self, info, action, *args):
+ return reverse("admin:%s_%s_%s" % (info + (action,)),
+ current_app=self.admin_site.name, args=args)
+
+ def render(self, name, value, *args, **kwargs):
+ from django.contrib.admin.views.main import IS_POPUP_VAR, TO_FIELD_VAR
+ rel_opts = self.rel.model._meta
+ info = (rel_opts.app_label, rel_opts.model_name)
+ self.widget.choices = self.choices
+ url_params = '&'.join("%s=%s" % param for param in [
+ (TO_FIELD_VAR, self.rel.get_related_field().name),
+ (IS_POPUP_VAR, 1),
+ ])
+ context = {
+ 'widget': self.widget.render(name, value, *args, **kwargs),
+ 'name': name,
+ 'url_params': url_params,
+ 'model': rel_opts.verbose_name,
+ }
+ if self.can_change_related:
+ change_related_template_url = self.get_related_url(info, 'change', '__fk__')
+ context.update(
+ can_change_related=True,
+ change_related_template_url=change_related_template_url,
+ )
+ if self.can_add_related:
+ add_related_url = self.get_related_url(info, 'add')
+ context.update(
+ can_add_related=True,
+ add_related_url=add_related_url,
+ )
+ if self.can_delete_related:
+ delete_related_template_url = self.get_related_url(info, 'delete', '__fk__')
+ context.update(
+ can_delete_related=True,
+ delete_related_template_url=delete_related_template_url,
+ )
+ return mark_safe(render_to_string(self.template, context))
+
+ 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 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 AdminEmailInputWidget(forms.EmailInput):
+ def __init__(self, attrs=None):
+ final_attrs = {'class': 'vTextField'}
+ if attrs is not None:
+ final_attrs.update(attrs)
+ super(AdminEmailInputWidget, self).__init__(attrs=final_attrs)
+
+
+class AdminURLFieldWidget(forms.URLInput):
+ 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)
+
+ def render(self, name, value, attrs=None):
+ html = super(AdminURLFieldWidget, self).render(name, value, attrs)
+ if value:
+ value = force_text(self._format_value(value))
+ final_attrs = {'href': smart_urlquote(value)}
+ html = format_html(
+ '<p class="url">{} <a{}>{}</a><br />{} {}</p>',
+ _('Currently:'), flatatt(final_attrs), value,
+ _('Change:'), html
+ )
+ return html
+
+
+class AdminIntegerFieldWidget(forms.TextInput):
+ class_name = 'vIntegerField'
+
+ def __init__(self, attrs=None):
+ final_attrs = {'class': self.class_name}
+ if attrs is not None:
+ final_attrs.update(attrs)
+ super(AdminIntegerFieldWidget, self).__init__(attrs=final_attrs)
+
+
+class AdminBigIntegerFieldWidget(AdminIntegerFieldWidget):
+ class_name = 'vBigIntegerField'
+
+
+class AdminCommaSeparatedIntegerFieldWidget(forms.TextInput):
+ def __init__(self, attrs=None):
+ final_attrs = {'class': 'vCommaSeparatedIntegerField'}
+ if attrs is not None:
+ final_attrs.update(attrs)
+ super(AdminCommaSeparatedIntegerFieldWidget, self).__init__(attrs=final_attrs)