diff options
| -rw-r--r-- | django/contrib/admin/options.py | 6 | ||||
| -rw-r--r-- | django/contrib/admin/widgets.py | 51 | ||||
| -rw-r--r-- | tests/regressiontests/admin_widgets/models.py | 32 | ||||
| -rw-r--r-- | tests/regressiontests/admin_widgets/tests.py | 92 | ||||
| -rw-r--r-- | tests/regressiontests/admin_widgets/widgetadmin.py | 10 |
5 files changed, 143 insertions, 48 deletions
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index b0eb113359..35c3cde0fc 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -154,7 +154,8 @@ class BaseModelAdmin(object): """ db = kwargs.get('using') if db_field.name in self.raw_id_fields: - kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel, using=db) + kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel, + self.admin_site, using=db) elif db_field.name in self.radio_fields: kwargs['widget'] = widgets.AdminRadioSelect(attrs={ 'class': get_ul_class(self.radio_fields[db_field.name]), @@ -174,7 +175,8 @@ class BaseModelAdmin(object): db = kwargs.get('using') if db_field.name in self.raw_id_fields: - kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel, using=db) + kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel, + self.admin_site, using=db) kwargs['help_text'] = '' elif db_field.name in (list(self.filter_vertical) + list(self.filter_horizontal)): kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical)) diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py index 713ce63831..038351e6da 100644 --- a/django/contrib/admin/widgets.py +++ b/django/contrib/admin/widgets.py @@ -4,7 +4,7 @@ Form Widget classes specific to the Django admin site. import copy from django import forms -from django.core.urlresolvers import reverse, NoReverseMatch +from django.core.urlresolvers import reverse from django.forms.widgets import RadioFieldRenderer from django.forms.util import flatatt from django.templatetags.static import static @@ -112,29 +112,38 @@ class ForeignKeyRawIdWidget(forms.TextInput): A Widget for displaying ForeignKeys in the "raw_id" interface rather than in a <select> box. """ - def __init__(self, rel, attrs=None, using=None): + def __init__(self, rel, admin_site, attrs=None, using=None): self.rel = rel + self.admin_site = admin_site self.db = using super(ForeignKeyRawIdWidget, self).__init__(attrs) def render(self, name, value, attrs=None): + rel_to = self.rel.to if attrs is None: attrs = {} - related_url = '../../../%s/%s/' % (self.rel.to._meta.app_label, self.rel.to._meta.object_name.lower()) - params = self.url_parameters() - if params: - url = u'?' + u'&'.join([u'%s=%s' % (k, v) for k, v in params.items()]) - else: - url = u'' - if "class" not in attrs: - attrs['class'] = 'vForeignKeyRawIdAdminField' # The JavaScript looks for this hook. - output = [super(ForeignKeyRawIdWidget, self).render(name, value, attrs)] - # TODO: "id_" is hard-coded here. This should instead use the correct - # API to determine the ID dynamically. - output.append(u'<a href="%s%s" class="related-lookup" id="lookup_id_%s" onclick="return showRelatedObjectLookupPopup(this);"> ' - % (related_url, url, name)) - output.append(u'<img src="%s" width="16" height="16" alt="%s" /></a>' - % (static('admin/img/selector-search.gif'), _('Lookup'))) + extra = [] + if rel_to in self.admin_site._registry: + # The related object is registered with the same AdminSite + related_url = reverse('admin:%s_%s_changelist' % + (rel_to._meta.app_label, + rel_to._meta.module_name), + current_app=self.admin_site.name) + + params = self.url_parameters() + if params: + url = u'?' + u'&'.join([u'%s=%s' % (k, v) for k, v in params.items()]) + else: + url = u'' + if "class" not in attrs: + attrs['class'] = 'vForeignKeyRawIdAdminField' # The JavaScript code looks for this hook. + # TODO: "lookup_id_" is hard-coded here. This should instead use + # the correct API to determine the ID dynamically. + extra.append(u'<a href="%s%s" class="related-lookup" id="lookup_id_%s" onclick="return showRelatedObjectLookupPopup(this);"> ' + % (related_url, url, name)) + extra.append(u'<img src="%s" width="16" height="16" alt="%s" /></a>' + % (static('admin/img/selector-search.gif'), _('Lookup'))) + output = [super(ForeignKeyRawIdWidget, self).render(name, value, attrs)] + extra if value: output.append(self.label_for_value(value)) return mark_safe(u''.join(output)) @@ -164,7 +173,9 @@ class ManyToManyRawIdWidget(ForeignKeyRawIdWidget): def render(self, name, value, attrs=None): if attrs is None: attrs = {} - attrs['class'] = 'vManyToManyRawIdAdminField' + if self.rel.to in self.admin_site._registry: + # The related object is registered with the same AdminSite + attrs['class'] = 'vManyToManyRawIdAdminField' if value: value = ','.join([force_unicode(v) for v in value]) else: @@ -232,8 +243,8 @@ class RelatedFieldWidgetWrapper(forms.Widget): output = [self.widget.render(name, value, *args, **kwargs)] if self.can_add_related: related_url = reverse('admin:%s_%s_add' % info, current_app=self.admin_site.name) - # TODO: "id_" is hard-coded here. This should instead use the correct - # API to determine the ID dynamically. + # TODO: "add_id_" is hard-coded here. This should instead use the + # correct API to determine the ID dynamically. output.append(u'<a href="%s" class="add-another" id="add_id_%s" onclick="return showAddAnotherPopup(this);"> ' % (related_url, name)) output.append(u'<img src="%s" width="10" height="10" alt="%s"/></a>' diff --git a/tests/regressiontests/admin_widgets/models.py b/tests/regressiontests/admin_widgets/models.py index c187584996..c4489e07c7 100644 --- a/tests/regressiontests/admin_widgets/models.py +++ b/tests/regressiontests/admin_widgets/models.py @@ -67,3 +67,35 @@ class CarTire(models.Model): A single car tire. This to test that a user can only select their own cars. """ car = models.ForeignKey(Car) + +class Honeycomb(models.Model): + location = models.CharField(max_length=20) + +class Bee(models.Model): + """ + A model with a FK to a model that won't be registered with the admin + (Honeycomb) so the corresponding raw ID widget won't have a magnifying + glass link to select related honeycomb instances. + """ + honeycomb = models.ForeignKey(Honeycomb) + +class Individual(models.Model): + """ + A model with a FK to itself. It won't be registered with the admin, so the + corresponding raw ID widget won't have a magnifying glass link to select + related instances (rendering will be called programmatically in this case). + """ + name = models.CharField(max_length=20) + parent = models.ForeignKey('self', null=True) + +class Company(models.Model): + name = models.CharField(max_length=20) + +class Advisor(models.Model): + """ + A model with a m2m to a model that won't be registered with the admin + (Company) so the corresponding raw ID widget won't have a magnifying + glass link to select related company instances. + """ + name = models.CharField(max_length=20) + companies = models.ManyToManyField(Company) diff --git a/tests/regressiontests/admin_widgets/tests.py b/tests/regressiontests/admin_widgets/tests.py index 3cdb017372..c1b6888967 100644 --- a/tests/regressiontests/admin_widgets/tests.py +++ b/tests/regressiontests/admin_widgets/tests.py @@ -7,10 +7,6 @@ from django import forms from django.conf import settings from django.contrib import admin from django.contrib.admin import widgets -from django.contrib.admin.widgets import (FilteredSelectMultiple, - AdminSplitDateTime, AdminFileWidget, ForeignKeyRawIdWidget, AdminRadioSelect, - RelatedFieldWidgetWrapper, ManyToManyRawIdWidget, - url_params_from_lookup_dict) from django.core.files.storage import default_storage from django.core.files.uploadedfile import SimpleUploadedFile from django.db.models import DateField @@ -20,6 +16,7 @@ from django.utils.html import conditional_escape from django.utils.unittest import TestCase import models +from widgetadmin import site as widget_admin_site admin_media_prefix = lambda: { 'ADMIN_MEDIA_PREFIX': "%sadmin/" % settings.STATIC_URL, @@ -186,22 +183,22 @@ class AdminForeignKeyRawIdWidget(DjangoTestCase): 'Select a valid choice. That choice is not one of the available choices.') def test_url_params_from_lookup_dict_any_iterable(self): - lookup1 = url_params_from_lookup_dict({'color__in': ('red', 'blue')}) - lookup2 = url_params_from_lookup_dict({'color__in': ['red', 'blue']}) + lookup1 = widgets.url_params_from_lookup_dict({'color__in': ('red', 'blue')}) + lookup2 = widgets.url_params_from_lookup_dict({'color__in': ['red', 'blue']}) self.assertEqual(lookup1, {'color__in': 'red,blue'}) self.assertEqual(lookup1, lookup2) class FilteredSelectMultipleWidgetTest(DjangoTestCase): def test_render(self): - w = FilteredSelectMultiple('test', False) + w = widgets.FilteredSelectMultiple('test', False) self.assertEqual( conditional_escape(w.render('test', 'test')), '<select multiple="multiple" name="test" class="selectfilter">\n</select><script type="text/javascript">addEvent(window, "load", function(e) {SelectFilter.init("id_test", "test", 0, "%(ADMIN_MEDIA_PREFIX)s"); });</script>\n' % admin_media_prefix() ) def test_stacked_render(self): - w = FilteredSelectMultiple('test', True) + w = widgets.FilteredSelectMultiple('test', True) self.assertEqual( conditional_escape(w.render('test', 'test')), '<select multiple="multiple" name="test" class="selectfilterstacked">\n</select><script type="text/javascript">addEvent(window, "load", function(e) {SelectFilter.init("id_test", "test", 1, "%(ADMIN_MEDIA_PREFIX)s"); });</script>\n' % admin_media_prefix() @@ -210,14 +207,14 @@ class FilteredSelectMultipleWidgetTest(DjangoTestCase): class AdminSplitDateTimeWidgetTest(DjangoTestCase): def test_render(self): - w = AdminSplitDateTime() + w = widgets.AdminSplitDateTime() self.assertEqual( conditional_escape(w.render('test', datetime(2007, 12, 1, 9, 30))), '<p class="datetime">Date: <input value="2007-12-01" type="text" class="vDateField" name="test_0" size="10" /><br />Time: <input value="09:30:00" type="text" class="vTimeField" name="test_1" size="8" /></p>', ) def test_localization(self): - w = AdminSplitDateTime() + w = widgets.AdminSplitDateTime() with self.settings(USE_L10N=True): with translation.override('de-at'): @@ -235,7 +232,7 @@ class AdminFileWidgetTest(DjangoTestCase): name='Hybrid Theory', cover_art=r'albums\hybrid_theory.jpg' ) - w = AdminFileWidget() + w = widgets.AdminFileWidget() self.assertEqual( conditional_escape(w.render('test', album.cover_art)), '<p class="file-upload">Currently: <a href="%(STORAGE_URL)salbums/hybrid_theory.jpg">albums\hybrid_theory.jpg</a> <span class="clearable-file-input"><input type="checkbox" name="test-clear" id="test-clear_id" /> <label for="test-clear_id">Clear</label></span><br />Change: <input type="file" name="test" /></p>' % { 'STORAGE_URL': default_storage.url('') }, @@ -255,10 +252,10 @@ class ForeignKeyRawIdWidgetTest(DjangoTestCase): ) rel = models.Album._meta.get_field('band').rel - w = ForeignKeyRawIdWidget(rel) + w = widgets.ForeignKeyRawIdWidget(rel, widget_admin_site) self.assertEqual( conditional_escape(w.render('test', band.pk, attrs={})), - '<input type="text" name="test" value="%(bandpk)s" class="vForeignKeyRawIdAdminField" /><a href="../../../admin_widgets/band/?t=id" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_MEDIA_PREFIX)simg/selector-search.gif" width="16" height="16" alt="Lookup" /></a> <strong>Linkin Park</strong>' % dict(admin_media_prefix(), bandpk=band.pk), + '<input type="text" name="test" value="%(bandpk)s" class="vForeignKeyRawIdAdminField" /><a href="/widget_admin/admin_widgets/band/?t=id" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_MEDIA_PREFIX)simg/selector-search.gif" width="16" height="16" alt="Lookup" /></a> <strong>Linkin Park</strong>' % dict(admin_media_prefix(), bandpk=band.pk) ) def test_relations_to_non_primary_key(self): @@ -270,17 +267,42 @@ class ForeignKeyRawIdWidgetTest(DjangoTestCase): barcode=87, name='Core', parent=apple ) rel = models.Inventory._meta.get_field('parent').rel - w = ForeignKeyRawIdWidget(rel) + w = widgets.ForeignKeyRawIdWidget(rel, widget_admin_site) self.assertEqual( w.render('test', core.parent_id, attrs={}), - '<input type="text" name="test" value="86" class="vForeignKeyRawIdAdminField" /><a href="../../../admin_widgets/inventory/?t=barcode" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_MEDIA_PREFIX)simg/selector-search.gif" width="16" height="16" alt="Lookup" /></a> <strong>Apple</strong>' % admin_media_prefix(), + '<input type="text" name="test" value="86" class="vForeignKeyRawIdAdminField" /><a href="/widget_admin/admin_widgets/inventory/?t=barcode" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_MEDIA_PREFIX)simg/selector-search.gif" width="16" height="16" alt="Lookup" /></a> <strong>Apple</strong>' % admin_media_prefix() ) + def test_fk_related_model_not_in_admin(self): + # FK to a model not registered with admin site. Raw ID widget shoud + # have no magnifying glass link. See #16542 + big_honeycomb = models.Honeycomb.objects.create(location='Old tree') + big_honeycomb.bee_set.create() + rel = models.Bee._meta.get_field('honeycomb').rel + + w = widgets.ForeignKeyRawIdWidget(rel, widget_admin_site) + self.assertEqual( + conditional_escape(w.render('honeycomb_widget', big_honeycomb.pk, attrs={})), + '<input type="text" name="honeycomb_widget" value="%(hcombpk)s" /> <strong>Honeycomb object</strong>' % {'hcombpk': big_honeycomb.pk} + ) + + def test_fk_to_self_model_not_in_admin(self): + # FK to self, not registered with admin site. Raw ID widget shoud have + # no magnifying glass link. See #16542 + subject1 = models.Individual.objects.create(name='Subject #1') + models.Individual.objects.create(name='Child', parent=subject1) + rel = models.Individual._meta.get_field('parent').rel + + w = widgets.ForeignKeyRawIdWidget(rel, widget_admin_site) + self.assertEqual( + conditional_escape(w.render('individual_widget', subject1.pk, attrs={})), + '<input type="text" name="individual_widget" value="%(subj1pk)s" /> <strong>Individual object</strong>' % {'subj1pk': subject1.pk} + ) def test_proper_manager_for_label_lookup(self): # see #9258 rel = models.Inventory._meta.get_field('parent').rel - w = ForeignKeyRawIdWidget(rel) + w = widgets.ForeignKeyRawIdWidget(rel, widget_admin_site) hidden = models.Inventory.objects.create( barcode=93, name='Hidden', hidden=True @@ -290,31 +312,28 @@ class ForeignKeyRawIdWidgetTest(DjangoTestCase): ) self.assertEqual( w.render('test', child_of_hidden.parent_id, attrs={}), - '<input type="text" name="test" value="93" class="vForeignKeyRawIdAdminField" /><a href="../../../admin_widgets/inventory/?t=barcode" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_MEDIA_PREFIX)simg/selector-search.gif" width="16" height="16" alt="Lookup" /></a> <strong>Hidden</strong>' % admin_media_prefix(), + '<input type="text" name="test" value="93" class="vForeignKeyRawIdAdminField" /><a href="/widget_admin/admin_widgets/inventory/?t=barcode" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_MEDIA_PREFIX)simg/selector-search.gif" width="16" height="16" alt="Lookup" /></a> <strong>Hidden</strong>' % admin_media_prefix() ) class ManyToManyRawIdWidgetTest(DjangoTestCase): def test_render(self): band = models.Band.objects.create(name='Linkin Park') - band.album_set.create( - name='Hybrid Theory', cover_art=r'albums\hybrid_theory.jpg' - ) m1 = models.Member.objects.create(name='Chester') m2 = models.Member.objects.create(name='Mike') band.members.add(m1, m2) rel = models.Band._meta.get_field('members').rel - w = ManyToManyRawIdWidget(rel) + w = widgets.ManyToManyRawIdWidget(rel, widget_admin_site) self.assertEqual( conditional_escape(w.render('test', [m1.pk, m2.pk], attrs={})), - '<input type="text" name="test" value="%(m1pk)s,%(m2pk)s" class="vManyToManyRawIdAdminField" /><a href="../../../admin_widgets/member/" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_MEDIA_PREFIX)simg/selector-search.gif" width="16" height="16" alt="Lookup" /></a>' % dict(admin_media_prefix(), m1pk=m1.pk, m2pk=m2.pk), + '<input type="text" name="test" value="%(m1pk)s,%(m2pk)s" class="vManyToManyRawIdAdminField" /><a href="/widget_admin/admin_widgets/member/" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="/static/admin/img/selector-search.gif" width="16" height="16" alt="Lookup" /></a>' % dict(admin_media_prefix(), m1pk=m1.pk, m2pk=m2.pk) ) self.assertEqual( conditional_escape(w.render('test', [m1.pk])), - '<input type="text" name="test" value="%(m1pk)s" class="vManyToManyRawIdAdminField" /><a href="../../../admin_widgets/member/" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_MEDIA_PREFIX)simg/selector-search.gif" width="16" height="16" alt="Lookup" /></a>' % dict(admin_media_prefix(), m1pk=m1.pk, m2pk=m2.pk), + '<input type="text" name="test" value="%(m1pk)s" class="vManyToManyRawIdAdminField" /><a href="/widget_admin/admin_widgets/member/" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_MEDIA_PREFIX)simg/selector-search.gif" width="16" height="16" alt="Lookup" /></a>' % dict(admin_media_prefix(), m1pk=m1.pk) ) self.assertEqual(w._has_changed(None, None), False) @@ -324,10 +343,31 @@ class ManyToManyRawIdWidgetTest(DjangoTestCase): self.assertEqual(w._has_changed([1, 2], [u'1']), True) self.assertEqual(w._has_changed([1, 2], [u'1', u'3']), True) + def test_m2m_related_model_not_in_admin(self): + # M2M relationship with model not registered with admin site. Raw ID + # widget shoud have no magnifying glass link. See #16542 + consultor1 = models.Advisor.objects.create(name='Rockstar Techie') + + c1 = models.Company.objects.create(name='Doodle') + c2 = models.Company.objects.create(name='Pear') + consultor1.companies.add(c1, c2) + rel = models.Advisor._meta.get_field('companies').rel + + w = widgets.ManyToManyRawIdWidget(rel, widget_admin_site) + self.assertEqual( + conditional_escape(w.render('company_widget1', [c1.pk, c2.pk], attrs={})), + '<input type="text" name="company_widget1" value="%(c1pk)s,%(c2pk)s" />' % {'c1pk': c1.pk, 'c2pk': c2.pk} + ) + + self.assertEqual( + conditional_escape(w.render('company_widget2', [c1.pk])), + '<input type="text" name="company_widget2" value="%(c1pk)s" />' % {'c1pk': c1.pk} + ) + class RelatedFieldWidgetWrapperTests(DjangoTestCase): def test_no_can_add_related(self): - rel = models.Inventory._meta.get_field('parent').rel - w = AdminRadioSelect() + rel = models.Individual._meta.get_field('parent').rel + w = widgets.AdminRadioSelect() # Used to fail with a name error. - w = RelatedFieldWidgetWrapper(w, rel, admin.site) + w = widgets.RelatedFieldWidgetWrapper(w, rel, widget_admin_site) self.assertFalse(w.can_add_related) diff --git a/tests/regressiontests/admin_widgets/widgetadmin.py b/tests/regressiontests/admin_widgets/widgetadmin.py index 6f15d9208b..ea738125fe 100644 --- a/tests/regressiontests/admin_widgets/widgetadmin.py +++ b/tests/regressiontests/admin_widgets/widgetadmin.py @@ -27,4 +27,14 @@ site = WidgetAdmin(name='widget-admin') site.register(models.User) site.register(models.Car, CarAdmin) site.register(models.CarTire, CarTireAdmin) + +site.register(models.Member) +site.register(models.Band) site.register(models.Event, EventAdmin) +site.register(models.Album) + +site.register(models.Inventory) + +site.register(models.Bee) + +site.register(models.Advisor) |
