/django/contrib/admin/widgets.py
Python | 296 lines | 266 code | 11 blank | 19 comment | 11 complexity | ae5532a23cfe1289edc1e2eff42eea85 MD5 | raw file
Possible License(s): BSD-3-Clause
- """
- Form Widget classes specific to the Django admin site.
- """
- import django.utils.copycompat as copy
- from django import forms
- from django.forms.widgets import RadioFieldRenderer
- from django.forms.util import flatatt
- from django.utils.html import escape
- 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
- from django.core.urlresolvers import reverse, NoReverseMatch
- 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 = (settings.ADMIN_MEDIA_PREFIX + "js/core.js",
- settings.ADMIN_MEDIA_PREFIX + "js/SelectBox.js",
- settings.ADMIN_MEDIA_PREFIX + "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'<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.DateInput):
- class Media:
- js = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js",
- settings.ADMIN_MEDIA_PREFIX + "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 = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js",
- settings.ADMIN_MEDIA_PREFIX + "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'<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.ClearableFileInput):
- template_with_initial = (u'<p class="file-upload">%s</p>'
- % forms.ClearableFileInput.template_with_initial)
- template_with_clear = (u'<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 isinstance(v, 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 <select> box.
- """
- def __init__(self, rel, attrs=None, using=None):
- self.rel = rel
- self.db = using
- super(ForeignKeyRawIdWidget, self).__init__(attrs)
- def render(self, name, value, attrs=None):
- if attrs is None:
- attrs = {}
- related_url = '../../../%s/%s/' % (self.rel.to._meta.app_label, self.rel.to._meta.object_name.lower())
- params = self.url_parameters()
- if params:
- url = u'?' + u'&'.join([u'%s=%s' % (k, v) for k, v in params.items()])
- else:
- url = u''
- if "class" not in attrs:
- 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(u'<a href="%s%s" class="related-lookup" id="lookup_id_%s" onclick="return showRelatedObjectLookupPopup(this);"> ' % \
- (related_url, url, name))
- output.append(u'<img src="%simg/admin/selector-search.gif" width="16" height="16" alt="%s" /></a>' % (settings.ADMIN_MEDIA_PREFIX, _('Lookup')))
- if value:
- output.append(self.label_for_value(value))
- return mark_safe(u''.join(output))
- def base_url_parameters(self):
- return url_params_from_lookup_dict(self.rel.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.to._default_manager.using(self.db).get(**{key: value})
- return ' <strong>%s</strong>' % escape(truncate_words(obj, 14))
- except (ValueError, self.rel.to.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 = {}
- 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())
- try:
- related_url = reverse('admin:%s_%s_add' % info, current_app=self.admin_site.name)
- except NoReverseMatch:
- info = (self.admin_site.root_path, rel_to._meta.app_label, rel_to._meta.object_name.lower())
- related_url = '%s%s/%s/add/' % info
- self.widget.choices = self.choices
- output = [self.widget.render(name, value, *args, **kwargs)]
- if self.can_add_related:
- # TODO: "id_" is hard-coded here. This should instead use the correct
- # API to determine the ID dynamically.
- output.append(u'<a href="%s" 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="%s"/></a>' % (settings.ADMIN_MEDIA_PREFIX, _('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)