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:
parent
c8da0874c7
commit
c349ba4cfc
@ -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:
|
||||||
|
@ -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_)
|
||||||
|
@ -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'}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user