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