diff options
Diffstat (limited to 'tbc/static/admin/widgets.py')
-rw-r--r-- | tbc/static/admin/widgets.py | 391 |
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 = '?' + '&'.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 ' <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) |