summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--django/contrib/admin/options.py6
-rw-r--r--django/contrib/admin/widgets.py51
-rw-r--r--tests/regressiontests/admin_widgets/models.py32
-rw-r--r--tests/regressiontests/admin_widgets/tests.py92
-rw-r--r--tests/regressiontests/admin_widgets/widgetadmin.py10
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'&amp;'.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'&amp;'.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>&nbsp;<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>&nbsp;<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>&nbsp;<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>&nbsp;<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" />&nbsp;<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" />&nbsp;<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>&nbsp;<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>&nbsp;<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)