web/lib/django/contrib/admin/widgets.py
changeset 0 0d40e90630ef
child 29 cc9b7e14412b
equal deleted inserted replaced
-1:000000000000 0:0d40e90630ef
       
     1 """
       
     2 Form Widget classes specific to the Django admin site.
       
     3 """
       
     4 
       
     5 import copy
       
     6 
       
     7 from django import forms
       
     8 from django.forms.widgets import RadioFieldRenderer
       
     9 from django.forms.util import flatatt
       
    10 from django.utils.html import escape
       
    11 from django.utils.text import truncate_words
       
    12 from django.utils.translation import ugettext as _
       
    13 from django.utils.safestring import mark_safe
       
    14 from django.utils.encoding import force_unicode
       
    15 from django.conf import settings
       
    16 from django.core.urlresolvers import reverse, NoReverseMatch
       
    17 
       
    18 class FilteredSelectMultiple(forms.SelectMultiple):
       
    19     """
       
    20     A SelectMultiple with a JavaScript filter interface.
       
    21 
       
    22     Note that the resulting JavaScript assumes that the jsi18n
       
    23     catalog has been loaded in the page
       
    24     """
       
    25     class Media:
       
    26         js = (settings.ADMIN_MEDIA_PREFIX + "js/core.js",
       
    27               settings.ADMIN_MEDIA_PREFIX + "js/SelectBox.js",
       
    28               settings.ADMIN_MEDIA_PREFIX + "js/SelectFilter2.js")
       
    29 
       
    30     def __init__(self, verbose_name, is_stacked, attrs=None, choices=()):
       
    31         self.verbose_name = verbose_name
       
    32         self.is_stacked = is_stacked
       
    33         super(FilteredSelectMultiple, self).__init__(attrs, choices)
       
    34 
       
    35     def render(self, name, value, attrs=None, choices=()):
       
    36         output = [super(FilteredSelectMultiple, self).render(name, value, attrs, choices)]
       
    37         output.append(u'<script type="text/javascript">addEvent(window, "load", function(e) {')
       
    38         # TODO: "id_" is hard-coded here. This should instead use the correct
       
    39         # API to determine the ID dynamically.
       
    40         output.append(u'SelectFilter.init("id_%s", "%s", %s, "%s"); });</script>\n' % \
       
    41             (name, self.verbose_name.replace('"', '\\"'), int(self.is_stacked), settings.ADMIN_MEDIA_PREFIX))
       
    42         return mark_safe(u''.join(output))
       
    43 
       
    44 class AdminDateWidget(forms.TextInput):
       
    45     class Media:
       
    46         js = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js",
       
    47               settings.ADMIN_MEDIA_PREFIX + "js/admin/DateTimeShortcuts.js")
       
    48 
       
    49     def __init__(self, attrs={}):
       
    50         super(AdminDateWidget, self).__init__(attrs={'class': 'vDateField', 'size': '10'})
       
    51 
       
    52 class AdminTimeWidget(forms.TextInput):
       
    53     class Media:
       
    54         js = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js",
       
    55               settings.ADMIN_MEDIA_PREFIX + "js/admin/DateTimeShortcuts.js")
       
    56 
       
    57     def __init__(self, attrs={}):
       
    58         super(AdminTimeWidget, self).__init__(attrs={'class': 'vTimeField', 'size': '8'})
       
    59 
       
    60 class AdminSplitDateTime(forms.SplitDateTimeWidget):
       
    61     """
       
    62     A SplitDateTime Widget that has some admin-specific styling.
       
    63     """
       
    64     def __init__(self, attrs=None):
       
    65         widgets = [AdminDateWidget, AdminTimeWidget]
       
    66         # Note that we're calling MultiWidget, not SplitDateTimeWidget, because
       
    67         # we want to define widgets.
       
    68         forms.MultiWidget.__init__(self, widgets, attrs)
       
    69 
       
    70     def format_output(self, rendered_widgets):
       
    71         return mark_safe(u'<p class="datetime">%s %s<br />%s %s</p>' % \
       
    72             (_('Date:'), rendered_widgets[0], _('Time:'), rendered_widgets[1]))
       
    73 
       
    74 class AdminRadioFieldRenderer(RadioFieldRenderer):
       
    75     def render(self):
       
    76         """Outputs a <ul> for this set of radio fields."""
       
    77         return mark_safe(u'<ul%s>\n%s\n</ul>' % (
       
    78             flatatt(self.attrs),
       
    79             u'\n'.join([u'<li>%s</li>' % force_unicode(w) for w in self]))
       
    80         )
       
    81 
       
    82 class AdminRadioSelect(forms.RadioSelect):
       
    83     renderer = AdminRadioFieldRenderer
       
    84 
       
    85 class AdminFileWidget(forms.FileInput):
       
    86     """
       
    87     A FileField Widget that shows its current value if it has one.
       
    88     """
       
    89     def __init__(self, attrs={}):
       
    90         super(AdminFileWidget, self).__init__(attrs)
       
    91 
       
    92     def render(self, name, value, attrs=None):
       
    93         output = []
       
    94         if value and hasattr(value, "url"):
       
    95             output.append('%s <a target="_blank" href="%s">%s</a> <br />%s ' % \
       
    96                 (_('Currently:'), value.url, value, _('Change:')))
       
    97         output.append(super(AdminFileWidget, self).render(name, value, attrs))
       
    98         return mark_safe(u''.join(output))
       
    99 
       
   100 class ForeignKeyRawIdWidget(forms.TextInput):
       
   101     """
       
   102     A Widget for displaying ForeignKeys in the "raw_id" interface rather than
       
   103     in a <select> box.
       
   104     """
       
   105     def __init__(self, rel, attrs=None):
       
   106         self.rel = rel
       
   107         super(ForeignKeyRawIdWidget, self).__init__(attrs)
       
   108 
       
   109     def render(self, name, value, attrs=None):
       
   110         if attrs is None:
       
   111             attrs = {}
       
   112         related_url = '../../../%s/%s/' % (self.rel.to._meta.app_label, self.rel.to._meta.object_name.lower())
       
   113         params = self.url_parameters()
       
   114         if params:
       
   115             url = '?' + '&amp;'.join(['%s=%s' % (k, v) for k, v in params.items()])
       
   116         else:
       
   117             url = ''
       
   118         if not attrs.has_key('class'):
       
   119             attrs['class'] = 'vForeignKeyRawIdAdminField' # The JavaScript looks for this hook.
       
   120         output = [super(ForeignKeyRawIdWidget, self).render(name, value, attrs)]
       
   121         # TODO: "id_" is hard-coded here. This should instead use the correct
       
   122         # API to determine the ID dynamically.
       
   123         output.append('<a href="%s%s" class="related-lookup" id="lookup_id_%s" onclick="return showRelatedObjectLookupPopup(this);"> ' % \
       
   124             (related_url, url, name))
       
   125         output.append('<img src="%simg/admin/selector-search.gif" width="16" height="16" alt="%s" /></a>' % (settings.ADMIN_MEDIA_PREFIX, _('Lookup')))
       
   126         if value:
       
   127             output.append(self.label_for_value(value))
       
   128         return mark_safe(u''.join(output))
       
   129 
       
   130     def base_url_parameters(self):
       
   131         params = {}
       
   132         if self.rel.limit_choices_to:
       
   133             items = []
       
   134             for k, v in self.rel.limit_choices_to.items():
       
   135                 if isinstance(v, list):
       
   136                     v = ','.join([str(x) for x in v])
       
   137                 else:
       
   138                     v = str(v)
       
   139                 items.append((k, v))
       
   140             params.update(dict(items))
       
   141         return params
       
   142 
       
   143     def url_parameters(self):
       
   144         from django.contrib.admin.views.main import TO_FIELD_VAR
       
   145         params = self.base_url_parameters()
       
   146         params.update({TO_FIELD_VAR: self.rel.get_related_field().name})
       
   147         return params
       
   148 
       
   149     def label_for_value(self, value):
       
   150         key = self.rel.get_related_field().name
       
   151         obj = self.rel.to._default_manager.get(**{key: value})
       
   152         return '&nbsp;<strong>%s</strong>' % escape(truncate_words(obj, 14))
       
   153 
       
   154 class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):
       
   155     """
       
   156     A Widget for displaying ManyToMany ids in the "raw_id" interface rather than
       
   157     in a <select multiple> box.
       
   158     """
       
   159     def __init__(self, rel, attrs=None):
       
   160         super(ManyToManyRawIdWidget, self).__init__(rel, attrs)
       
   161 
       
   162     def render(self, name, value, attrs=None):
       
   163         attrs['class'] = 'vManyToManyRawIdAdminField'
       
   164         if value:
       
   165             value = ','.join([str(v) for v in value])
       
   166         else:
       
   167             value = ''
       
   168         return super(ManyToManyRawIdWidget, self).render(name, value, attrs)
       
   169 
       
   170     def url_parameters(self):
       
   171         return self.base_url_parameters()
       
   172 
       
   173     def label_for_value(self, value):
       
   174         return ''
       
   175 
       
   176     def value_from_datadict(self, data, files, name):
       
   177         value = data.get(name, None)
       
   178         if value and ',' in value:
       
   179             return data[name].split(',')
       
   180         if value:
       
   181             return [value]
       
   182         return None
       
   183 
       
   184     def _has_changed(self, initial, data):
       
   185         if initial is None:
       
   186             initial = []
       
   187         if data is None:
       
   188             data = []
       
   189         if len(initial) != len(data):
       
   190             return True
       
   191         for pk1, pk2 in zip(initial, data):
       
   192             if force_unicode(pk1) != force_unicode(pk2):
       
   193                 return True
       
   194         return False
       
   195 
       
   196 class RelatedFieldWidgetWrapper(forms.Widget):
       
   197     """
       
   198     This class is a wrapper to a given widget to add the add icon for the
       
   199     admin interface.
       
   200     """
       
   201     def __init__(self, widget, rel, admin_site):
       
   202         self.is_hidden = widget.is_hidden
       
   203         self.needs_multipart_form = widget.needs_multipart_form
       
   204         self.attrs = widget.attrs
       
   205         self.choices = widget.choices
       
   206         self.widget = widget
       
   207         self.rel = rel
       
   208         # so we can check if the related object is registered with this AdminSite
       
   209         self.admin_site = admin_site
       
   210 
       
   211     def __deepcopy__(self, memo):
       
   212         obj = copy.copy(self)
       
   213         obj.widget = copy.deepcopy(self.widget, memo)
       
   214         obj.attrs = self.widget.attrs
       
   215         memo[id(self)] = obj
       
   216         return obj
       
   217 
       
   218     def _media(self):
       
   219         return self.widget.media
       
   220     media = property(_media)
       
   221 
       
   222     def render(self, name, value, *args, **kwargs):
       
   223         rel_to = self.rel.to
       
   224         info = (rel_to._meta.app_label, rel_to._meta.object_name.lower())
       
   225         try:
       
   226             related_url = reverse('admin:%s_%s_add' % info, current_app=self.admin_site.name)
       
   227         except NoReverseMatch:
       
   228             info = (self.admin_site.root_path, rel_to._meta.app_label, rel_to._meta.object_name.lower())
       
   229             related_url = '%s%s/%s/add/' % info
       
   230         self.widget.choices = self.choices
       
   231         output = [self.widget.render(name, value, *args, **kwargs)]
       
   232         if rel_to in self.admin_site._registry: # If the related object has an admin interface:
       
   233             # TODO: "id_" is hard-coded here. This should instead use the correct
       
   234             # API to determine the ID dynamically.
       
   235             output.append(u'<a href="%s" class="add-another" id="add_id_%s" onclick="return showAddAnotherPopup(this);"> ' % \
       
   236                 (related_url, name))
       
   237             output.append(u'<img src="%simg/admin/icon_addlink.gif" width="10" height="10" alt="%s"/></a>' % (settings.ADMIN_MEDIA_PREFIX, _('Add Another')))
       
   238         return mark_safe(u''.join(output))
       
   239 
       
   240     def build_attrs(self, extra_attrs=None, **kwargs):
       
   241         "Helper function for building an attribute dictionary."
       
   242         self.attrs = self.widget.build_attrs(extra_attrs=None, **kwargs)
       
   243         return self.attrs
       
   244 
       
   245     def value_from_datadict(self, data, files, name):
       
   246         return self.widget.value_from_datadict(data, files, name)
       
   247 
       
   248     def _has_changed(self, initial, data):
       
   249         return self.widget._has_changed(initial, data)
       
   250 
       
   251     def id_for_label(self, id_):
       
   252         return self.widget.id_for_label(id_)
       
   253 
       
   254 class AdminTextareaWidget(forms.Textarea):
       
   255     def __init__(self, attrs=None):
       
   256         final_attrs = {'class': 'vLargeTextField'}
       
   257         if attrs is not None:
       
   258             final_attrs.update(attrs)
       
   259         super(AdminTextareaWidget, self).__init__(attrs=final_attrs)
       
   260 
       
   261 class AdminTextInputWidget(forms.TextInput):
       
   262     def __init__(self, attrs=None):
       
   263         final_attrs = {'class': 'vTextField'}
       
   264         if attrs is not None:
       
   265             final_attrs.update(attrs)
       
   266         super(AdminTextInputWidget, self).__init__(attrs=final_attrs)
       
   267 
       
   268 class AdminURLFieldWidget(forms.TextInput):
       
   269     def __init__(self, attrs=None):
       
   270         final_attrs = {'class': 'vURLField'}
       
   271         if attrs is not None:
       
   272             final_attrs.update(attrs)
       
   273         super(AdminURLFieldWidget, self).__init__(attrs=final_attrs)
       
   274 
       
   275 class AdminIntegerFieldWidget(forms.TextInput):
       
   276     def __init__(self, attrs=None):
       
   277         final_attrs = {'class': 'vIntegerField'}
       
   278         if attrs is not None:
       
   279             final_attrs.update(attrs)
       
   280         super(AdminIntegerFieldWidget, self).__init__(attrs=final_attrs)
       
   281 
       
   282 class AdminCommaSeparatedIntegerFieldWidget(forms.TextInput):
       
   283     def __init__(self, attrs=None):
       
   284         final_attrs = {'class': 'vCommaSeparatedIntegerField'}
       
   285         if attrs is not None:
       
   286             final_attrs.update(attrs)
       
   287         super(AdminCommaSeparatedIntegerFieldWidget, self).__init__(attrs=final_attrs)