Source

django / django / contrib / admin / widgets.py

The default branch has multiple heads

Full commit
bro...@bcc190cf-… 1bd979d 



ja...@bcc190cf-c… fcc98eb 
bro...@bcc190cf-… 1bd979d 
ja...@bcc190cf-c… 04b4e93 


tyrion 060c308 
luke...@bcc190cf… cc1e8b6 
adr...@bcc190cf-… f66bc67 
bro...@bcc190cf-… 1bd979d 



ja...@bcc190cf-c… 4d57074 
bro...@bcc190cf-… 1bd979d 




bro...@bcc190cf-… 750b869 

bro...@bcc190cf-… 1bd979d 
bro...@bcc190cf-… 750b869 




bro...@bcc190cf-… 1bd979d 





jbr...@bcc190cf-… 2190550 


bro...@bcc190cf-… 1bd979d 







jez...@bcc190cf-… 1482bba 
bro...@bcc190cf-… 1bd979d 
bro...@bcc190cf-… dea8289 
bro...@bcc190cf-… 1bd979d 
bro...@bcc190cf-… dea8289 
jez...@bcc190cf-… 9fe668e 

bro...@bcc190cf-… 1bd979d 
jez...@bcc190cf-… 9fe668e 
bro...@bcc190cf-… 1bd979d 
bro...@bcc190cf-… dea8289 
bro...@bcc190cf-… 1bd979d 

jez...@bcc190cf-… 9fe668e 

bro...@bcc190cf-… dea8289 
bro...@bcc190cf-… 1bd979d 
























jez...@bcc190cf-… 8ae7036 




bro...@bcc190cf-… dea8289 
bro...@bcc190cf-… 1bd979d 





russ...@bcc190cf… 0d9930b 
bro...@bcc190cf-… 1bd979d 
russ...@bcc190cf… 0d9930b 
bro...@bcc190cf-… 1bd979d 


mtre...@bcc190cf… 24a2038 

bro...@bcc190cf-… 1bd979d 
bro...@bcc190cf-… 96ce9b7 




Al...@bcc190cf-c… baa8dc0 
adr...@bcc190cf-… d31c559 
bro...@bcc190cf-… 1bd979d 




ja...@bcc190cf-c… dd0b907 
bro...@bcc190cf-… 1bd979d 


russ...@bcc190cf… f7b4019 
bro...@bcc190cf-… 96ce9b7 

kmtr...@bcc190cf… d57e98f 
bro...@bcc190cf-… 5238da6 


uber...@bcc190cf… 00f6897 
bro...@bcc190cf-… 5238da6 

uber...@bcc190cf… 00f6897 
bro...@bcc190cf-… 5238da6 
russ...@bcc190cf… f7b4019 

bro...@bcc190cf-… 96ce9b7 




russ...@bcc190cf… f7b4019 
bro...@bcc190cf-… 1bd979d 
bro...@bcc190cf-… dea8289 
kmtr...@bcc190cf… acf502b 

jbr...@bcc190cf-… a002290 

kmtr...@bcc190cf… acf502b 
bro...@bcc190cf-… dea8289 
tyrion 060c308 
tyrion dfe9ce5 





tyrion 060c308 
tyrion 6d038d6 
tyrion dfe9ce5 
tyrion 060c308 






tyrion dfe9ce5 



tyrion 060c308 





Ian Struble fb6fad2 
tyrion 060c308 










tyrion dfe9ce5 

tyrion 060c308 
tyrion dfe9ce5 
tyrion 060c308 

tyrion dfe9ce5 
tyrion 060c308 
tyrion dfe9ce5 


bro...@bcc190cf-… 1bd979d 





jez...@bcc190cf-… 03265fe 

bro...@bcc190cf-… 1bd979d 

jbr...@bcc190cf-… a002290 
bro...@bcc190cf-… 1bd979d 


russ...@bcc190cf… f7b4019 
bro...@bcc190cf-… 96ce9b7 

russ...@bcc190cf… f7b4019 
bro...@bcc190cf-… 1bd979d 



Honz...@bcc190cf… 0b5a787 
bro...@bcc190cf-… 1bd979d 
Honz...@bcc190cf… 0b5a787 
bro...@bcc190cf-… dea8289 
bro...@bcc190cf-… 1bd979d 











tyrion 060c308 
tyrion dfe9ce5 
tyrion 060c308 












tyrion dfe9ce5 








tyrion 060c308 







tyrion dfe9ce5 

bro...@bcc190cf-… 1bd979d 




mtre...@bcc190cf… 7ccbadd 
bro...@bcc190cf-… 1bd979d 





mtre...@bcc190cf… 7ccbadd 




bro...@bcc190cf-… 1bd979d 









bro...@bcc190cf-… 750b869 



bro...@bcc190cf-… 1bd979d 

ja...@bcc190cf-c… 4d57074 

russ...@bcc190cf… f7b4019 
ja...@bcc190cf-c… 4d57074 
russ...@bcc190cf… e761e9e 

bro...@bcc190cf-… 1bd979d 

mtre...@bcc190cf… 7ccbadd 
bro...@bcc190cf-… 1bd979d 

ja...@bcc190cf-c… 4d57074 
bro...@bcc190cf-… 1bd979d 
ja...@bcc190cf-c… dd0b907 
bro...@bcc190cf-… 1bd979d 














bro...@bcc190cf-… ff2d0ff 

bro...@bcc190cf-… 6915a3b 



bro...@bcc190cf-… 00360d3 
bro...@bcc190cf-… ff2d0ff 

bro...@bcc190cf-… 6915a3b 




bro...@bcc190cf-… ff2d0ff 

bro...@bcc190cf-… 6915a3b 




bro...@bcc190cf-… ff2d0ff 

bro...@bcc190cf-… 6915a3b 




ja...@bcc190cf-c… 25728d8 





"""
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 import simplejson
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)


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 = '?' + '&amp;'.join(['%s=%s' % (k, v) for k, v in params.items()])
        else:
            url = ''
        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('<a href="%s%s" class="related-lookup" id="lookup_id_%s" onclick="return showRelatedObjectLookupPopup(this);"> ' % \
            (related_url, url, name))
        output.append('<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):
        params = {}
        if self.rel.limit_choices_to and hasattr(self.rel.limit_choices_to, 'items'):
            items = []
            for k, v in self.rel.limit_choices_to.items():
                if isinstance(v, list):
                    v = ','.join([str(x) for x in v])
                else:
                    v = str(v)
                items.append((k, v))
            params.update(dict(items))
        return params

    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 '&nbsp;<strong>%s</strong>' % escape(truncate_words(obj, 14))
        except (ValueError, self.rel.to.DoesNotExist):
            return ''

class AutocompleteWidget(forms.Widget):
    
    class Media:
        js = (
            settings.ADMIN_MEDIA_PREFIX + 'js/admin/autocomplete.js',
        )

    def __init__(self, settings, attrs=None, using=None, **js_options):
        self.settings = settings
        self.db = using
        self.js_options = dict(
            source = settings.get('source'),
            multiple = settings.get('multiple', False),
            force_selection = settings.get('force_selection', True),
        )
        self.js_options.update(js_options)
        super(AutocompleteWidget, self).__init__(attrs)

    def get_autocomplete_url(self, name):
        return '../autocomplete/%s/' % name

    def render(self, name, value, attrs=None, hattrs=None, initial_objects=u''):
        if value is None:
            value = ''
        hidden_id = 'id_hidden_%s' % name
        hidden_attrs = self.build_attrs(type='hidden', name=name, value=value, id=hidden_id)
        normal_attrs = self.build_attrs(attrs, type='text')
        normal_attrs['value'] = self.label_for_value(value)
        if not self.js_options.get('source'):
            self.js_options['source'] = self.get_autocomplete_url(name)
        options = simplejson.dumps(self.js_options)
        return mark_safe(u''.join((
            u'<input%s />\n' % flatatt(hidden_attrs),
            u'<input%s />\n' % flatatt(normal_attrs),
            initial_objects,
            u'<script type="text/javascript">',
            u'django.jQuery("#id_%s").djangoautocomplete(%s);' % (name, options),
            u'</script>\n',
        )))

    def label_for_value(self, value):
        qs, key, value_fmt = [self.settings[k] for k in ('queryset','id','value')]
        try:
            obj = qs.get(**{key: value})
            return value_fmt(obj)
        # XXX this shouldn't happen.
        except qs.model.DoesNotExist:
            return value


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 MultipleAutocompleteWidget(AutocompleteWidget):

    def __init__(self, settings, attrs=None, using=None, **js_options):
        js_options['multiple'] = True
        super(MultipleAutocompleteWidget, self).__init__(settings, attrs,
            using, **js_options)

    def render(self, name, value, attrs=None, hattrs=None):
        if value:
            initial_objects = self.initial_objects(value)
            value = ','.join([str(v) for v in value])
        else:
            initial_objects = u''
        return super(MultipleAutocompleteWidget, self).render(
            name, value, attrs, hattrs, initial_objects)

    def label_for_value(self, value):
        return ''

    def value_from_datadict(self, data, files, name):
        value = data.get(name)
        if value:
            return value.split(',')
        return value
    
    def initial_objects(self, value):
        qs, key, label_fmt = [self.settings[k] for k in ('queryset','id','label')]
        output = [u'<ul class="ui-autocomplete-values">']
        for obj in qs.filter(**{'%s__in' % key: value}):
            output.append(u'<li>%s</li>' % label_fmt(obj))
        output.append(u'</ul>\n')
        return mark_safe(u'\n'.join(output))


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 self.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)