Ticket #5863: nested_list_display.diff

File nested_list_display.diff, 6.8 KB (added by graham@…, 14 years ago)

Allow double underscore style access to related object fields in list_display

  • django/contrib/admin/templatetags/admin_list.py

     
    7373   
    7474    for i, field_name in enumerate(cl.list_display):
    7575        attr = None
     76        nested_name = field_name.split('__')
    7677        try:
    77             f = lookup_opts.get_field(field_name)
     78            f = lookup_opts.get_field(nested_name[0])
    7879            admin_order_field = None
    7980        except models.FieldDoesNotExist:
    8081            # For non-field list_display values, check for the function
     
    142143    pk = cl.lookup_opts.pk.attname
    143144    for field_name in cl.list_display:
    144145        row_class = ''
     146        nested_name = field_name.split('__')
    145147        try:
    146             f = cl.lookup_opts.get_field(field_name)
     148            f = cl.lookup_opts.get_field(nested_name[0])
    147149        except models.FieldDoesNotExist:
    148150            # For non-field list_display values, the value is either a method,
    149151            # property or returned via a callable.
     
    179181                    result_repr = mark_safe(result_repr)
    180182        else:
    181183            field_val = getattr(result, f.attname)
    182 
    183             if isinstance(f.rel, models.ManyToOneRel):
    184                 if field_val is not None:
    185                     result_repr = escape(getattr(result, f.name))
    186                 else:
    187                     result_repr = EMPTY_CHANGELIST_VALUE
    188             # Dates and times are special: They're formatted in a certain way.
    189             elif isinstance(f, models.DateField) or isinstance(f, models.TimeField):
    190                 if field_val:
    191                     (date_format, datetime_format, time_format) = get_date_formats()
    192                     if isinstance(f, models.DateTimeField):
    193                         result_repr = capfirst(dateformat.format(field_val, datetime_format))
    194                     elif isinstance(f, models.TimeField):
    195                         result_repr = capfirst(dateformat.time_format(field_val, time_format))
    196                     else:
    197                         result_repr = capfirst(dateformat.format(field_val, date_format))
    198                 else:
    199                     result_repr = EMPTY_CHANGELIST_VALUE
    200                 row_class = ' class="nowrap"'
    201             # Booleans are special: We use images.
    202             elif isinstance(f, models.BooleanField) or isinstance(f, models.NullBooleanField):
    203                 result_repr = _boolean_icon(field_val)
    204             # DecimalFields are special: Zero-pad the decimals.
    205             elif isinstance(f, models.DecimalField):
    206                 if field_val is not None:
    207                     result_repr = ('%%.%sf' % f.decimal_places) % field_val
    208                 else:
    209                     result_repr = EMPTY_CHANGELIST_VALUE
    210             # Fields with choices are special: Use the representation
    211             # of the choice.
    212             elif f.choices:
    213                 result_repr = dict(f.choices).get(field_val, EMPTY_CHANGELIST_VALUE)
    214             else:
    215                 result_repr = escape(field_val)
     184            result_repr = field_item(cl, result, nested_name, f)
    216185        if force_unicode(result_repr) == '':
    217186            result_repr = mark_safe(' ')
    218187        # If list_display_links not defined, add the link tag to the first field
     
    232201        else:
    233202            yield mark_safe(u'<td%s>%s</td>' % (row_class, conditional_escape(result_repr)))
    234203
     204def field_item(cl, result, nested_name, f):
     205    field_val = getattr(result, f.attname)
     206
     207    if isinstance(f.rel, models.ManyToOneRel):
     208        if field_val is not None:
     209            val = getattr(result, f.name)
     210            if len(nested_name) == 1:
     211                result_repr = escape(val)
     212            else:
     213                f = val._meta.get_field(nested_name[1])
     214                result_repr = field_item(cl, val, nested_name[1:], f)
     215        else:
     216            result_repr = EMPTY_CHANGELIST_VALUE
     217    # Dates and times are special: They're formatted in a certain way.
     218    elif isinstance(f, models.DateField) or isinstance(f, models.TimeField):
     219        if field_val:
     220            (date_format, datetime_format, time_format) = get_date_formats()
     221            if isinstance(f, models.DateTimeField):
     222                result_repr = capfirst(dateformat.format(field_val, datetime_format))
     223            elif isinstance(f, models.TimeField):
     224                result_repr = capfirst(dateformat.time_format(field_val, time_format))
     225            else:
     226                result_repr = capfirst(dateformat.format(field_val, date_format))
     227        else:
     228            result_repr = EMPTY_CHANGELIST_VALUE
     229        row_class = ' class="nowrap"'
     230    # Booleans are special: We use images.
     231    elif isinstance(f, models.BooleanField) or isinstance(f, models.NullBooleanField):
     232        result_repr = _boolean_icon(field_val)
     233    # DecimalFields are special: Zero-pad the decimals.
     234    elif isinstance(f, models.DecimalField):
     235        if field_val is not None:
     236            result_repr = ('%%.%sf' % f.decimal_places) % field_val
     237        else:
     238            result_repr = EMPTY_CHANGELIST_VALUE
     239            # Fields with choices are special: Use the representation
     240            # of the choice.
     241    elif f.choices:
     242        result_repr = dict(f.choices).get(field_val, EMPTY_CHANGELIST_VALUE)
     243    else:
     244        result_repr = escape(field_val)
     245    return result_repr
     246
    235247def results(cl):
    236248    for res in cl.result_list:
    237249        yield list(items_for_result(cl,res))
  • tests/regressiontests/admin_views/tests.py

     
    9999        response = self.client.post('/test_admin/admin/admin_views/section/1/', post_data)
    100100        self.failUnlessEqual(response.status_code, 302) # redirect somewhere
    101101
     102    def testNestedListDisplay(self):
     103        response = self.client.get('/test_admin/admin/admin_views/article/')
     104        self.failUnlessEqual(response.status_code, 200)
     105        self.assertContains(response, 'Test section')
     106
    102107def get_perm(Model, perm):
    103108    """Return the permission object, for the Model"""
    104109    ct = ContentType.objects.get_for_model(Model)
  • tests/regressiontests/admin_views/models.py

     
    2424    model = Article
    2525
    2626class ArticleAdmin(admin.ModelAdmin):
    27     list_display = ('content', 'date')
     27    list_display = ('content', 'date', 'section__name')
    2828    list_filter = ('date',)
    2929
    3030    def changelist_view(self, request):
Back to Top