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

File ticket-11058-list_display_links-1.3.patch, 7.1 KB (added by Chris Adams, 13 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,
Back to Top