Ticket #8054: 8054-list-column.diff
File 8054-list-column.diff, 17.0 KB (added by , 16 years ago) |
---|
-
django/contrib/admin/validation.py
7 7 from django.db import models 8 8 from django.forms.models import BaseModelForm, BaseModelFormSet, fields_for_model 9 9 from django.contrib.admin.options import flatten_fieldsets, BaseModelAdmin 10 from django.contrib.admin.options import ListColumn 10 11 from django.contrib.admin.options import HORIZONTAL, VERTICAL 11 12 12 13 def validate(cls, model): … … 35 36 if hasattr(cls, 'list_display'): 36 37 _check_istuplew('list_display', cls.list_display) 37 38 for idx, field in enumerate(cls.list_display): 38 f = _check_attr_existsw("list_display[%d]" % idx, field) 39 if isinstance(field, ListColumn): 40 f = _check_attr_existsw("list_display[%d]" % idx, field.field_name) 41 else: 42 f = _check_attr_existsw("list_display[%d]" % idx, field) 39 43 if isinstance(f, models.ManyToManyField): 40 44 raise ImproperlyConfigured("`%s.list_display[%d]`, `%s` is a " 41 45 "ManyToManyField which is not supported." -
django/contrib/admin/options.py
9 9 from django.db import models, transaction 10 10 from django.http import Http404, HttpResponse, HttpResponseRedirect 11 11 from django.shortcuts import get_object_or_404, render_to_response 12 from django.utils.html import escape 12 from django.utils.html import escape, conditional_escape 13 13 from django.utils.safestring import mark_safe 14 from django.utils.encoding import smart_unicode, smart_str, force_unicode 14 15 from django.utils.text import capfirst, get_text_list 15 16 from django.utils.translation import ugettext as _ 16 17 from django.utils.encoding import force_unicode … … 905 906 self.extend(inline_formset.non_form_errors()) 906 907 for errors_in_inline_form in inline_formset.errors: 907 908 self.extend(errors_in_inline_form.values()) 909 910 911 class ListColumn(object): 912 def __init__(self, field_name, header=None, filter='', load_filters=[], order_field=None, value_map=None): 913 self.field_name = field_name 914 self.header = header 915 self.filter = filter 916 self.load_filters = load_filters 917 self.order_field = order_field 918 self.value_map = value_map 919 self._nowrap = False 920 921 def for_model(self, model): 922 """Create a new ListColumn instance where unset properties in this 923 ListColumn have been filled in by inspecting the Model provided.""" 924 925 try: 926 f = model._meta.get_field(self.field_name) 927 except models.FieldDoesNotExist: 928 return AttributeListColumn(model, self.field_name, 929 header=self.header, 930 filter=self.filter, 931 load_filters=self.load_filters, 932 order_field=self.order_field 933 ) 934 else: 935 return FieldListColumn(f, self.field_name, 936 header=self.header, 937 filter=self.filter, 938 load_filters=self.load_filters, 939 order_field=self.order_field 940 ) 941 942 def get_value_display(self, result): 943 from django.template import Context, Parser, get_library 944 field_value = self.get_value(result) 945 if self.value_map: 946 field_value = self.value_map.get(field_value, None) 947 948 if field_value is None: 949 return EMPTY_CHANGELIST_VALUE 950 if field_value == '': 951 return mark_safe(' ') 952 953 # apply filters by faking a template token 954 if self.filter: 955 p = Parser([]) 956 for lib in ['django.contrib.admin.templatetags.admin_list'] + self.load_filters: 957 p.add_library(get_library(lib)) 958 fe = p.compile_filter('val|' + self.filter) 959 return fe.resolve(Context({'val': field_value})) 960 961 return smart_unicode(field_value) 962 963 964 class FieldListColumn(ListColumn): 965 def __init__(self, field, *args, **kwargs): 966 super(FieldListColumn, self).__init__(*args, **kwargs) 967 self.field = field 968 969 if not self.header: 970 self.header = field.verbose_name 971 972 if isinstance(field.rel, models.ManyToOneRel): 973 if field.null: 974 self.order_field = None 975 elif not self.order_field: 976 self.order_field = self.field_name 977 978 if not self.filter: 979 if isinstance(field, models.DateTimeField): 980 self.filter = 'admin_datetime_format' 981 elif isinstance(field, models.DateField): 982 self.filter = 'admin_date_format' 983 elif isinstance(field, models.TimeField): 984 self.filter = 'admin_time_format' 985 elif isinstance(field, models.BooleanField) or isinstance(field, models.NullBooleanField): 986 self.filter = 'boolean_icon' 987 elif isinstance(field, models.DecimalField): 988 self.filter = 'floatformat:%d' % field.decimal_places 989 990 if isinstance(field, models.DateField) or isinstance(field, models.TimeField): 991 self._nowrap = True 992 993 if not self.value_map and field.choices: 994 self.value_map = dict(field.choices) 995 996 def get_value(self, result): 997 return getattr(result, self.field_name, None) 998 999 1000 class AttributeListColumn(ListColumn): 1001 def __init__(self, model, *args, **kwargs): 1002 super(AttributeListColumn, self).__init__(*args, **kwargs) 1003 attr = getattr(model, self.field_name, None) 1004 1005 if attr is not None and not self.filter: 1006 if getattr(attr, 'boolean', False): 1007 self.filter = 'boolean_icon' 1008 elif getattr(attr, 'allow_tags', False): 1009 self.filter = 'safe' 1010 1011 if self.header is None: 1012 if self.field_name == '__unicode__': 1013 self.header = force_unicode(model._meta.verbose_name) 1014 elif self.field_name == '__str__': 1015 self.header = smart_str(model._meta.verbose_name) 1016 elif attr is not None: 1017 try: 1018 self.header = attr.short_description 1019 except AttributeError: 1020 pass 1021 1022 if not self.header: 1023 self.header = self.field_name.replace('_', ' ') 1024 1025 if attr is not None and not self.order_field: 1026 self.order_field = getattr(attr, "admin_order_field", None) 1027 1028 def get_value(self, result): 1029 attr = getattr(result, self.field_name, None) 1030 if callable(attr): 1031 return attr() 1032 return attr -
django/contrib/admin/__init__.py
1 1 from django.contrib.admin.options import ModelAdmin, HORIZONTAL, VERTICAL 2 2 from django.contrib.admin.options import StackedInline, TabularInline 3 from django.contrib.admin.options import ListColumn 3 4 from django.contrib.admin.sites import AdminSite, site 4 5 5 6 def autodiscover(): -
django/contrib/admin/templatetags/admin_list.py
1 1 from django.conf import settings 2 from django.contrib.admin.options import ListColumn 2 3 from django.contrib.admin.views.main import ALL_VAR, EMPTY_CHANGELIST_VALUE 3 4 from django.contrib.admin.views.main import ORDER_VAR, ORDER_TYPE_VAR, PAGE_VAR, SEARCH_VAR 4 5 from django.core.exceptions import ObjectDoesNotExist … … 68 69 } 69 70 pagination = register.inclusion_tag('admin/pagination.html')(pagination) 70 71 71 def result_headers(cl):72 lookup_opts = cl.lookup_opts73 72 74 for i, field_name in enumerate(cl.list_display): 75 try: 76 f = lookup_opts.get_field(field_name) 77 admin_order_field = None 78 except models.FieldDoesNotExist: 79 # For non-field list_display values, check for the function 80 # attribute "short_description". If that doesn't exist, fall back 81 # to the method name. And __str__ and __unicode__ are special-cases. 82 if field_name == '__unicode__': 83 header = force_unicode(lookup_opts.verbose_name) 84 elif field_name == '__str__': 85 header = smart_str(lookup_opts.verbose_name) 86 else: 87 attr = getattr(cl.model, field_name) # Let AttributeErrors propagate. 88 try: 89 header = attr.short_description 90 except AttributeError: 91 header = field_name.replace('_', ' ') 73 def boolean_icon(field_val): 74 if field_val is None: 75 v = 'unknown' 76 else: 77 v = field_val and 'yes' or 'no' 78 return mark_safe(u'<img src="%simg/admin/icon-%s.gif" alt="%s" />' % (settings.ADMIN_MEDIA_PREFIX, v, field_val)) 79 boolean_icon = register.filter(boolean_icon) 92 80 93 # It is a non-field, but perhaps one that is sortable94 admin_order_field = getattr(getattr(cl.model, field_name), "admin_order_field", None)95 if not admin_order_field:96 yield {"text": header}97 continue98 81 99 # So this _is_ a sortable non-field. Go to the yield 100 # after the else clause. 101 else: 102 if isinstance(f.rel, models.ManyToOneRel) and f.null: 103 yield {"text": f.verbose_name} 104 continue 105 else: 106 header = f.verbose_name 82 def admin_datetime_format(field_val): 83 (date_format, datetime_format, time_format) = get_date_formats() 84 return capfirst(dateformat.format(field_val, datetime_format)) 85 admin_datetime_format = register.filter(admin_datetime_format) 107 86 108 th_classes = []109 new_order_type = 'asc'110 if field_name == cl.order_field or admin_order_field == cl.order_field:111 th_classes.append('sorted %sending' % cl.order_type.lower())112 new_order_type = {'asc': 'desc', 'desc': 'asc'}[cl.order_type.lower()]113 87 114 yield {"text": header, 115 "sortable": True,116 "url": cl.get_query_string({ORDER_VAR: i, ORDER_TYPE_VAR: new_order_type}),117 "class_attrib": mark_safe(th_classes and ' class="%s"' % ' '.join(th_classes) or '')} 88 def admin_date_format(field_val): 89 (date_format, datetime_format, time_format) = get_date_formats() 90 return capfirst(dateformat.format(field_val, date_format)) 91 admin_date_format = register.filter(admin_date_format) 118 92 119 def _boolean_icon(field_val):120 BOOLEAN_MAPPING = {True: 'yes', False: 'no', None: 'unknown'}121 return mark_safe(u'<img src="%simg/admin/icon-%s.gif" alt="%s" />' % (settings.ADMIN_MEDIA_PREFIX, BOOLEAN_MAPPING[field_val], field_val))122 93 123 def items_for_result(cl, result): 124 first = True 125 pk = cl.lookup_opts.pk.attname 126 for field_name in cl.list_display: 127 row_class = '' 128 try: 129 f = cl.lookup_opts.get_field(field_name) 130 except models.FieldDoesNotExist: 131 # For non-field list_display values, the value is either a method 132 # or a property. 133 try: 134 attr = getattr(result, field_name) 135 allow_tags = getattr(attr, 'allow_tags', False) 136 boolean = getattr(attr, 'boolean', False) 137 if callable(attr): 138 attr = attr() 139 if boolean: 140 allow_tags = True 141 result_repr = _boolean_icon(attr) 142 else: 143 result_repr = smart_unicode(attr) 144 except (AttributeError, ObjectDoesNotExist): 145 result_repr = EMPTY_CHANGELIST_VALUE 146 else: 147 # Strip HTML tags in the resulting text, except if the 148 # function has an "allow_tags" attribute set to True. 149 if not allow_tags: 150 result_repr = escape(result_repr) 151 else: 152 result_repr = mark_safe(result_repr) 153 else: 154 field_val = getattr(result, f.attname) 94 def admin_time_format(field_val): 95 (date_format, datetime_format, time_format) = get_date_formats() 96 return capfirst(dateformat.format(field_val, time_format)) 97 admin_time_format = register.filter(admin_time_format) 155 98 156 if isinstance(f.rel, models.ManyToOneRel):157 if field_val is not None:158 result_repr = escape(getattr(result, f.name))159 else:160 result_repr = EMPTY_CHANGELIST_VALUE161 # Dates and times are special: They're formatted in a certain way.162 elif isinstance(f, models.DateField) or isinstance(f, models.TimeField):163 if field_val:164 (date_format, datetime_format, time_format) = get_date_formats()165 if isinstance(f, models.DateTimeField):166 result_repr = capfirst(dateformat.format(field_val, datetime_format))167 elif isinstance(f, models.TimeField):168 result_repr = capfirst(dateformat.time_format(field_val, time_format))169 else:170 result_repr = capfirst(dateformat.format(field_val, date_format))171 else:172 result_repr = EMPTY_CHANGELIST_VALUE173 row_class = ' class="nowrap"'174 # Booleans are special: We use images.175 elif isinstance(f, models.BooleanField) or isinstance(f, models.NullBooleanField):176 result_repr = _boolean_icon(field_val)177 # DecimalFields are special: Zero-pad the decimals.178 elif isinstance(f, models.DecimalField):179 if field_val is not None:180 result_repr = ('%%.%sf' % f.decimal_places) % field_val181 else:182 result_repr = EMPTY_CHANGELIST_VALUE183 # Fields with choices are special: Use the representation184 # of the choice.185 elif f.choices:186 result_repr = dict(f.choices).get(field_val, EMPTY_CHANGELIST_VALUE)187 else:188 result_repr = escape(field_val)189 if force_unicode(result_repr) == '':190 result_repr = mark_safe(' ')191 # If list_display_links not defined, add the link tag to the first field192 if (first and not cl.list_display_links) or field_name in cl.list_display_links:193 table_tag = {True:'th', False:'td'}[first]194 first = False195 url = cl.url_for_result(result)196 # Convert the pk to something that can be used in Javascript.197 # Problem cases are long ints (23L) and non-ASCII strings.198 result_id = repr(force_unicode(getattr(result, pk)))[1:]199 yield mark_safe(u'<%s%s><a href="%s"%s>%s</a></%s>' % \200 (table_tag, row_class, url, (cl.is_popup and ' onclick="opener.dismissRelatedLookupPopup(window, %s); return false;"' % result_id or ''), conditional_escape(result_repr), table_tag))201 else:202 yield mark_safe(u'<td%s>%s</td>' % (row_class, conditional_escape(result_repr)))203 99 204 def results(cl): 205 for res in cl.result_list: 206 yield list(items_for_result(cl,res)) 100 def result_headers(cl, columns): 101 for i, col in enumerate(columns): 102 if col.order_field is None: 103 yield {'text': col.header} 104 continue 207 105 106 th_classes = [] 107 new_order_type = 'asc' 108 if col.order_field == cl.order_field: 109 th_classes.append('sorted %sending' % cl.order_type.lower()) 110 new_order_type = {'asc': 'desc', 'desc': 'asc'}[cl.order_type.lower()] 111 112 yield {"text": col.header, 113 "sortable": True, 114 "url": cl.get_query_string({ORDER_VAR: i, ORDER_TYPE_VAR: new_order_type}), 115 "class_attrib": mark_safe(th_classes and ' class="%s"' % ' '.join(th_classes) or '')} 116 117 118 def items_for_result(cl, columns, result): 119 pk = cl.lookup_opts.pk.attname 120 for i, col in enumerate(columns): 121 result_repr = col.get_value_display(result) 122 row_class = col._nowrap and ' class="nowrap"' or '' 123 first = i == 0 124 # If list_display_links not defined, add the link tag to the first field 125 if (first and not cl.list_display_links) or col.field_name in cl.list_display_links: 126 table_tag = {True:'th', False:'td'}[first] 127 url = cl.url_for_result(result) 128 # Convert the pk to something that can be used in Javascript. 129 # Problem cases are long ints (23L) and non-ASCII strings. 130 result_id = repr(force_unicode(getattr(result, pk)))[1:] 131 yield mark_safe(u'<%s%s><a href="%s"%s>%s</a></%s>' % \ 132 (table_tag, row_class, url, (cl.is_popup and ' onclick="opener.dismissRelatedLookupPopup(window, %s); return false;"' % result_id or ''), conditional_escape(result_repr), table_tag)) 133 else: 134 yield mark_safe(u'<td%s>%s</td>' % (row_class, conditional_escape(result_repr))) 135 136 208 137 def result_list(cl): 138 cols = [] 139 for col in cl.list_display: 140 if isinstance(col, ListColumn): 141 cols += [col.for_model(cl.model)] 142 else: 143 cols += [ListColumn(col).for_model(cl.model)] 209 144 return {'cl': cl, 210 'result_headers': list(result_headers(cl )),211 'results': list(results(cl))}145 'result_headers': list(result_headers(cl, cols)), 146 'results': [items_for_result(cl, cols, r) for r in cl.result_list]} 212 147 result_list = register.inclusion_tag("admin/change_list_results.html")(result_list) 213 148 149 214 150 def date_hierarchy(cl): 215 151 if cl.date_hierarchy: 216 152 field_name = cl.date_hierarchy