mirror of
https://github.com/django/django.git
synced 2025-07-03 17:29:12 +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:
parent
c8da0874c7
commit
c349ba4cfc
@ -198,7 +198,7 @@ class BaseModelAdmin(object):
|
||||
formfield = db_field.formfield(**kwargs)
|
||||
# Don't wrap raw_id fields. Their add function is in the popup window.
|
||||
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
|
||||
|
||||
if db_field.choices and db_field.name in self.radio_fields:
|
||||
|
@ -2,6 +2,8 @@
|
||||
Form Widget classes specific to the Django admin site.
|
||||
"""
|
||||
|
||||
import copy
|
||||
|
||||
from django import newforms as forms
|
||||
from django.newforms.widgets import RadioFieldRenderer
|
||||
from django.newforms.util import flatatt
|
||||
@ -162,21 +164,34 @@ class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):
|
||||
return True
|
||||
return False
|
||||
|
||||
class RelatedFieldWidgetWrapper(object):
|
||||
class RelatedFieldWidgetWrapper(forms.Widget):
|
||||
"""
|
||||
This class is a wrapper whose __call__() method mimics the interface of a
|
||||
Widget's render() method.
|
||||
This class is a wrapper to a given widget to add the add icon for the
|
||||
admin interface.
|
||||
"""
|
||||
def __init__(self, render_func, rel, admin_site):
|
||||
self.render_func, self.rel = render_func, rel
|
||||
def __init__(self, widget, rel, admin_site):
|
||||
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
|
||||
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
|
||||
rel_to = self.rel.to
|
||||
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:
|
||||
# TODO: "id_" is hard-coded here. This should instead use the correct
|
||||
# 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)
|
||||
return mark_safe(u''.join(output))
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
# There's no reason to deepcopy admin_site, etc, so just return self.
|
||||
memo[id(self)] = self
|
||||
return self
|
||||
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_)
|
||||
|
@ -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)
|
||||
<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 ################################################
|
||||
|
||||
First, without any radio_fields specified, the widgets for ForeignKey
|
||||
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)
|
||||
>>> 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'>
|
||||
>>> list(cmafa.base_fields['main_band'].widget.choices)
|
||||
[(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'>
|
||||
>>> list(cmafa.base_fields['opening_band'].widget.choices)
|
||||
[(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')]
|
||||
|
||||
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
|
||||
'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)
|
||||
>>> 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'>
|
||||
>>> cmafa.base_fields['main_band'].widget.attrs
|
||||
{'class': 'radiolist inline'}
|
||||
>>> list(cmafa.base_fields['main_band'].widget.choices)
|
||||
[(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'>
|
||||
>>> cmafa.base_fields['opening_band'].widget.attrs
|
||||
{'class': 'radiolist'}
|
||||
|
Loading…
x
Reference in New Issue
Block a user