""" Form Widget classes specific to the Django admin site. """ import copy from django import forms from django.core.urlresolvers import reverse from django.forms.widgets import RadioFieldRenderer from django.forms.util import flatatt from django.templatetags.static import static from django.utils.html import escape from django.utils.text import Truncator from django.utils.translation import ugettext as _ from django.utils.safestring import mark_safe from django.utils.encoding import force_unicode 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 """ class Media: js = ["admin/js/%s" % path for path in ["core.js", "SelectBox.js", "SelectFilter2.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(u'\n' % (name, self.verbose_name.replace('"', '\\"'), int(self.is_stacked), static('admin/'))) return mark_safe(u''.join(output)) class AdminDateWidget(forms.DateInput): class Media: js = ["admin/js/calendar.js", "admin/js/admin/DateTimeShortcuts.js"] def __init__(self, attrs={}, format=None): super(AdminDateWidget, self).__init__(attrs={'class': 'vDateField', 'size': '10'}, format=format) class AdminTimeWidget(forms.TimeInput): class Media: js = ["admin/js/calendar.js", "admin/js/admin/DateTimeShortcuts.js"] def __init__(self, attrs={}, format=None): super(AdminTimeWidget, self).__init__(attrs={'class': 'vTimeField', 'size': '8'}, 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 mark_safe(u'

%s %s
%s %s

' % \ (_('Date:'), rendered_widgets[0], _('Time:'), rendered_widgets[1])) class AdminRadioFieldRenderer(RadioFieldRenderer): def render(self): """Outputs a ' % ( flatatt(self.attrs), u'\n'.join([u'
  • %s
  • ' % force_unicode(w) for w in self])) ) class AdminRadioSelect(forms.RadioSelect): renderer = AdminRadioFieldRenderer class AdminFileWidget(forms.ClearableFileInput): template_with_initial = (u'

    %s

    ' % forms.ClearableFileInput.template_with_initial) template_with_clear = (u'%s' % 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 isinstance(v, (tuple, list)): v = u','.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 = unicode(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 box. """ def render(self, name, value, attrs=None): if attrs is None: attrs = {} if self.rel.to in self.admin_site._registry: # The related object is registered with the same AdminSite attrs['class'] = 'vManyToManyRawIdAdminField' if value: value = ','.join([force_unicode(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(',') 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, can_add_related=None): 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 # Backwards compatible check for whether a user can add related # objects. if can_add_related is None: can_add_related = rel.to in admin_site._registry self.can_add_related = can_add_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 def _media(self): return self.widget.media media = property(_media) def render(self, name, value, *args, **kwargs): rel_to = self.rel.to info = (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 self.can_add_related: related_url = reverse('admin:%s_%s_add' % info, current_app=self.admin_site.name) # TODO: "add_id_" is hard-coded here. This should instead use the # correct API to determine the ID dynamically. output.append(u' ' % (related_url, name)) output.append(u'%s' % (static('admin/img/icon_addlink.gif'), _('Add Another'))) 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) 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)