Code

Ticket #11058: ticket-11058-list_display_links-1.3.patch

File ticket-11058-list_display_links-1.3.patch, 7.1 KB (added by acdha, 3 years ago)
  • django/contrib/admin/validation.py

    From bb052c2234aa9f59ffde62d39b8ea04449d119d0 Mon Sep 17 00:00:00 2001
    From: Chris Adams <chris@improbable.org>
    Date: Wed, 2 Feb 2011 10:22:55 -0500
    Subject: [PATCH] ModelAdmin: allow non-model fields in list_display_links (See #11058)
    
    In addition to model field names, list_display accepts callables, ModelAdmin
    attributes or Model attributes.
    
    This patch reuses the list_display resolution for these values for
    list_display_links validation as well and adds a test for each of the three
    extra types.
    
    See http://code.djangoproject.com/ticket/11058
    ---
     django/contrib/admin/validation.py              |   20 ++++++++++++++++----
     docs/ref/contrib/admin/index.txt                |    6 +++---
     tests/regressiontests/admin_validation/tests.py |   22 ++++++++++++++++++++++
     tests/regressiontests/modeladmin/tests.py       |    4 ++--
     4 files changed, 43 insertions(+), 9 deletions(-)
    
    diff --git a/django/contrib/admin/validation.py b/django/contrib/admin/validation.py
    index 027db63..deca4b8 100644
    a b def validate(cls, model): 
    2323    opts = model._meta 
    2424    validate_base(cls, model) 
    2525 
     26    # To handle values other than pure strings, we'll maintain a list of 
     27    # possible values allowing the possibility of using ModelAdmin fields 
     28    # or arbitrary callables in list_display_links. 
     29    list_display_fields = set() 
     30 
    2631    # list_display 
    2732    if hasattr(cls, 'list_display'): 
    2833        check_isseq(cls, 'list_display', cls.list_display) 
    2934        for idx, field in enumerate(cls.list_display): 
     35            list_display_fields.add(field) 
     36 
    3037            if not callable(field): 
    3138                if not hasattr(cls, field): 
    3239                    if not hasattr(model, field): 
    3340                        try: 
    34                             opts.get_field(field) 
     41                            f = opts.get_field(field) 
    3542                        except models.FieldDoesNotExist: 
    3643                            raise ImproperlyConfigured("%s.list_display[%d], %r is not a callable or an attribute of %r or found in the model %r." 
    3744                                % (cls.__name__, idx, field, cls.__name__, model._meta.object_name)) 
    3845                    else: 
    3946                        # getattr(model, field) could be an X_RelatedObjectsDescriptor 
    4047                        f = fetch_attr(cls, model, opts, "list_display[%d]" % idx, field) 
     48 
    4149                        if isinstance(f, models.ManyToManyField): 
    4250                            raise ImproperlyConfigured("'%s.list_display[%d]', '%s' is a ManyToManyField which is not supported." 
    4351                                % (cls.__name__, idx, field)) 
    4452 
     53                    list_display_fields.add(f) 
     54 
    4555    # list_display_links 
    4656    if hasattr(cls, 'list_display_links'): 
    4757        check_isseq(cls, 'list_display_links', cls.list_display_links) 
     58 
    4859        for idx, field in enumerate(cls.list_display_links): 
    49             fetch_attr(cls, model, opts, 'list_display_links[%d]' % idx, field) 
    50             if field not in cls.list_display: 
     60            if field not in list_display_fields: 
    5161                raise ImproperlyConfigured("'%s.list_display_links[%d]'" 
    52                         "refers to '%s' which is not defined in 'list_display'." 
     62                        " refers to '%s' which is not defined in 'list_display'." 
    5363                        % (cls.__name__, idx, field)) 
    5464 
     65    del list_display_fields 
     66 
    5567    # list_filter 
    5668    if hasattr(cls, 'list_filter'): 
    5769        check_isseq(cls, 'list_filter', cls.list_filter) 
  • docs/ref/contrib/admin/index.txt

    diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt
    index 2815b5d..bd1485f 100644
    a b subclass:: 
    479479    By default, the change list page will link the first column -- the first 
    480480    field specified in ``list_display`` -- to the change page for each item. 
    481481    But ``list_display_links`` lets you change which columns are linked. Set 
    482     ``list_display_links`` to a list or tuple of field names (in the same 
     482    ``list_display_links`` to a list or tuple of field (in the same 
    483483    format as ``list_display``) to link. 
    484484 
    485     ``list_display_links`` can specify one or many field names. As long as the 
    486     field names appear in ``list_display``, Django doesn't care how many (or 
     485    ``list_display_links`` can specify one or many field. As long as the 
     486    fields appear in ``list_display``, Django doesn't care how many (or 
    487487    how few) fields are linked. The only requirement is: If you want to use 
    488488    ``list_display_links``, you must define ``list_display``. 
    489489 
  • tests/regressiontests/admin_validation/tests.py

    diff --git a/tests/regressiontests/admin_validation/tests.py b/tests/regressiontests/admin_validation/tests.py
    index 1872ca5..207933b 100644
    a b class ValidationTestCase(TestCase): 
    242242 
    243243        validate(FieldsOnFormOnlyAdmin, Song) 
    244244 
     245    def test_list_display_links(self): 
     246        """ 
     247        Confirm that list_display_links supports model fields, model admin 
     248        methods and arbitrary callables 
     249        """ 
     250 
     251        def fancy_formatter(obj): 
     252            return "Custom display of %s" % obj 
    245253 
     254        class SongAdmin(admin.ModelAdmin): 
     255            foo_date = fancy_formatter 
     256 
     257            list_display = ("title", "album", "original_release", 
     258                            "readonly_method_on_model", "short_title", 
     259                            foo_date) 
     260            list_display_links = ("title", "album", "original_release", 
     261                                    "readonly_method_on_model", "short_title", 
     262                                    foo_date) 
    246263 
     264            def short_title(self, obj): 
     265                # In real code this would use truncatewords/chars: 
     266                return obj.title[:20] 
     267            short_title.short_description = "Short Title" 
    247268 
     269        validate(SongAdmin, Song) 
  • tests/regressiontests/modeladmin/tests.py

    diff --git a/tests/regressiontests/modeladmin/tests.py b/tests/regressiontests/modeladmin/tests.py
    index 042c1b7..3eeb845 100644
    a b class ValidationTests(unittest.TestCase): 
    794794 
    795795        self.assertRaisesRegexp( 
    796796            ImproperlyConfigured, 
    797             "'ValidationTestModelAdmin.list_display_links\[0\]' refers to 'non_existent_field' that is neither a field, method or property of model 'ValidationTestModel'.", 
     797            "'ValidationTestModelAdmin.list_display_links\[0\]' refers to 'non_existent_field' which is not defined in 'list_display'.", 
    798798            validate, 
    799799            ValidationTestModelAdmin, 
    800800            ValidationTestModel, 
    class ValidationTests(unittest.TestCase): 
    805805 
    806806        self.assertRaisesRegexp( 
    807807            ImproperlyConfigured, 
    808             "'ValidationTestModelAdmin.list_display_links\[0\]'refers to 'name' which is not defined in 'list_display'.", 
     808            "'ValidationTestModelAdmin.list_display_links\[0\]' refers to 'name' which is not defined in 'list_display'.", 
    809809            validate, 
    810810            ValidationTestModelAdmin, 
    811811            ValidationTestModel,