Skip to content

Commit c349ba4

Browse files
committed
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.
git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@7771 bcc190cf-cafb-0310-a4f2-bffc1f526a37
1 parent c8da087 commit c349ba4

File tree

3 files changed

+71
-18
lines changed

3 files changed

+71
-18
lines changed

django/contrib/admin/options.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ def formfield_for_dbfield(self, db_field, **kwargs):
198198
formfield = db_field.formfield(**kwargs)
199199
# Don't wrap raw_id fields. Their add function is in the popup window.
200200
if not db_field.name in self.raw_id_fields:
201-
formfield.widget.render = widgets.RelatedFieldWidgetWrapper(formfield.widget.render, db_field.rel, self.admin_site)
201+
formfield.widget = widgets.RelatedFieldWidgetWrapper(formfield.widget, db_field.rel, self.admin_site)
202202
return formfield
203203

204204
if db_field.choices and db_field.name in self.radio_fields:

django/contrib/admin/widgets.py

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
Form Widget classes specific to the Django admin site.
33
"""
44

5+
import copy
6+
57
from django import newforms as forms
68
from django.newforms.widgets import RadioFieldRenderer
79
from django.newforms.util import flatatt
@@ -162,21 +164,34 @@ def _has_changed(self, initial, data):
162164
return True
163165
return False
164166

165-
class RelatedFieldWidgetWrapper(object):
167+
class RelatedFieldWidgetWrapper(forms.Widget):
166168
"""
167-
This class is a wrapper whose __call__() method mimics the interface of a
168-
Widget's render() method.
169+
This class is a wrapper to a given widget to add the add icon for the
170+
admin interface.
169171
"""
170-
def __init__(self, render_func, rel, admin_site):
171-
self.render_func, self.rel = render_func, rel
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
172179
# so we can check if the related object is registered with this AdminSite
173180
self.admin_site = admin_site
174181

175-
def __call__(self, name, value, *args, **kwargs):
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):
176190
from django.conf import settings
177191
rel_to = self.rel.to
178192
related_url = '../../../%s/%s/' % (rel_to._meta.app_label, rel_to._meta.object_name.lower())
179-
output = [self.render_func(name, value, *args, **kwargs)]
193+
self.widget.choices = self.choices
194+
output = [self.widget.render(name, value, *args, **kwargs)]
180195
if rel_to in self.admin_site._registry: # If the related object has an admin interface:
181196
# TODO: "id_" is hard-coded here. This should instead use the correct
182197
# API to determine the ID dynamically.
@@ -185,7 +200,16 @@ def __call__(self, name, value, *args, **kwargs):
185200
output.append(u'<img src="%simg/admin/icon_addlink.gif" width="10" height="10" alt="Add Another"/></a>' % settings.ADMIN_MEDIA_PREFIX)
186201
return mark_safe(u''.join(output))
187202

188-
def __deepcopy__(self, memo):
189-
# There's no reason to deepcopy admin_site, etc, so just return self.
190-
memo[id(self)] = self
191-
return self
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_)

tests/regressiontests/modeladmin/models.py

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -122,21 +122,50 @@ class and an AdminSite instance, so let's just go ahead and do that manually
122122
>>> type(ma.get_form(request).base_fields['sign_date'].widget)
123123
<class 'django.contrib.admin.widgets.AdminDateWidget'>
124124
125+
If we need to override the queryset of a ModelChoiceField in our custom form
126+
make sure that RelatedFieldWidgetWrapper doesn't mess that up.
127+
128+
>>> band2 = Band(name='The Beetles', bio='', sign_date=date(1962, 1, 1))
129+
>>> band2.save()
130+
131+
>>> class AdminConcertForm(forms.ModelForm):
132+
... class Meta:
133+
... model = Concert
134+
...
135+
... def __init__(self, *args, **kwargs):
136+
... super(AdminConcertForm, self).__init__(*args, **kwargs)
137+
... self.fields["main_band"].queryset = Band.objects.filter(name='The Doors')
138+
139+
>>> class ConcertAdmin(ModelAdmin):
140+
... form = AdminConcertForm
141+
142+
>>> ma = ConcertAdmin(Concert, site)
143+
>>> form = ma.get_form(request)()
144+
>>> print form["main_band"]
145+
<select name="main_band" id="id_main_band">
146+
<option value="" selected="selected">---------</option>
147+
<option value="1">The Doors</option>
148+
</select>
149+
150+
>>> band2.delete()
151+
125152
# radio_fields behavior ################################################
126153
127154
First, without any radio_fields specified, the widgets for ForeignKey
128155
and fields with choices specified ought to be a basic Select widget.
129-
For Select fields, all of the choices lists have a first entry of dashes.
156+
ForeignKey widgets in the admin are wrapped with RelatedFieldWidgetWrapper so
157+
they need to be handled properly when type checking. For Select fields, all of
158+
the choices lists have a first entry of dashes.
130159
131160
>>> cma = ModelAdmin(Concert, site)
132161
>>> cmafa = cma.get_form(request)
133162
134-
>>> type(cmafa.base_fields['main_band'].widget)
163+
>>> type(cmafa.base_fields['main_band'].widget.widget)
135164
<class 'django.newforms.widgets.Select'>
136165
>>> list(cmafa.base_fields['main_band'].widget.choices)
137166
[(u'', u'---------'), (1, u'The Doors')]
138167
139-
>>> type(cmafa.base_fields['opening_band'].widget)
168+
>>> type(cmafa.base_fields['opening_band'].widget.widget)
140169
<class 'django.newforms.widgets.Select'>
141170
>>> list(cmafa.base_fields['opening_band'].widget.choices)
142171
[(u'', u'---------'), (1, u'The Doors')]
@@ -152,7 +181,7 @@ class and an AdminSite instance, so let's just go ahead and do that manually
152181
[('', '---------'), (1, 'Plane'), (2, 'Train'), (3, 'Bus')]
153182
154183
Now specify all the fields as radio_fields. Widgets should now be
155-
RadioSelect, and the choices list should have a first entry of 'None' iff
184+
RadioSelect, and the choices list should have a first entry of 'None' if
156185
blank=True for the model field. Finally, the widget should have the
157186
'radiolist' attr, and 'inline' as well if the field is specified HORIZONTAL.
158187
@@ -167,14 +196,14 @@ class and an AdminSite instance, so let's just go ahead and do that manually
167196
>>> cma = ConcertAdmin(Concert, site)
168197
>>> cmafa = cma.get_form(request)
169198
170-
>>> type(cmafa.base_fields['main_band'].widget)
199+
>>> type(cmafa.base_fields['main_band'].widget.widget)
171200
<class 'django.contrib.admin.widgets.AdminRadioSelect'>
172201
>>> cmafa.base_fields['main_band'].widget.attrs
173202
{'class': 'radiolist inline'}
174203
>>> list(cmafa.base_fields['main_band'].widget.choices)
175204
[(1, u'The Doors')]
176205
177-
>>> type(cmafa.base_fields['opening_band'].widget)
206+
>>> type(cmafa.base_fields['opening_band'].widget.widget)
178207
<class 'django.contrib.admin.widgets.AdminRadioSelect'>
179208
>>> cmafa.base_fields['opening_band'].widget.attrs
180209
{'class': 'radiolist'}

0 commit comments

Comments
 (0)