1
0
mirror of https://github.com/django/django.git synced 2025-07-04 01:39:20 +00:00

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
This commit is contained in:
Brian Rosner 2008-06-26 16:53:53 +00:00
parent c8da0874c7
commit c349ba4cfc
3 changed files with 71 additions and 18 deletions

View File

@ -198,7 +198,7 @@ class BaseModelAdmin(object):
formfield = db_field.formfield(**kwargs) formfield = db_field.formfield(**kwargs)
# Don't wrap raw_id fields. Their add function is in the popup window. # Don't wrap raw_id fields. Their add function is in the popup window.
if not db_field.name in self.raw_id_fields: if not db_field.name in self.raw_id_fields:
formfield.widget.render = widgets.RelatedFieldWidgetWrapper(formfield.widget.render, db_field.rel, self.admin_site) formfield.widget = widgets.RelatedFieldWidgetWrapper(formfield.widget, db_field.rel, self.admin_site)
return formfield return formfield
if db_field.choices and db_field.name in self.radio_fields: if db_field.choices and db_field.name in self.radio_fields:

View File

@ -2,6 +2,8 @@
Form Widget classes specific to the Django admin site. Form Widget classes specific to the Django admin site.
""" """
import copy
from django import newforms as forms from django import newforms as forms
from django.newforms.widgets import RadioFieldRenderer from django.newforms.widgets import RadioFieldRenderer
from django.newforms.util import flatatt from django.newforms.util import flatatt
@ -162,21 +164,34 @@ class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):
return True return True
return False return False
class RelatedFieldWidgetWrapper(object): class RelatedFieldWidgetWrapper(forms.Widget):
""" """
This class is a wrapper whose __call__() method mimics the interface of a This class is a wrapper to a given widget to add the add icon for the
Widget's render() method. admin interface.
""" """
def __init__(self, render_func, rel, admin_site): def __init__(self, widget, rel, admin_site):
self.render_func, self.rel = render_func, rel 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
# so we can check if the related object is registered with this AdminSite # so we can check if the related object is registered with this AdminSite
self.admin_site = admin_site self.admin_site = admin_site
def __call__(self, name, value, *args, **kwargs): 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 render(self, name, value, *args, **kwargs):
from django.conf import settings from django.conf import settings
rel_to = self.rel.to rel_to = self.rel.to
related_url = '../../../%s/%s/' % (rel_to._meta.app_label, rel_to._meta.object_name.lower()) related_url = '../../../%s/%s/' % (rel_to._meta.app_label, rel_to._meta.object_name.lower())
output = [self.render_func(name, value, *args, **kwargs)] self.widget.choices = self.choices
output = [self.widget.render(name, value, *args, **kwargs)]
if rel_to in self.admin_site._registry: # If the related object has an admin interface: if rel_to in self.admin_site._registry: # If the related object has an admin interface:
# TODO: "id_" is hard-coded here. This should instead use the correct # TODO: "id_" is hard-coded here. This should instead use the correct
# API to determine the ID dynamically. # API to determine the ID dynamically.
@ -185,7 +200,16 @@ class RelatedFieldWidgetWrapper(object):
output.append(u'<img src="%simg/admin/icon_addlink.gif" width="10" height="10" alt="Add Another"/></a>' % settings.ADMIN_MEDIA_PREFIX) output.append(u'<img src="%simg/admin/icon_addlink.gif" width="10" height="10" alt="Add Another"/></a>' % settings.ADMIN_MEDIA_PREFIX)
return mark_safe(u''.join(output)) return mark_safe(u''.join(output))
def __deepcopy__(self, memo): def build_attrs(self, extra_attrs=None, **kwargs):
# There's no reason to deepcopy admin_site, etc, so just return self. "Helper function for building an attribute dictionary."
memo[id(self)] = self self.attrs = self.widget.build_attrs(extra_attrs=None, **kwargs)
return self 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_)

View File

@ -122,21 +122,50 @@ properly. This won't, however, break any of the admin widgets or media.
>>> type(ma.get_form(request).base_fields['sign_date'].widget) >>> type(ma.get_form(request).base_fields['sign_date'].widget)
<class 'django.contrib.admin.widgets.AdminDateWidget'> <class 'django.contrib.admin.widgets.AdminDateWidget'>
If we need to override the queryset of a ModelChoiceField in our custom form
make sure that RelatedFieldWidgetWrapper doesn't mess that up.
>>> band2 = Band(name='The Beetles', bio='', sign_date=date(1962, 1, 1))
>>> band2.save()
>>> class AdminConcertForm(forms.ModelForm):
... class Meta:
... model = Concert
...
... def __init__(self, *args, **kwargs):
... super(AdminConcertForm, self).__init__(*args, **kwargs)
... self.fields["main_band"].queryset = Band.objects.filter(name='The Doors')
>>> class ConcertAdmin(ModelAdmin):
... form = AdminConcertForm
>>> ma = ConcertAdmin(Concert, site)
>>> form = ma.get_form(request)()
>>> print form["main_band"]
<select name="main_band" id="id_main_band">
<option value="" selected="selected">---------</option>
<option value="1">The Doors</option>
</select>
>>> band2.delete()
# radio_fields behavior ################################################ # radio_fields behavior ################################################
First, without any radio_fields specified, the widgets for ForeignKey First, without any radio_fields specified, the widgets for ForeignKey
and fields with choices specified ought to be a basic Select widget. and fields with choices specified ought to be a basic Select widget.
For Select fields, all of the choices lists have a first entry of dashes. ForeignKey widgets in the admin are wrapped with RelatedFieldWidgetWrapper so
they need to be handled properly when type checking. For Select fields, all of
the choices lists have a first entry of dashes.
>>> cma = ModelAdmin(Concert, site) >>> cma = ModelAdmin(Concert, site)
>>> cmafa = cma.get_form(request) >>> cmafa = cma.get_form(request)
>>> type(cmafa.base_fields['main_band'].widget) >>> type(cmafa.base_fields['main_band'].widget.widget)
<class 'django.newforms.widgets.Select'> <class 'django.newforms.widgets.Select'>
>>> list(cmafa.base_fields['main_band'].widget.choices) >>> list(cmafa.base_fields['main_band'].widget.choices)
[(u'', u'---------'), (1, u'The Doors')] [(u'', u'---------'), (1, u'The Doors')]
>>> type(cmafa.base_fields['opening_band'].widget) >>> type(cmafa.base_fields['opening_band'].widget.widget)
<class 'django.newforms.widgets.Select'> <class 'django.newforms.widgets.Select'>
>>> list(cmafa.base_fields['opening_band'].widget.choices) >>> list(cmafa.base_fields['opening_band'].widget.choices)
[(u'', u'---------'), (1, u'The Doors')] [(u'', u'---------'), (1, u'The Doors')]
@ -152,7 +181,7 @@ For Select fields, all of the choices lists have a first entry of dashes.
[('', '---------'), (1, 'Plane'), (2, 'Train'), (3, 'Bus')] [('', '---------'), (1, 'Plane'), (2, 'Train'), (3, 'Bus')]
Now specify all the fields as radio_fields. Widgets should now be Now specify all the fields as radio_fields. Widgets should now be
RadioSelect, and the choices list should have a first entry of 'None' iff RadioSelect, and the choices list should have a first entry of 'None' if
blank=True for the model field. Finally, the widget should have the blank=True for the model field. Finally, the widget should have the
'radiolist' attr, and 'inline' as well if the field is specified HORIZONTAL. 'radiolist' attr, and 'inline' as well if the field is specified HORIZONTAL.
@ -167,14 +196,14 @@ blank=True for the model field. Finally, the widget should have the
>>> cma = ConcertAdmin(Concert, site) >>> cma = ConcertAdmin(Concert, site)
>>> cmafa = cma.get_form(request) >>> cmafa = cma.get_form(request)
>>> type(cmafa.base_fields['main_band'].widget) >>> type(cmafa.base_fields['main_band'].widget.widget)
<class 'django.contrib.admin.widgets.AdminRadioSelect'> <class 'django.contrib.admin.widgets.AdminRadioSelect'>
>>> cmafa.base_fields['main_band'].widget.attrs >>> cmafa.base_fields['main_band'].widget.attrs
{'class': 'radiolist inline'} {'class': 'radiolist inline'}
>>> list(cmafa.base_fields['main_band'].widget.choices) >>> list(cmafa.base_fields['main_band'].widget.choices)
[(1, u'The Doors')] [(1, u'The Doors')]
>>> type(cmafa.base_fields['opening_band'].widget) >>> type(cmafa.base_fields['opening_band'].widget.widget)
<class 'django.contrib.admin.widgets.AdminRadioSelect'> <class 'django.contrib.admin.widgets.AdminRadioSelect'>
>>> cmafa.base_fields['opening_band'].widget.attrs >>> cmafa.base_fields['opening_band'].widget.attrs
{'class': 'radiolist'} {'class': 'radiolist'}