Ticket #16542: 16542.2.diff

File 16542.2.diff, 20.5 KB (added by ramiro, 4 years ago)
  • django/contrib/admin/options.py

    diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
    a b  
    154154        """
    155155        db = kwargs.get('using')
    156156        if db_field.name in self.raw_id_fields:
    157             kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel, using=db)
     157            kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel,
     158                                    self.admin_site, using=db)
    158159        elif db_field.name in self.radio_fields:
    159160            kwargs['widget'] = widgets.AdminRadioSelect(attrs={
    160161                'class': get_ul_class(self.radio_fields[db_field.name]),
     
    174175        db = kwargs.get('using')
    175176
    176177        if db_field.name in self.raw_id_fields:
    177             kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel, using=db)
     178            kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel,
     179                                    self.admin_site, using=db)
    178180            kwargs['help_text'] = ''
    179181        elif db_field.name in (list(self.filter_vertical) + list(self.filter_horizontal)):
    180182            kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical))
  • django/contrib/admin/widgets.py

    diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py
    a b  
    112112    A Widget for displaying ForeignKeys in the "raw_id" interface rather than
    113113    in a <select> box.
    114114    """
    115     def __init__(self, rel, attrs=None, using=None):
     115    def __init__(self, rel, admin_site, attrs=None, using=None):
    116116        self.rel = rel
     117        self.admin_site = admin_site
    117118        self.db = using
    118119        super(ForeignKeyRawIdWidget, self).__init__(attrs)
    119120
    120121    def render(self, name, value, attrs=None):
     122        rel_to = self.rel.to
    121123        if attrs is None:
    122124            attrs = {}
    123         related_url = '../../../%s/%s/' % (self.rel.to._meta.app_label, self.rel.to._meta.object_name.lower())
    124         params = self.url_parameters()
    125         if params:
    126             url = u'?' + u'&amp;'.join([u'%s=%s' % (k, v) for k, v in params.items()])
    127         else:
    128             url = u''
    129         if "class" not in attrs:
    130             attrs['class'] = 'vForeignKeyRawIdAdminField' # The JavaScript looks for this hook.
    131         output = [super(ForeignKeyRawIdWidget, self).render(name, value, attrs)]
    132         # TODO: "id_" is hard-coded here. This should instead use the correct
    133         # API to determine the ID dynamically.
    134         output.append(u'<a href="%s%s" class="related-lookup" id="lookup_id_%s" onclick="return showRelatedObjectLookupPopup(this);"> '
    135                       % (related_url, url, name))
    136         output.append(u'<img src="%s" width="16" height="16" alt="%s" /></a>'
    137                       % (static('admin/img/selector-search.gif'), _('Lookup')))
     125        extra = []
     126        if rel_to in self.admin_site._registry: # If the related object has an admin interface:
     127            try:
     128                related_url = reverse('admin:%s_%s_changelist' %
     129                                        (rel_to._meta.app_label,
     130                                        rel_to._meta.module_name),
     131                                        current_app=self.admin_site.name)
     132            except NoReverseMatch:
     133                raise
     134
     135            params = self.url_parameters()
     136            if params:
     137                url = u'?' + u'&amp;'.join([u'%s=%s' % (k, v) for k, v in params.items()])
     138            else:
     139                url = u''
     140            if "class" not in attrs:
     141                attrs['class'] = 'vForeignKeyRawIdAdminField' # The JavaScript code looks for this hook.
     142            # TODO: "lookup_id_" is hard-coded here. This should instead use
     143            # the correct API to determine the ID dynamically.
     144            extra.append(u'<a href="%s%s" class="related-lookup" id="lookup_id_%s" onclick="return showRelatedObjectLookupPopup(this);"> '
     145                            % (related_url, url, name))
     146            extra.append(u'<img src="%s" width="16" height="16" alt="%s" /></a>'
     147                            % (static('admin/img/selector-search.gif'), _('Lookup')))
     148        output = [super(ForeignKeyRawIdWidget, self).render(name, value, attrs)] + extra
    138149        if value:
    139150            output.append(self.label_for_value(value))
    140151        return mark_safe(u''.join(output))
     
    164175    def render(self, name, value, attrs=None):
    165176        if attrs is None:
    166177            attrs = {}
    167         attrs['class'] = 'vManyToManyRawIdAdminField'
     178        if self.rel.to in self.admin_site._registry: # If the related object has an admin interface:
     179            attrs['class'] = 'vManyToManyRawIdAdminField'
    168180        if value:
    169181            value = ','.join([force_unicode(v) for v in value])
    170182        else:
     
    232244        output = [self.widget.render(name, value, *args, **kwargs)]
    233245        if self.can_add_related:
    234246            related_url = reverse('admin:%s_%s_add' % info, current_app=self.admin_site.name)
    235             # TODO: "id_" is hard-coded here. This should instead use the correct
    236             # API to determine the ID dynamically.
     247            # TODO: "add_id_" is hard-coded here. This should instead use the
     248            # correct API to determine the ID dynamically.
    237249            output.append(u'<a href="%s" class="add-another" id="add_id_%s" onclick="return showAddAnotherPopup(this);"> '
    238250                          % (related_url, name))
    239251            output.append(u'<img src="%s" width="10" height="10" alt="%s"/></a>'
  • tests/regressiontests/admin_widgets/models.py

    diff --git a/tests/regressiontests/admin_widgets/models.py b/tests/regressiontests/admin_widgets/models.py
    a b  
    6767    A single car tire. This to test that a user can only select their own cars.
    6868    """
    6969    car = models.ForeignKey(Car)
     70
     71class Honeycomb(models.Model):
     72    location = models.CharField(max_length=20)
     73
     74class Bee(models.Model):
     75    """
     76    A model with a FK to a model that won't be registered with the admin
     77    (Honeycomb) so the corresponding raw ID widget won't have a magnifying
     78    glass link to select related honeycomb instances.
     79    """
     80    honeycomb = models.ForeignKey(Honeycomb)
     81
     82class Individual(models.Model):
     83    """
     84    A model with a FK to itself. It won't be registered with the admin, so the
     85    corresponding raw ID widget won't have a magnifying glass link to select
     86    related instances (rendering will be called programmatically in this case).
     87    """
     88    name = models.CharField(max_length=20)
     89    parent = models.ForeignKey('self', null=True)
     90
     91class Company(models.Model):
     92    name = models.CharField(max_length=20)
     93
     94class Advisor(models.Model):
     95    """
     96    A model with a m2m to a model that won't be registered with the admin
     97    (Company) so the corresponding raw ID widget won't have a magnifying
     98    glass link to select related company instances.
     99    """
     100    name = models.CharField(max_length=20)
     101    companies = models.ManyToManyField(Company)
  • tests/regressiontests/admin_widgets/tests.py

    diff --git a/tests/regressiontests/admin_widgets/tests.py b/tests/regressiontests/admin_widgets/tests.py
    a b  
    77from django.conf import settings
    88from django.contrib import admin
    99from django.contrib.admin import widgets
    10 from django.contrib.admin.widgets import (FilteredSelectMultiple,
    11     AdminSplitDateTime, AdminFileWidget, ForeignKeyRawIdWidget, AdminRadioSelect,
    12     RelatedFieldWidgetWrapper, ManyToManyRawIdWidget,
    13     url_params_from_lookup_dict)
    1410from django.core.files.storage import default_storage
    1511from django.core.files.uploadedfile import SimpleUploadedFile
    1612from django.db.models import DateField
     
    2016from django.utils.unittest import TestCase
    2117
    2218import models
     19from widgetadmin import site as widget_admin_site
    2320
    2421admin_media_prefix = lambda: {
    2522    'ADMIN_MEDIA_PREFIX': "%sadmin/" % settings.STATIC_URL,
     
    186183                'Select a valid choice. That choice is not one of the available choices.')
    187184
    188185    def test_url_params_from_lookup_dict_any_iterable(self):
    189         lookup1 = url_params_from_lookup_dict({'color__in': ('red', 'blue')})
    190         lookup2 = url_params_from_lookup_dict({'color__in': ['red', 'blue']})
     186        lookup1 = widgets.url_params_from_lookup_dict({'color__in': ('red', 'blue')})
     187        lookup2 = widgets.url_params_from_lookup_dict({'color__in': ['red', 'blue']})
    191188        self.assertEqual(lookup1, {'color__in': 'red,blue'})
    192189        self.assertEqual(lookup1, lookup2)
    193190
    194191
    195192class FilteredSelectMultipleWidgetTest(DjangoTestCase):
    196193    def test_render(self):
    197         w = FilteredSelectMultiple('test', False)
     194        w = widgets.FilteredSelectMultiple('test', False)
    198195        self.assertEqual(
    199196            conditional_escape(w.render('test', 'test')),
    200197            '<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()
    201198        )
    202199
    203200    def test_stacked_render(self):
    204         w = FilteredSelectMultiple('test', True)
     201        w = widgets.FilteredSelectMultiple('test', True)
    205202        self.assertEqual(
    206203            conditional_escape(w.render('test', 'test')),
    207204            '<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()
     
    210207
    211208class AdminSplitDateTimeWidgetTest(DjangoTestCase):
    212209    def test_render(self):
    213         w = AdminSplitDateTime()
     210        w = widgets.AdminSplitDateTime()
    214211        self.assertEqual(
    215212            conditional_escape(w.render('test', datetime(2007, 12, 1, 9, 30))),
    216213            '<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>',
    217214        )
    218215
    219216    def test_localization(self):
    220         w = AdminSplitDateTime()
     217        w = widgets.AdminSplitDateTime()
    221218
    222219        with self.settings(USE_L10N=True):
    223220            with translation.override('de-at'):
     
    235232            name='Hybrid Theory', cover_art=r'albums\hybrid_theory.jpg'
    236233        )
    237234
    238         w = AdminFileWidget()
     235        w = widgets.AdminFileWidget()
    239236        self.assertEqual(
    240237            conditional_escape(w.render('test', album.cover_art)),
    241238            '<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('') },
     
    255252        )
    256253        rel = models.Album._meta.get_field('band').rel
    257254
    258         w = ForeignKeyRawIdWidget(rel)
     255        w = widgets.ForeignKeyRawIdWidget(rel, widget_admin_site)
    259256        self.assertEqual(
    260257            conditional_escape(w.render('test', band.pk, attrs={})),
    261             '<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),
     258            '<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)
    262259        )
    263260
    264261    def test_relations_to_non_primary_key(self):
     
    270267            barcode=87, name='Core', parent=apple
    271268        )
    272269        rel = models.Inventory._meta.get_field('parent').rel
    273         w = ForeignKeyRawIdWidget(rel)
     270        w = widgets.ForeignKeyRawIdWidget(rel, widget_admin_site)
    274271        self.assertEqual(
    275272            w.render('test', core.parent_id, attrs={}),
    276             '<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(),
     273            '<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()
    277274        )
    278275
     276    def test_fk_related_model_not_in_admin(self):
     277        # FK to a model not registered with admin site. Raw ID widget shoud
     278        # have no magnifying glass link.
     279        big_honeycomb = models.Honeycomb.objects.create(location='Old tree')
     280        big_honeycomb.bee_set.create()
     281        rel = models.Bee._meta.get_field('honeycomb').rel
     282
     283        w = widgets.ForeignKeyRawIdWidget(rel, widget_admin_site)
     284        self.assertEqual(
     285            conditional_escape(w.render('honeycomb_widget', big_honeycomb.pk, attrs={})),
     286            '<input type="text" name="honeycomb_widget" value="%(hcombpk)s" />&nbsp;<strong>Honeycomb object</strong>' % {'hcombpk': big_honeycomb.pk}
     287        )
     288
     289    def test_fk_to_self_model_not_in_admin(self):
     290        # FK to self, not registered with admin site. Raw ID widget shoud have
     291        # no magnifying glass link.
     292        subject1 = models.Individual.objects.create(name='Subject #1')
     293        models.Individual.objects.create(name='Child', parent=subject1)
     294        rel = models.Individual._meta.get_field('parent').rel
     295
     296        w = widgets.ForeignKeyRawIdWidget(rel, widget_admin_site)
     297        self.assertEqual(
     298            conditional_escape(w.render('individual_widget', subject1.pk, attrs={})),
     299            '<input type="text" name="individual_widget" value="%(subj1pk)s" />&nbsp;<strong>Individual object</strong>' % {'subj1pk': subject1.pk}
     300        )
    279301
    280302    def test_proper_manager_for_label_lookup(self):
    281303        # see #9258
    282304        rel = models.Inventory._meta.get_field('parent').rel
    283         w = ForeignKeyRawIdWidget(rel)
     305        w = widgets.ForeignKeyRawIdWidget(rel, widget_admin_site)
    284306
    285307        hidden = models.Inventory.objects.create(
    286308            barcode=93, name='Hidden', hidden=True
     
    290312        )
    291313        self.assertEqual(
    292314            w.render('test', child_of_hidden.parent_id, attrs={}),
    293             '<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(),
     315            '<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()
    294316        )
    295317
    296318
    297319class ManyToManyRawIdWidgetTest(DjangoTestCase):
    298320    def test_render(self):
    299321        band = models.Band.objects.create(name='Linkin Park')
    300         band.album_set.create(
    301             name='Hybrid Theory', cover_art=r'albums\hybrid_theory.jpg'
    302         )
    303322
    304323        m1 = models.Member.objects.create(name='Chester')
    305324        m2 = models.Member.objects.create(name='Mike')
    306325        band.members.add(m1, m2)
    307326        rel = models.Band._meta.get_field('members').rel
    308327
    309         w = ManyToManyRawIdWidget(rel)
     328        w = widgets.ManyToManyRawIdWidget(rel, widget_admin_site)
    310329        self.assertEqual(
    311330            conditional_escape(w.render('test', [m1.pk, m2.pk], attrs={})),
    312             '<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),
     331            '<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)
    313332        )
    314333
    315334        self.assertEqual(
    316335            conditional_escape(w.render('test', [m1.pk])),
    317             '<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),
     336            '<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)
    318337        )
    319338
    320339        self.assertEqual(w._has_changed(None, None), False)
     
    324343        self.assertEqual(w._has_changed([1, 2], [u'1']), True)
    325344        self.assertEqual(w._has_changed([1, 2], [u'1', u'3']), True)
    326345
     346    def test_m2m_related_model_not_in_admin(self):
     347        # M2M relatinship with model not registered with admin site. Raw ID
     348        # widget shoud have no magnifying glass link.
     349        consultor1 = models.Advisor.objects.create(name='Rockstar Techie')
     350
     351        c1 = models.Company.objects.create(name='Doodle')
     352        c2 = models.Company.objects.create(name='Pear')
     353        consultor1.companies.add(c1, c2)
     354        rel = models.Advisor._meta.get_field('companies').rel
     355
     356        w = widgets.ManyToManyRawIdWidget(rel, widget_admin_site)
     357        self.assertEqual(
     358            conditional_escape(w.render('company_widget1', [c1.pk, c2.pk], attrs={})),
     359            '<input type="text" name="company_widget1" value="%(c1pk)s,%(c2pk)s" />' % {'c1pk': c1.pk, 'c2pk': c2.pk}
     360        )
     361
     362        self.assertEqual(
     363            conditional_escape(w.render('company_widget2', [c1.pk])),
     364            '<input type="text" name="company_widget2" value="%(c1pk)s" />' % {'c1pk': c1.pk}
     365        )
     366
    327367class RelatedFieldWidgetWrapperTests(DjangoTestCase):
    328368    def test_no_can_add_related(self):
    329         rel = models.Inventory._meta.get_field('parent').rel
    330         w = AdminRadioSelect()
     369        rel = models.Individual._meta.get_field('parent').rel
     370        w = widgets.AdminRadioSelect()
    331371        # Used to fail with a name error.
    332         w = RelatedFieldWidgetWrapper(w, rel, admin.site)
     372        w = widgets.RelatedFieldWidgetWrapper(w, rel, widget_admin_site)
    333373        self.assertFalse(w.can_add_related)
  • tests/regressiontests/admin_widgets/widgetadmin.py

    diff --git a/tests/regressiontests/admin_widgets/widgetadmin.py b/tests/regressiontests/admin_widgets/widgetadmin.py
    a b  
    2727site.register(models.User)
    2828site.register(models.Car, CarAdmin)
    2929site.register(models.CarTire, CarTireAdmin)
     30
     31site.register(models.Member)
     32site.register(models.Band)
    3033site.register(models.Event, EventAdmin)
     34site.register(models.Album)
     35
     36site.register(models.Inventory)
     37
     38site.register(models.Bee)
     39
     40site.register(models.Advisor)
Back to Top