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