Ticket #8054: 8054-list-column.3.diff
File 8054-list-column.3.diff, 21.8 KB (added by , 14 years ago) |
---|
-
django/contrib/admin/validation.py
5 5 from django.contrib.admin.options import flatten_fieldsets, BaseModelAdmin 6 6 from django.contrib.admin.options import HORIZONTAL, VERTICAL 7 7 from django.contrib.admin.util import lookup_field 8 from django.contrib.admin.options import ListColumn 8 9 9 10 10 11 __all__ = ['validate'] … … 27 28 check_isseq(cls, 'list_display', cls.list_display) 28 29 for idx, field in enumerate(cls.list_display): 29 30 if not callable(field): 30 if not hasattr(cls, field):31 if not hasattr(model, field):31 if isinstance(field, ListColumn) or not hasattr(cls, field): 32 if not isinstance(field, ListColumn) and not hasattr(model, field): 32 33 try: 33 34 opts.get_field(field) 34 35 except models.FieldDoesNotExist: … … 36 37 % (cls.__name__, idx, field, cls.__name__, model._meta.object_name)) 37 38 else: 38 39 # getattr(model, field) could be an X_RelatedObjectsDescriptor 39 f = fetch_attr(cls, model, opts, "list_display[%d]" % idx, field) 40 if isinstance(field, ListColumn): 41 f = fetch_attr(cls, model, opts, "list_display[%d]" % idx, field.field_name) 42 else: 43 f = fetch_attr(cls, model, opts, "list_display[%d]" % idx, field) 40 44 if isinstance(f, models.ManyToManyField): 41 45 raise ImproperlyConfigured("'%s.list_display[%d]', '%s' is a ManyToManyField which is not supported." 42 46 % (cls.__name__, idx, field)) -
django/contrib/admin/options.py
9 9 from django.contrib import messages 10 10 from django.views.decorators.csrf import csrf_protect 11 11 from django.core.exceptions import PermissionDenied, ValidationError 12 from django.contrib.admin.util import label_for_field, lookup_field 13 from django.contrib.admin import views 14 from django.core.exceptions import ObjectDoesNotExist 12 15 from django.db import models, transaction 13 16 from django.db.models.fields import BLANK_CHOICE_DASH 14 17 from django.http import Http404, HttpResponse, HttpResponseRedirect … … 16 19 from django.utils.decorators import method_decorator 17 20 from django.utils.datastructures import SortedDict 18 21 from django.utils.functional import update_wrapper 22 from django.utils.encoding import smart_unicode, smart_str 19 23 from django.utils.html import escape 20 24 from django.utils.safestring import mark_safe 21 25 from django.utils.functional import curry … … 24 28 from django.utils.translation import ungettext, ugettext_lazy 25 29 from django.utils.encoding import force_unicode 26 30 31 32 class ListColumn(object): 33 def __init__(self, field_name, header=None, filter='', load_filters=[], order_field=None, value_map=None): 34 self.field_name = field_name 35 self.header = header 36 self.filter = filter 37 self.load_filters = load_filters 38 self.order_field = order_field 39 self.value_map = value_map 40 self._nowrap = False 41 42 def for_model(self, model, cl=None): 43 """Create a new ListColumn instance where unset properties in this 44 ListColumn have been filled in by inspecting the Model provided.""" 45 header, attr = label_for_field(self.field_name, model, 46 model_admin = cl.model_admin, 47 return_attr = True 48 ) 49 try: 50 f = model._meta.get_field(self.field_name) 51 except models.FieldDoesNotExist: 52 return AttributeListColumn(model, field_name=self.field_name, 53 header=self.header if self.header else header, 54 filter=self.filter, 55 load_filters=self.load_filters, 56 order_field=self.order_field, 57 attr=attr 58 ) 59 else: 60 return FieldListColumn(f, self.field_name, 61 header=self.header if self.header else header, 62 filter=self.filter, 63 load_filters=self.load_filters, 64 order_field=self.order_field 65 #attr=attr 66 ) 67 68 def get_value_display(self, result, cl=None): 69 from django.template import Context, Parser, get_library 70 #field_value = self.get_value(result) 71 # TODO: i don't know how 2 lines below is working 72 #if self.value_map: 73 # field_value = self.value_map.get(field_value, None) 74 try: 75 f, attr, field_value = lookup_field(self.field_name, result, cl.model_admin) 76 except (AttributeError, ObjectDoesNotExist): 77 field_value = views.main.EMPTY_CHANGELIST_VALUE 78 else: 79 #if field_value is None: 80 # return EMPTY_CHANGELIST_VALUE 81 82 # apply filters by faking a template token 83 if self.filter: 84 p = Parser([]) 85 for lib in ['admin_list', ] + self.load_filters: 86 p.add_library(get_library(lib)) 87 fe = p.compile_filter('val|' + self.filter) 88 return fe.resolve(Context({'val': field_value})) 89 90 if force_unicode(field_value) == '': 91 return mark_safe(' ') 92 return smart_unicode(field_value) 93 94 95 class FieldListColumn(ListColumn): 96 def __init__(self, field, *args, **kwargs): 97 super(FieldListColumn, self).__init__(*args, **kwargs) 98 self.field = field 99 100 if not self.header: 101 self.header = field.verbose_name 102 103 #if isinstance(field.rel, models.ManyToOneRel): 104 # if field.null: 105 # self.order_field = None 106 if not self.order_field: 107 self.order_field = self.field_name 108 109 if not self.filter: 110 if isinstance(field, models.DateTimeField): 111 self.filter = 'admin_datetime_format' 112 elif isinstance(field, models.DateField): 113 self.filter = 'admin_date_format' 114 elif isinstance(field, models.TimeField): 115 self.filter = 'admin_time_format' 116 elif isinstance(field, models.BooleanField) or isinstance(field, models.NullBooleanField): 117 self.filter = 'boolean_icon' 118 elif isinstance(field, models.DecimalField): 119 self.filter = 'floatformat:%d' % field.decimal_places 120 121 if isinstance(field, models.DateField) or isinstance(field, models.TimeField): 122 self._nowrap = True 123 124 if not self.value_map and field.choices: 125 self.value_map = dict(field.choices) 126 127 def get_value(self, result): 128 return getattr(result, self.field_name, None) 129 130 131 class AttributeListColumn(ListColumn): 132 def __init__(self, model, attr=None, *args, **kwargs): 133 super(AttributeListColumn, self).__init__(*args, **kwargs) 134 135 if attr is not None and not self.filter: 136 if getattr(attr, 'boolean', False): 137 self.filter = 'boolean_icon' 138 elif getattr(attr, 'allow_tags', False): 139 self.filter = 'safe' 140 141 if self.header is None: 142 if self.field_name == '__unicode__': 143 self.header = force_unicode(model._meta.verbose_name) 144 elif self.field_name == '__str__': 145 self.header = smart_str(model._meta.verbose_name) 146 elif attr is not None: 147 try: 148 self.header = attr.short_description 149 except AttributeError: 150 pass 151 152 if not self.header: 153 self.header = self.field_name.replace('_', ' ') 154 155 if attr is not None and not self.order_field: 156 self.order_field = getattr(attr, "admin_order_field", None) 157 158 def get_value(self, result): 159 attr = getattr(result, self.field_name, None) 160 if callable(attr): 161 return attr() 162 return attr 163 164 27 165 HORIZONTAL, VERTICAL = 1, 2 28 166 # returns the <ul> class for a given radio_admin field 29 167 get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '') … … 233 371 if not self.list_display_links: 234 372 for name in self.list_display: 235 373 if name != 'action_checkbox': 236 self.list_display_links = [name] 374 if isinstance(name, ListColumn): 375 self.list_display_links = [name.field_name] 376 else: 377 self.list_display_links = [name] 237 378 break 238 379 super(ModelAdmin, self).__init__() 239 380 -
django/contrib/admin/util.py
314 314 315 315 316 316 def display_for_field(value, field): 317 from django.contrib.admin.templatetags.admin_list import _boolean_icon317 from django.contrib.admin.templatetags.admin_list import boolean_icon 318 318 from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE 319 319 320 320 if field.flatchoices: … … 322 322 # NullBooleanField needs special-case null-handling, so it comes 323 323 # before the general null test. 324 324 elif isinstance(field, models.BooleanField) or isinstance(field, models.NullBooleanField): 325 return _boolean_icon(value)325 return boolean_icon(value) 326 326 elif value is None: 327 327 return EMPTY_CHANGELIST_VALUE 328 328 elif isinstance(field, models.DateField) or isinstance(field, models.TimeField): -
django/contrib/admin/__init__.py
2 2 from django.contrib.admin.options import ModelAdmin, HORIZONTAL, VERTICAL 3 3 from django.contrib.admin.options import StackedInline, TabularInline 4 4 from django.contrib.admin.sites import AdminSite, site 5 from django.contrib.admin.options import ListColumn 5 6 6 7 7 8 def autodiscover(): -
django/contrib/admin/helpers.py
161 161 }) 162 162 163 163 def contents(self): 164 from django.contrib.admin.templatetags.admin_list import _boolean_icon164 from django.contrib.admin.templatetags.admin_list import boolean_icon 165 165 from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE 166 166 field, obj, model_admin = self.field['field'], self.form.instance, self.model_admin 167 167 try: … … 172 172 if f is None: 173 173 boolean = getattr(attr, "boolean", False) 174 174 if boolean: 175 result_repr = _boolean_icon(value)175 result_repr = boolean_icon(value) 176 176 else: 177 177 result_repr = smart_unicode(value) 178 178 if getattr(attr, "allow_tags", False): -
django/contrib/admin/templatetags/admin_list.py
1 import datetime2 3 1 from django.conf import settings 4 2 from django.contrib.admin.util import lookup_field, display_for_field, label_for_field 5 3 from django.contrib.admin.views.main import ALL_VAR, EMPTY_CHANGELIST_VALUE 6 4 from django.contrib.admin.views.main import ORDER_VAR, ORDER_TYPE_VAR, PAGE_VAR, SEARCH_VAR 5 from django.contrib.admin.options import ListColumn 7 6 from django.core.exceptions import ObjectDoesNotExist 8 7 from django.db import models 9 8 from django.forms.forms import pretty_name 10 from django.utils import formats 9 from django.utils import formats, dateformat 11 10 from django.utils.html import escape, conditional_escape 12 11 from django.utils.safestring import mark_safe 13 12 from django.utils.text import capfirst 14 from django.utils.translation import ugettext as _ 13 from django.utils.translation import ugettext as _, get_date_formats 15 14 from django.utils.encoding import smart_unicode, force_unicode 16 15 from django.template import Library 16 import datetime 17 17 18 18 19 19 register = Library() … … 32 32 return mark_safe(u'<a href="%s"%s>%d</a> ' % (escape(cl.get_query_string({PAGE_VAR: i})), (i == cl.paginator.num_pages-1 and ' class="end"' or ''), i+1)) 33 33 paginator_number = register.simple_tag(paginator_number) 34 34 35 35 36 def pagination(cl): 36 37 """ 37 38 Generates the series of links to the pages in a paginated list. … … 78 79 } 79 80 pagination = register.inclusion_tag('admin/pagination.html')(pagination) 80 81 81 def result_headers(cl ):82 def result_headers(cl, columns): 82 83 """ 83 84 Generates the list column headers. 84 85 """ 85 lookup_opts = cl.lookup_opts 86 87 for i, field_name in enumerate(cl.list_display): 88 header, attr = label_for_field(field_name, cl.model, 89 model_admin = cl.model_admin, 90 return_attr = True 91 ) 92 if attr: 93 # if the field is the action checkbox: no sorting and special class 94 if field_name == 'action_checkbox': 95 yield { 86 for i, col in enumerate(columns): 87 # if the field is the action checkbox: no sorting and special class 88 if col.field_name == 'action_checkbox': 89 header, attr = label_for_field(col.field_name, cl.model, 90 model_admin = cl.model_admin, 91 return_attr = True 92 ) 93 yield { 96 94 "text": header, 97 95 "class_attrib": mark_safe(' class="action-checkbox-column"') 98 96 } 99 97 continue 100 98 101 # It is a non-field, but perhaps one that is sortable 102 admin_order_field = getattr(attr, "admin_order_field", None) 103 if not admin_order_field: 104 yield {"text": header} 105 continue 99 if col.order_field is None: 100 yield {'text': col.header} 101 continue 106 102 107 # So this _is_ a sortable non-field. Go to the yield108 # after the else clause.109 else:110 admin_order_field = None111 112 103 th_classes = [] 113 104 new_order_type = 'asc' 114 if field_name == cl.order_field or admin_order_field == cl.order_field:105 if col.order_field == cl.order_field: 115 106 th_classes.append('sorted %sending' % cl.order_type.lower()) 116 107 new_order_type = {'asc': 'desc', 'desc': 'asc'}[cl.order_type.lower()] 117 108 118 109 yield { 119 "text": header,110 "text": col.header, 120 111 "sortable": True, 121 112 "url": cl.get_query_string({ORDER_VAR: i, ORDER_TYPE_VAR: new_order_type}), 122 113 "class_attrib": mark_safe(th_classes and ' class="%s"' % ' '.join(th_classes) or '') 123 114 } 124 115 125 def _boolean_icon(field_val): 126 BOOLEAN_MAPPING = {True: 'yes', False: 'no', None: 'unknown'} 127 return mark_safe(u'<img src="%simg/admin/icon-%s.gif" alt="%s" />' % (settings.ADMIN_MEDIA_PREFIX, BOOLEAN_MAPPING[field_val], field_val)) 116 def boolean_icon(field_val): 117 if field_val is None: 118 v = 'unknown' 119 else: 120 v = field_val and 'yes' or 'no' 121 return mark_safe(u'<img src="%simg/admin/icon-%s.gif" alt="%s" />' % (settings.ADMIN_MEDIA_PREFIX, v, field_val)) 122 boolean_icon = register.filter(boolean_icon) 128 123 129 def items_for_result(cl, result, form): 124 125 def admin_datetime_format(field_val): 126 (date_format, datetime_format, time_format) = get_date_formats() 127 return capfirst(dateformat.format(field_val, datetime_format)) 128 admin_datetime_format = register.filter(admin_datetime_format) 129 130 131 def admin_date_format(field_val): 132 (date_format, datetime_format, time_format) = get_date_formats() 133 return capfirst(dateformat.format(field_val, date_format)) 134 admin_date_format = register.filter(admin_date_format) 135 136 137 def admin_time_format(field_val): 138 (date_format, datetime_format, time_format) = get_date_formats() 139 return capfirst(dateformat.format(field_val, time_format)) 140 admin_time_format = register.filter(admin_time_format) 141 142 143 def items_for_result(cl, result, form, columns): 130 144 """ 131 145 Generates the actual list of data. 132 146 """ 133 first = True134 147 pk = cl.lookup_opts.pk.attname 135 for field_name in cl.list_display: 136 row_class = '' 137 try: 138 f, attr, value = lookup_field(field_name, result, cl.model_admin) 139 except (AttributeError, ObjectDoesNotExist): 140 result_repr = EMPTY_CHANGELIST_VALUE 141 else: 142 if f is None: 143 allow_tags = getattr(attr, 'allow_tags', False) 144 boolean = getattr(attr, 'boolean', False) 145 if boolean: 146 allow_tags = True 147 result_repr = _boolean_icon(value) 148 else: 149 result_repr = smart_unicode(value) 150 # Strip HTML tags in the resulting text, except if the 151 # function has an "allow_tags" attribute set to True. 152 if not allow_tags: 153 result_repr = escape(result_repr) 154 else: 155 result_repr = mark_safe(result_repr) 156 else: 157 if value is None: 158 result_repr = EMPTY_CHANGELIST_VALUE 159 if isinstance(f.rel, models.ManyToOneRel): 160 result_repr = escape(getattr(result, f.name)) 161 else: 162 result_repr = display_for_field(value, f) 163 if isinstance(f, models.DateField) or isinstance(f, models.TimeField): 164 row_class = ' class="nowrap"' 165 if force_unicode(result_repr) == '': 166 result_repr = mark_safe(' ') 148 for i, col in enumerate(columns): 149 result_repr = col.get_value_display(result, cl) 150 row_class = col._nowrap and ' class="nowrap"' or '' 151 first = (i == 0) 167 152 # If list_display_links not defined, add the link tag to the first field 168 if (first and not cl.list_display_links) or field_name in cl.list_display_links:153 if (first and not cl.list_display_links) or col.field_name in cl.list_display_links: 169 154 table_tag = {True:'th', False:'td'}[first] 170 first = False171 155 url = cl.url_for_result(result) 172 156 # Convert the pk to something that can be used in Javascript. 173 157 # Problem cases are long ints (23L) and non-ASCII strings. … … 183 167 # By default the fields come from ModelAdmin.list_editable, but if we pull 184 168 # the fields out of the form instead of list_editable custom admins 185 169 # can provide fields on a per request basis 186 if form and field_name in form.fields:187 bf = form[ field_name]170 if form and col.field_name in form.fields: 171 bf = form[col.field_name] 188 172 result_repr = mark_safe(force_unicode(bf.errors) + force_unicode(bf)) 189 173 else: 190 174 result_repr = conditional_escape(result_repr) … … 192 176 if form and not form[cl.model._meta.pk.name].is_hidden: 193 177 yield mark_safe(u'<td>%s</td>' % force_unicode(form[cl.model._meta.pk.name])) 194 178 195 def results(cl ):179 def results(cl, columns): 196 180 if cl.formset: 197 181 for res, form in zip(cl.result_list, cl.formset.forms): 198 yield list(items_for_result(cl, res, form ))182 yield list(items_for_result(cl, res, form, columns)) 199 183 else: 200 184 for res in cl.result_list: 201 yield list(items_for_result(cl, res, None ))185 yield list(items_for_result(cl, res, None, columns)) 202 186 203 187 def result_hidden_fields(cl): 204 188 if cl.formset: … … 210 194 """ 211 195 Displays the headers and data list together 212 196 """ 197 cols = [] 198 for col in cl.list_display: 199 if isinstance(col, ListColumn): 200 cols += [col.for_model(cl.model, cl)] 201 else: 202 cols += [ListColumn(col).for_model(cl.model, cl)] 213 203 return {'cl': cl, 214 204 'result_hidden_fields': list(result_hidden_fields(cl)), 215 'result_headers': list(result_headers(cl )),216 'results': list(results(cl ))}205 'result_headers': list(result_headers(cl, cols)), 206 'results': list(results(cl, cols))} 217 207 result_list = register.inclusion_tag("admin/change_list_results.html")(result_list) 218 208 219 209 def date_hierarchy(cl): -
django/contrib/admin/views/main.py
1 1 from django.contrib.admin.filterspecs import FilterSpec 2 from django.contrib.admin.options import IncorrectLookupParameters 2 from django.contrib.admin.options import IncorrectLookupParameters, ListColumn 3 3 from django.contrib.admin.util import quote 4 4 from django.core.paginator import Paginator, InvalidPage 5 5 from django.db import models … … 140 140 if ORDER_VAR in params: 141 141 try: 142 142 field_name = self.list_display[int(params[ORDER_VAR])] 143 field = self.list_display[int(params[ORDER_VAR])] 144 if isinstance(field, ListColumn): 145 field_name = field.field_name 146 else: 147 field_name = field 143 148 try: 144 149 f = lookup_opts.get_field(field_name) 145 150 except models.FieldDoesNotExist: