Django

Code

root/django/branches/newforms-admin/django/contrib/admin/widgets.py

Revision 7771, 8.7 kB (checked in by brosner, 1 week ago)

newforms-admin: Fixed #7541 -- RelatedFieldWidgetWrapper? now wraps the widget and not the just the render function which caused some stale values. Thanks lukas and Doug Napoleone.

Line 
1 """
2 Form Widget classes specific to the Django admin site.
3 """
4
5 import copy
6
7 from django import newforms as forms
8 from django.newforms.widgets import RadioFieldRenderer
9 from django.newforms.util import flatatt
10 from django.utils.datastructures import MultiValueDict
11 from django.utils.text import capfirst, 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
17 class FilteredSelectMultiple(forms.SelectMultiple):
18     """
19     A SelectMultiple with a JavaScript filter interface.
20
21     Note that the resulting JavaScript assumes that the SelectFilter2.js
22     library and its dependencies have been loaded in the HTML page.
23     """
24     def __init__(self, verbose_name, is_stacked, attrs=None, choices=()):
25         self.verbose_name = verbose_name
26         self.is_stacked = is_stacked
27         super(FilteredSelectMultiple, self).__init__(attrs, choices)
28
29     def render(self, name, value, attrs=None, choices=()):
30         from django.conf import settings
31         output = [super(FilteredSelectMultiple, self).render(name, value, attrs, choices)]
32         output.append(u'<script type="text/javascript">addEvent(window, "load", function(e) {')
33         # TODO: "id_" is hard-coded here. This should instead use the correct
34         # API to determine the ID dynamically.
35         output.append(u'SelectFilter.init("id_%s", "%s", %s, "%s"); });</script>\n' % \
36             (name, self.verbose_name.replace('"', '\\"'), int(self.is_stacked), settings.ADMIN_MEDIA_PREFIX))
37         return mark_safe(u''.join(output))
38
39 class AdminDateWidget(forms.TextInput):
40     class Media:
41         js = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js",
42               settings.ADMIN_MEDIA_PREFIX + "js/admin/DateTimeShortcuts.js")
43        
44     def __init__(self, attrs={}):
45         super(AdminDateWidget, self).__init__(attrs={'class': 'vDateField', 'size': '10'})
46
47 class AdminTimeWidget(forms.TextInput):
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={}):
53         super(AdminTimeWidget, self).__init__(attrs={'class': 'vTimeField', 'size': '8'})
54    
55 class AdminSplitDateTime(forms.SplitDateTimeWidget):
56     """
57     A SplitDateTime Widget that has some admin-specific styling.
58     """
59     def __init__(self, attrs=None):
60         widgets = [AdminDateWidget, AdminTimeWidget]
61         # Note that we're calling MultiWidget, not SplitDateTimeWidget, because
62         # we want to define widgets.
63         forms.MultiWidget.__init__(self, widgets, attrs)
64
65     def format_output(self, rendered_widgets):
66         return mark_safe(u'<p class="datetime">%s %s<br />%s %s</p>' % \
67             (_('Date:'), rendered_widgets[0], _('Time:'), rendered_widgets[1]))
68
69 class AdminRadioFieldRenderer(RadioFieldRenderer):
70     def render(self):
71         """Outputs a <ul> for this set of radio fields."""
72         return mark_safe(u'<ul%s>\n%s\n</ul>' % (
73             flatatt(self.attrs),
74             u'\n'.join([u'<li>%s</li>' % force_unicode(w) for w in self]))
75         )
76
77 class AdminRadioSelect(forms.RadioSelect):
78     renderer = AdminRadioFieldRenderer
79
80 class AdminFileWidget(forms.FileInput):
81     """
82     A FileField Widget that shows its current value if it has one.
83     """
84     def __init__(self, attrs={}):
85         super(AdminFileWidget, self).__init__(attrs)
86        
87     def render(self, name, value, attrs=None):
88         from django.conf import settings
89         output = []
90         if value:
91             output.append('%s <a target="_blank" href="%s%s">%s</a> <br />%s ' % \
92                 (_('Currently:'), settings.MEDIA_URL, value, value, _('Change:')))
93         output.append(super(AdminFileWidget, self).render(name, value, attrs))
94         return mark_safe(u''.join(output))
95
96 class ForeignKeyRawIdWidget(forms.TextInput):
97     """
98     A Widget for displaying ForeignKeys in the "raw_id" interface rather than
99     in a <select> box.
100     """
101     def __init__(self, rel, attrs=None):
102         self.rel = rel
103         super(ForeignKeyRawIdWidget, self).__init__(attrs)
104
105     def render(self, name, value, attrs=None):
106         from django.conf import settings
107         related_url = '../../../%s/%s/' % (self.rel.to._meta.app_label, self.rel.to._meta.object_name.lower())
108         if self.rel.limit_choices_to:
109             url = '?' + '&amp;'.join(['%s=%s' % (k, v) for k, v in self.rel.limit_choices_to.items()])
110         else:
111             url = ''
112         if not attrs.has_key('class'):
113           attrs['class'] = 'vForeignKeyRawIdAdminField' # The JavaScript looks for this hook.
114         output = [super(ForeignKeyRawIdWidget, self).render(name, value, attrs)]
115         # TODO: "id_" is hard-coded here. This should instead use the correct
116         # API to determine the ID dynamically.
117         output.append('<a href="%s%s" class="related-lookup" id="lookup_id_%s" onclick="return showRelatedObjectLookupPopup(this);"> ' % \
118             (related_url, url, name))
119         output.append('<img src="%simg/admin/selector-search.gif" width="16" height="16" alt="Lookup" /></a>' % settings.ADMIN_MEDIA_PREFIX)
120         if value:
121             output.append(self.label_for_value(value))
122         return mark_safe(u''.join(output))
123    
124     def label_for_value(self, value):
125         return '&nbsp;<strong>%s</strong>' % \
126             truncate_words(self.rel.to.objects.get(pk=value), 14)
127            
128 class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):
129     """
130     A Widget for displaying ManyToMany ids in the "raw_id" interface rather than
131     in a <select multiple> box.
132     """
133     def __init__(self, rel, attrs=None):
134         super(ManyToManyRawIdWidget, self).__init__(rel, attrs)
135    
136     def render(self, name, value, attrs=None):
137         attrs['class'] = 'vManyToManyRawIdAdminField'
138         if value:
139             value = ','.join([str(v) for v in value])
140         else:
141             value = ''
142         return super(ManyToManyRawIdWidget, self).render(name, value, attrs)
143    
144     def label_for_value(self, value):
145         return ''
146
147     def value_from_datadict(self, data, files, name):
148         value = data.get(name, None)
149         if value and ',' in value:
150             return data[name].split(',')
151         if value:
152             return [value]
153         return None
154    
155     def _has_changed(self, initial, data):
156         if initial is None:
157             initial = []
158         if data is None:
159             data = []
160         if len(initial) != len(data):
161             return True
162         for pk1, pk2 in zip(initial, data):
163             if force_unicode(pk1) != force_unicode(pk2):
164                 return True
165         return False
166
167 class RelatedFieldWidgetWrapper(forms.Widget):
168     """
169     This class is a wrapper to a given widget to add the add icon for the
170     admin interface.
171     """
172     def __init__(self, widget, rel, admin_site):
173         self.is_hidden = widget.is_hidden
174         self.needs_multipart_form = widget.needs_multipart_form
175         self.attrs = widget.attrs
176         self.choices = widget.choices
177         self.widget = widget
178         self.rel = rel
179         # so we can check if the related object is registered with this AdminSite
180         self.admin_site = admin_site
181
182     def __deepcopy__(self, memo):
183         obj = copy.copy(self)
184         obj.widget = copy.deepcopy(self.widget, memo)
185         obj.attrs = self.widget.attrs
186         memo[id(self)] = obj
187         return obj
188
189     def render(self, name, value, *args, **kwargs):
190         from django.conf import settings
191         rel_to = self.rel.to
192         related_url = '../../../%s/%s/' % (rel_to._meta.app_label, rel_to._meta.object_name.lower())
193         self.widget.choices = self.choices
194         output = [self.widget.render(name, value, *args, **kwargs)]
195         if rel_to in self.admin_site._registry: # If the related object has an admin interface:
196             # TODO: "id_" is hard-coded here. This should instead use the correct
197             # API to determine the ID dynamically.
198             output.append(u'<a href="%sadd/" class="add-another" id="add_id_%s" onclick="return showAddAnotherPopup(this);"> ' % \
199                 (related_url, name))
200             output.append(u'<img src="%simg/admin/icon_addlink.gif" width="10" height="10" alt="Add Another"/></a>' % settings.ADMIN_MEDIA_PREFIX)
201         return mark_safe(u''.join(output))
202
203     def build_attrs(self, extra_attrs=None, **kwargs):
204         "Helper function for building an attribute dictionary."
205         self.attrs = self.widget.build_attrs(extra_attrs=None, **kwargs)
206         return self.attrs
207
208     def value_from_datadict(self, data, files, name):
209         return self.widget.value_from_datadict(data, files, name)
210
211     def _has_changed(self, initial, data):
212         return self.widget._has_changed(initial, data)
213
214     def id_for_label(self, id_):
215         return self.widget.id_for_label(id_)
Note: See TracBrowser for help on using the browser.