Ticket #17627: 0001-Fixing-17627-renaming-contrib.admin.util-contrib.adm.patch
File 0001-Fixing-17627-renaming-contrib.admin.util-contrib.adm.patch, 60.1 KB (added by , 13 years ago) |
---|
-
AUTHORS
diff --git a/AUTHORS b/AUTHORS index 5fa21d3..faa6126 100644
a b answer newbie questions, and generally made Django that much better: 218 218 David Gouldin <dgouldin@gmail.com> 219 219 pradeep.gowda@gmail.com 220 220 Collin Grady <collin@collingrady.com> 221 Luke Granger-Brown <django@lukegb.com> 221 222 Gabriel Grant <g@briel.ca> 222 223 Simon Greenhill <dev@simon.net.nz> 223 224 Owen Griffiths … … answer newbie questions, and generally made Django that much better: 295 296 Cameron Knight (ckknight) 296 297 Nena Kojadin <nena@kiberpipa.org> 297 298 Igor Kolar <ike@email.si> 299 Wiktor Kołodziej 298 300 Tomáš Kopeček <permonik@m6.cz> 299 301 Gasper Koren 300 302 Mikhail Korobov <kmike84@googlemail.com> -
django/contrib/admin/actions.py
diff --git a/django/contrib/admin/actions.py b/django/contrib/admin/actions.py index 5b56402..4a03a97 100644
a b Built-in, globally-available admin actions. 4 4 5 5 from django.core.exceptions import PermissionDenied 6 6 from django.contrib.admin import helpers 7 from django.contrib.admin.util import get_deleted_objects, model_ngettext7 from django.contrib.admin.utils import get_deleted_objects, model_ngettext 8 8 from django.db import router 9 9 from django.template.response import TemplateResponse 10 10 from django.utils.encoding import force_unicode -
django/contrib/admin/filters.py
diff --git a/django/contrib/admin/filters.py b/django/contrib/admin/filters.py index 550683b..dc4434b 100644
a b from django.core.exceptions import ImproperlyConfigured 12 12 from django.utils.encoding import smart_unicode 13 13 from django.utils.translation import ugettext_lazy as _ 14 14 15 from django.contrib.admin.util import (get_model_from_relation,15 from django.contrib.admin.utils import (get_model_from_relation, 16 16 reverse_field_path, get_limit_choices_to_from_path, prepare_lookup_value) 17 17 18 18 class ListFilter(object): -
django/contrib/admin/helpers.py
diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py index b08d1c8..eb5b001 100644
a b 1 1 from django import forms 2 from django.contrib.admin.util import (flatten_fieldsets, lookup_field,2 from django.contrib.admin.utils import (flatten_fieldsets, lookup_field, 3 3 display_for_field, label_for_field, help_text_for_field) 4 4 from django.contrib.admin.templatetags.admin_static import static 5 5 from django.contrib.contenttypes.models import ContentType -
django/contrib/admin/models.py
diff --git a/django/contrib/admin/models.py b/django/contrib/admin/models.py index 0e5b8a7..013ae44 100644
a b 1 1 from django.db import models 2 2 from django.contrib.contenttypes.models import ContentType 3 3 from django.contrib.auth.models import User 4 from django.contrib.admin.util import quote4 from django.contrib.admin.utils import quote 5 5 from django.utils.translation import ugettext_lazy as _ 6 6 from django.utils.encoding import smart_unicode 7 7 from django.utils.safestring import mark_safe -
django/contrib/admin/options.py
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index f5f6256..0dcb4e6 100644
a b from django.forms.models import (modelform_factory, modelformset_factory, 6 6 inlineformset_factory, BaseInlineFormSet) 7 7 from django.contrib.contenttypes.models import ContentType 8 8 from django.contrib.admin import widgets, helpers 9 from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_format_dict9 from django.contrib.admin.utils import unquote, flatten_fieldsets, get_deleted_objects, model_format_dict 10 10 from django.contrib.admin.templatetags.admin_static import static 11 11 from django.contrib import messages 12 12 from django.views.decorators.csrf import csrf_protect -
django/contrib/admin/templatetags/admin_list.py
diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py index e778429..23ba3ea 100644
a b 1 1 import datetime 2 2 3 from django.contrib.admin.util import lookup_field, display_for_field, label_for_field3 from django.contrib.admin.utils import lookup_field, display_for_field, label_for_field 4 4 from django.contrib.admin.views.main import (ALL_VAR, EMPTY_CHANGELIST_VALUE, 5 5 ORDER_VAR, PAGE_VAR, SEARCH_VAR) 6 6 from django.contrib.admin.templatetags.admin_static import static -
django/contrib/admin/util.py
diff --git a/django/contrib/admin/util.py b/django/contrib/admin/util.py index 61182a6..fc8b3dd 100644
a b 1 from django.db import models 2 from django.db.models.sql.constants import LOOKUP_SEP 3 from django.db.models.deletion import Collector 4 from django.db.models.related import RelatedObject 5 from django.forms.forms import pretty_name 6 from django.utils import formats 7 from django.utils.html import escape 8 from django.utils.safestring import mark_safe 9 from django.utils.text import capfirst 10 from django.utils import timezone 11 from django.utils.encoding import force_unicode, smart_unicode, smart_str 12 from django.utils.translation import ungettext 13 from django.core.urlresolvers import reverse 1 import warnings 14 2 15 def lookup_needs_distinct(opts, lookup_path): 16 """ 17 Returns True if 'distinct()' should be used to query the given lookup path. 18 """ 19 field_name = lookup_path.split('__', 1)[0] 20 field = opts.get_field_by_name(field_name)[0] 21 if ((hasattr(field, 'rel') and 22 isinstance(field.rel, models.ManyToManyRel)) or 23 (isinstance(field, models.related.RelatedObject) and 24 not field.field.unique)): 25 return True 26 return False 3 warnings.warn("The django.contrib.admin.util module has been renamed " 4 "(https://code.djangoproject.com/ticket/17627/). " 5 "Use django.contrib.admin.utils instead.", PendingDeprecationWarning) 27 6 28 def prepare_lookup_value(key, value): 29 """ 30 Returns a lookup value prepared to be used in queryset filtering. 31 """ 32 # if key ends with __in, split parameter into separate values 33 if key.endswith('__in'): 34 value = value.split(',') 35 # if key ends with __isnull, special case '' and false 36 if key.endswith('__isnull'): 37 if value.lower() in ('', 'false'): 38 value = False 39 else: 40 value = True 41 return value 42 43 def quote(s): 44 """ 45 Ensure that primary key values do not confuse the admin URLs by escaping 46 any '/', '_' and ':' characters. Similar to urllib.quote, except that the 47 quoting is slightly different so that it doesn't get automatically 48 unquoted by the Web browser. 49 """ 50 if not isinstance(s, basestring): 51 return s 52 res = list(s) 53 for i in range(len(res)): 54 c = res[i] 55 if c in """:/_#?;@&=+$,"<>%\\""": 56 res[i] = '_%02X' % ord(c) 57 return ''.join(res) 58 59 60 def unquote(s): 61 """ 62 Undo the effects of quote(). Based heavily on urllib.unquote(). 63 """ 64 mychr = chr 65 myatoi = int 66 list = s.split('_') 67 res = [list[0]] 68 myappend = res.append 69 del list[0] 70 for item in list: 71 if item[1:2]: 72 try: 73 myappend(mychr(myatoi(item[:2], 16)) + item[2:]) 74 except ValueError: 75 myappend('_' + item) 76 else: 77 myappend('_' + item) 78 return "".join(res) 79 80 81 def flatten_fieldsets(fieldsets): 82 """Returns a list of field names from an admin fieldsets structure.""" 83 field_names = [] 84 for name, opts in fieldsets: 85 for field in opts['fields']: 86 # type checking feels dirty, but it seems like the best way here 87 if type(field) == tuple: 88 field_names.extend(field) 89 else: 90 field_names.append(field) 91 return field_names 92 93 94 def get_deleted_objects(objs, opts, user, admin_site, using): 95 """ 96 Find all objects related to ``objs`` that should also be deleted. ``objs`` 97 must be a homogenous iterable of objects (e.g. a QuerySet). 98 99 Returns a nested list of strings suitable for display in the 100 template with the ``unordered_list`` filter. 101 102 """ 103 collector = NestedObjects(using=using) 104 collector.collect(objs) 105 perms_needed = set() 106 107 def format_callback(obj): 108 has_admin = obj.__class__ in admin_site._registry 109 opts = obj._meta 110 111 if has_admin: 112 admin_url = reverse('%s:%s_%s_change' 113 % (admin_site.name, 114 opts.app_label, 115 opts.object_name.lower()), 116 None, (quote(obj._get_pk_val()),)) 117 p = '%s.%s' % (opts.app_label, 118 opts.get_delete_permission()) 119 if not user.has_perm(p): 120 perms_needed.add(opts.verbose_name) 121 # Display a link to the admin page. 122 return mark_safe(u'%s: <a href="%s">%s</a>' % 123 (escape(capfirst(opts.verbose_name)), 124 admin_url, 125 escape(obj))) 126 else: 127 # Don't display link to edit, because it either has no 128 # admin or is edited inline. 129 return u'%s: %s' % (capfirst(opts.verbose_name), 130 force_unicode(obj)) 131 132 to_delete = collector.nested(format_callback) 133 134 protected = [format_callback(obj) for obj in collector.protected] 135 136 return to_delete, perms_needed, protected 137 138 139 class NestedObjects(Collector): 140 def __init__(self, *args, **kwargs): 141 super(NestedObjects, self).__init__(*args, **kwargs) 142 self.edges = {} # {from_instance: [to_instances]} 143 self.protected = set() 144 145 def add_edge(self, source, target): 146 self.edges.setdefault(source, []).append(target) 147 148 def collect(self, objs, source_attr=None, **kwargs): 149 for obj in objs: 150 if source_attr: 151 self.add_edge(getattr(obj, source_attr), obj) 152 else: 153 self.add_edge(None, obj) 154 try: 155 return super(NestedObjects, self).collect(objs, source_attr=source_attr, **kwargs) 156 except models.ProtectedError, e: 157 self.protected.update(e.protected_objects) 158 159 def related_objects(self, related, objs): 160 qs = super(NestedObjects, self).related_objects(related, objs) 161 return qs.select_related(related.field.name) 162 163 def _nested(self, obj, seen, format_callback): 164 if obj in seen: 165 return [] 166 seen.add(obj) 167 children = [] 168 for child in self.edges.get(obj, ()): 169 children.extend(self._nested(child, seen, format_callback)) 170 if format_callback: 171 ret = [format_callback(obj)] 172 else: 173 ret = [obj] 174 if children: 175 ret.append(children) 176 return ret 177 178 def nested(self, format_callback=None): 179 """ 180 Return the graph as a nested list. 181 182 """ 183 seen = set() 184 roots = [] 185 for root in self.edges.get(None, ()): 186 roots.extend(self._nested(root, seen, format_callback)) 187 return roots 188 189 190 def model_format_dict(obj): 191 """ 192 Return a `dict` with keys 'verbose_name' and 'verbose_name_plural', 193 typically for use with string formatting. 194 195 `obj` may be a `Model` instance, `Model` subclass, or `QuerySet` instance. 196 197 """ 198 if isinstance(obj, (models.Model, models.base.ModelBase)): 199 opts = obj._meta 200 elif isinstance(obj, models.query.QuerySet): 201 opts = obj.model._meta 202 else: 203 opts = obj 204 return { 205 'verbose_name': force_unicode(opts.verbose_name), 206 'verbose_name_plural': force_unicode(opts.verbose_name_plural) 207 } 208 209 210 def model_ngettext(obj, n=None): 211 """ 212 Return the appropriate `verbose_name` or `verbose_name_plural` value for 213 `obj` depending on the count `n`. 214 215 `obj` may be a `Model` instance, `Model` subclass, or `QuerySet` instance. 216 If `obj` is a `QuerySet` instance, `n` is optional and the length of the 217 `QuerySet` is used. 218 219 """ 220 if isinstance(obj, models.query.QuerySet): 221 if n is None: 222 n = obj.count() 223 obj = obj.model 224 d = model_format_dict(obj) 225 singular, plural = d["verbose_name"], d["verbose_name_plural"] 226 return ungettext(singular, plural, n or 0) 227 228 229 def lookup_field(name, obj, model_admin=None): 230 opts = obj._meta 231 try: 232 f = opts.get_field(name) 233 except models.FieldDoesNotExist: 234 # For non-field values, the value is either a method, property or 235 # returned via a callable. 236 if callable(name): 237 attr = name 238 value = attr(obj) 239 elif (model_admin is not None and hasattr(model_admin, name) and 240 not name == '__str__' and not name == '__unicode__'): 241 attr = getattr(model_admin, name) 242 value = attr(obj) 243 else: 244 attr = getattr(obj, name) 245 if callable(attr): 246 value = attr() 247 else: 248 value = attr 249 f = None 250 else: 251 attr = None 252 value = getattr(obj, name) 253 return f, attr, value 254 255 256 def label_for_field(name, model, model_admin=None, return_attr=False): 257 """ 258 Returns a sensible label for a field name. The name can be a callable or the 259 name of an object attributes, as well as a genuine fields. If return_attr is 260 True, the resolved attribute (which could be a callable) is also returned. 261 This will be None if (and only if) the name refers to a field. 262 """ 263 attr = None 264 try: 265 field = model._meta.get_field_by_name(name)[0] 266 if isinstance(field, RelatedObject): 267 label = field.opts.verbose_name 268 else: 269 label = field.verbose_name 270 except models.FieldDoesNotExist: 271 if name == "__unicode__": 272 label = force_unicode(model._meta.verbose_name) 273 attr = unicode 274 elif name == "__str__": 275 label = smart_str(model._meta.verbose_name) 276 attr = str 277 else: 278 if callable(name): 279 attr = name 280 elif model_admin is not None and hasattr(model_admin, name): 281 attr = getattr(model_admin, name) 282 elif hasattr(model, name): 283 attr = getattr(model, name) 284 else: 285 message = "Unable to lookup '%s' on %s" % (name, model._meta.object_name) 286 if model_admin: 287 message += " or %s" % (model_admin.__class__.__name__,) 288 raise AttributeError(message) 289 290 if hasattr(attr, "short_description"): 291 label = attr.short_description 292 elif callable(attr): 293 if attr.__name__ == "<lambda>": 294 label = "--" 295 else: 296 label = pretty_name(attr.__name__) 297 else: 298 label = pretty_name(name) 299 if return_attr: 300 return (label, attr) 301 else: 302 return label 303 304 def help_text_for_field(name, model): 305 try: 306 help_text = model._meta.get_field_by_name(name)[0].help_text 307 except models.FieldDoesNotExist: 308 help_text = "" 309 return smart_unicode(help_text) 310 311 312 def display_for_field(value, field): 313 from django.contrib.admin.templatetags.admin_list import _boolean_icon 314 from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE 315 316 if field.flatchoices: 317 return dict(field.flatchoices).get(value, EMPTY_CHANGELIST_VALUE) 318 # NullBooleanField needs special-case null-handling, so it comes 319 # before the general null test. 320 elif isinstance(field, models.BooleanField) or isinstance(field, models.NullBooleanField): 321 return _boolean_icon(value) 322 elif value is None: 323 return EMPTY_CHANGELIST_VALUE 324 elif isinstance(field, models.DateTimeField): 325 return formats.localize(timezone.localtime(value)) 326 elif isinstance(field, models.DateField) or isinstance(field, models.TimeField): 327 return formats.localize(value) 328 elif isinstance(field, models.DecimalField): 329 return formats.number_format(value, field.decimal_places) 330 elif isinstance(field, models.FloatField): 331 return formats.number_format(value) 332 else: 333 return smart_unicode(value) 334 335 336 class NotRelationField(Exception): 337 pass 338 339 340 def get_model_from_relation(field): 341 if isinstance(field, models.related.RelatedObject): 342 return field.model 343 elif getattr(field, 'rel'): # or isinstance? 344 return field.rel.to 345 else: 346 raise NotRelationField 347 348 349 def reverse_field_path(model, path): 350 """ Create a reversed field path. 351 352 E.g. Given (Order, "user__groups"), 353 return (Group, "user__order"). 354 355 Final field must be a related model, not a data field. 356 357 """ 358 reversed_path = [] 359 parent = model 360 pieces = path.split(LOOKUP_SEP) 361 for piece in pieces: 362 field, model, direct, m2m = parent._meta.get_field_by_name(piece) 363 # skip trailing data field if extant: 364 if len(reversed_path) == len(pieces)-1: # final iteration 365 try: 366 get_model_from_relation(field) 367 except NotRelationField: 368 break 369 if direct: 370 related_name = field.related_query_name() 371 parent = field.rel.to 372 else: 373 related_name = field.field.name 374 parent = field.model 375 reversed_path.insert(0, related_name) 376 return (parent, LOOKUP_SEP.join(reversed_path)) 377 378 379 def get_fields_from_path(model, path): 380 """ Return list of Fields given path relative to model. 381 382 e.g. (ModelX, "user__groups__name") -> [ 383 <django.db.models.fields.related.ForeignKey object at 0x...>, 384 <django.db.models.fields.related.ManyToManyField object at 0x...>, 385 <django.db.models.fields.CharField object at 0x...>, 386 ] 387 """ 388 pieces = path.split(LOOKUP_SEP) 389 fields = [] 390 for piece in pieces: 391 if fields: 392 parent = get_model_from_relation(fields[-1]) 393 else: 394 parent = model 395 fields.append(parent._meta.get_field_by_name(piece)[0]) 396 return fields 397 398 399 def remove_trailing_data_field(fields): 400 """ Discard trailing non-relation field if extant. """ 401 try: 402 get_model_from_relation(fields[-1]) 403 except NotRelationField: 404 fields = fields[:-1] 405 return fields 406 407 408 def get_limit_choices_to_from_path(model, path): 409 """ Return Q object for limiting choices if applicable. 410 411 If final model in path is linked via a ForeignKey or ManyToManyField which 412 has a `limit_choices_to` attribute, return it as a Q object. 413 """ 414 415 fields = get_fields_from_path(model, path) 416 fields = remove_trailing_data_field(fields) 417 limit_choices_to = ( 418 fields and hasattr(fields[-1], 'rel') and 419 getattr(fields[-1].rel, 'limit_choices_to', None)) 420 if not limit_choices_to: 421 return models.Q() # empty Q 422 elif isinstance(limit_choices_to, models.Q): 423 return limit_choices_to # already a Q 424 else: 425 return models.Q(**limit_choices_to) # convert dict to Q 7 from django.contrib.admin.utils import * -
new file django/contrib/admin/utils.py
diff --git a/django/contrib/admin/utils.py b/django/contrib/admin/utils.py new file mode 100644 index 0000000..61182a6
- + 1 from django.db import models 2 from django.db.models.sql.constants import LOOKUP_SEP 3 from django.db.models.deletion import Collector 4 from django.db.models.related import RelatedObject 5 from django.forms.forms import pretty_name 6 from django.utils import formats 7 from django.utils.html import escape 8 from django.utils.safestring import mark_safe 9 from django.utils.text import capfirst 10 from django.utils import timezone 11 from django.utils.encoding import force_unicode, smart_unicode, smart_str 12 from django.utils.translation import ungettext 13 from django.core.urlresolvers import reverse 14 15 def lookup_needs_distinct(opts, lookup_path): 16 """ 17 Returns True if 'distinct()' should be used to query the given lookup path. 18 """ 19 field_name = lookup_path.split('__', 1)[0] 20 field = opts.get_field_by_name(field_name)[0] 21 if ((hasattr(field, 'rel') and 22 isinstance(field.rel, models.ManyToManyRel)) or 23 (isinstance(field, models.related.RelatedObject) and 24 not field.field.unique)): 25 return True 26 return False 27 28 def prepare_lookup_value(key, value): 29 """ 30 Returns a lookup value prepared to be used in queryset filtering. 31 """ 32 # if key ends with __in, split parameter into separate values 33 if key.endswith('__in'): 34 value = value.split(',') 35 # if key ends with __isnull, special case '' and false 36 if key.endswith('__isnull'): 37 if value.lower() in ('', 'false'): 38 value = False 39 else: 40 value = True 41 return value 42 43 def quote(s): 44 """ 45 Ensure that primary key values do not confuse the admin URLs by escaping 46 any '/', '_' and ':' characters. Similar to urllib.quote, except that the 47 quoting is slightly different so that it doesn't get automatically 48 unquoted by the Web browser. 49 """ 50 if not isinstance(s, basestring): 51 return s 52 res = list(s) 53 for i in range(len(res)): 54 c = res[i] 55 if c in """:/_#?;@&=+$,"<>%\\""": 56 res[i] = '_%02X' % ord(c) 57 return ''.join(res) 58 59 60 def unquote(s): 61 """ 62 Undo the effects of quote(). Based heavily on urllib.unquote(). 63 """ 64 mychr = chr 65 myatoi = int 66 list = s.split('_') 67 res = [list[0]] 68 myappend = res.append 69 del list[0] 70 for item in list: 71 if item[1:2]: 72 try: 73 myappend(mychr(myatoi(item[:2], 16)) + item[2:]) 74 except ValueError: 75 myappend('_' + item) 76 else: 77 myappend('_' + item) 78 return "".join(res) 79 80 81 def flatten_fieldsets(fieldsets): 82 """Returns a list of field names from an admin fieldsets structure.""" 83 field_names = [] 84 for name, opts in fieldsets: 85 for field in opts['fields']: 86 # type checking feels dirty, but it seems like the best way here 87 if type(field) == tuple: 88 field_names.extend(field) 89 else: 90 field_names.append(field) 91 return field_names 92 93 94 def get_deleted_objects(objs, opts, user, admin_site, using): 95 """ 96 Find all objects related to ``objs`` that should also be deleted. ``objs`` 97 must be a homogenous iterable of objects (e.g. a QuerySet). 98 99 Returns a nested list of strings suitable for display in the 100 template with the ``unordered_list`` filter. 101 102 """ 103 collector = NestedObjects(using=using) 104 collector.collect(objs) 105 perms_needed = set() 106 107 def format_callback(obj): 108 has_admin = obj.__class__ in admin_site._registry 109 opts = obj._meta 110 111 if has_admin: 112 admin_url = reverse('%s:%s_%s_change' 113 % (admin_site.name, 114 opts.app_label, 115 opts.object_name.lower()), 116 None, (quote(obj._get_pk_val()),)) 117 p = '%s.%s' % (opts.app_label, 118 opts.get_delete_permission()) 119 if not user.has_perm(p): 120 perms_needed.add(opts.verbose_name) 121 # Display a link to the admin page. 122 return mark_safe(u'%s: <a href="%s">%s</a>' % 123 (escape(capfirst(opts.verbose_name)), 124 admin_url, 125 escape(obj))) 126 else: 127 # Don't display link to edit, because it either has no 128 # admin or is edited inline. 129 return u'%s: %s' % (capfirst(opts.verbose_name), 130 force_unicode(obj)) 131 132 to_delete = collector.nested(format_callback) 133 134 protected = [format_callback(obj) for obj in collector.protected] 135 136 return to_delete, perms_needed, protected 137 138 139 class NestedObjects(Collector): 140 def __init__(self, *args, **kwargs): 141 super(NestedObjects, self).__init__(*args, **kwargs) 142 self.edges = {} # {from_instance: [to_instances]} 143 self.protected = set() 144 145 def add_edge(self, source, target): 146 self.edges.setdefault(source, []).append(target) 147 148 def collect(self, objs, source_attr=None, **kwargs): 149 for obj in objs: 150 if source_attr: 151 self.add_edge(getattr(obj, source_attr), obj) 152 else: 153 self.add_edge(None, obj) 154 try: 155 return super(NestedObjects, self).collect(objs, source_attr=source_attr, **kwargs) 156 except models.ProtectedError, e: 157 self.protected.update(e.protected_objects) 158 159 def related_objects(self, related, objs): 160 qs = super(NestedObjects, self).related_objects(related, objs) 161 return qs.select_related(related.field.name) 162 163 def _nested(self, obj, seen, format_callback): 164 if obj in seen: 165 return [] 166 seen.add(obj) 167 children = [] 168 for child in self.edges.get(obj, ()): 169 children.extend(self._nested(child, seen, format_callback)) 170 if format_callback: 171 ret = [format_callback(obj)] 172 else: 173 ret = [obj] 174 if children: 175 ret.append(children) 176 return ret 177 178 def nested(self, format_callback=None): 179 """ 180 Return the graph as a nested list. 181 182 """ 183 seen = set() 184 roots = [] 185 for root in self.edges.get(None, ()): 186 roots.extend(self._nested(root, seen, format_callback)) 187 return roots 188 189 190 def model_format_dict(obj): 191 """ 192 Return a `dict` with keys 'verbose_name' and 'verbose_name_plural', 193 typically for use with string formatting. 194 195 `obj` may be a `Model` instance, `Model` subclass, or `QuerySet` instance. 196 197 """ 198 if isinstance(obj, (models.Model, models.base.ModelBase)): 199 opts = obj._meta 200 elif isinstance(obj, models.query.QuerySet): 201 opts = obj.model._meta 202 else: 203 opts = obj 204 return { 205 'verbose_name': force_unicode(opts.verbose_name), 206 'verbose_name_plural': force_unicode(opts.verbose_name_plural) 207 } 208 209 210 def model_ngettext(obj, n=None): 211 """ 212 Return the appropriate `verbose_name` or `verbose_name_plural` value for 213 `obj` depending on the count `n`. 214 215 `obj` may be a `Model` instance, `Model` subclass, or `QuerySet` instance. 216 If `obj` is a `QuerySet` instance, `n` is optional and the length of the 217 `QuerySet` is used. 218 219 """ 220 if isinstance(obj, models.query.QuerySet): 221 if n is None: 222 n = obj.count() 223 obj = obj.model 224 d = model_format_dict(obj) 225 singular, plural = d["verbose_name"], d["verbose_name_plural"] 226 return ungettext(singular, plural, n or 0) 227 228 229 def lookup_field(name, obj, model_admin=None): 230 opts = obj._meta 231 try: 232 f = opts.get_field(name) 233 except models.FieldDoesNotExist: 234 # For non-field values, the value is either a method, property or 235 # returned via a callable. 236 if callable(name): 237 attr = name 238 value = attr(obj) 239 elif (model_admin is not None and hasattr(model_admin, name) and 240 not name == '__str__' and not name == '__unicode__'): 241 attr = getattr(model_admin, name) 242 value = attr(obj) 243 else: 244 attr = getattr(obj, name) 245 if callable(attr): 246 value = attr() 247 else: 248 value = attr 249 f = None 250 else: 251 attr = None 252 value = getattr(obj, name) 253 return f, attr, value 254 255 256 def label_for_field(name, model, model_admin=None, return_attr=False): 257 """ 258 Returns a sensible label for a field name. The name can be a callable or the 259 name of an object attributes, as well as a genuine fields. If return_attr is 260 True, the resolved attribute (which could be a callable) is also returned. 261 This will be None if (and only if) the name refers to a field. 262 """ 263 attr = None 264 try: 265 field = model._meta.get_field_by_name(name)[0] 266 if isinstance(field, RelatedObject): 267 label = field.opts.verbose_name 268 else: 269 label = field.verbose_name 270 except models.FieldDoesNotExist: 271 if name == "__unicode__": 272 label = force_unicode(model._meta.verbose_name) 273 attr = unicode 274 elif name == "__str__": 275 label = smart_str(model._meta.verbose_name) 276 attr = str 277 else: 278 if callable(name): 279 attr = name 280 elif model_admin is not None and hasattr(model_admin, name): 281 attr = getattr(model_admin, name) 282 elif hasattr(model, name): 283 attr = getattr(model, name) 284 else: 285 message = "Unable to lookup '%s' on %s" % (name, model._meta.object_name) 286 if model_admin: 287 message += " or %s" % (model_admin.__class__.__name__,) 288 raise AttributeError(message) 289 290 if hasattr(attr, "short_description"): 291 label = attr.short_description 292 elif callable(attr): 293 if attr.__name__ == "<lambda>": 294 label = "--" 295 else: 296 label = pretty_name(attr.__name__) 297 else: 298 label = pretty_name(name) 299 if return_attr: 300 return (label, attr) 301 else: 302 return label 303 304 def help_text_for_field(name, model): 305 try: 306 help_text = model._meta.get_field_by_name(name)[0].help_text 307 except models.FieldDoesNotExist: 308 help_text = "" 309 return smart_unicode(help_text) 310 311 312 def display_for_field(value, field): 313 from django.contrib.admin.templatetags.admin_list import _boolean_icon 314 from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE 315 316 if field.flatchoices: 317 return dict(field.flatchoices).get(value, EMPTY_CHANGELIST_VALUE) 318 # NullBooleanField needs special-case null-handling, so it comes 319 # before the general null test. 320 elif isinstance(field, models.BooleanField) or isinstance(field, models.NullBooleanField): 321 return _boolean_icon(value) 322 elif value is None: 323 return EMPTY_CHANGELIST_VALUE 324 elif isinstance(field, models.DateTimeField): 325 return formats.localize(timezone.localtime(value)) 326 elif isinstance(field, models.DateField) or isinstance(field, models.TimeField): 327 return formats.localize(value) 328 elif isinstance(field, models.DecimalField): 329 return formats.number_format(value, field.decimal_places) 330 elif isinstance(field, models.FloatField): 331 return formats.number_format(value) 332 else: 333 return smart_unicode(value) 334 335 336 class NotRelationField(Exception): 337 pass 338 339 340 def get_model_from_relation(field): 341 if isinstance(field, models.related.RelatedObject): 342 return field.model 343 elif getattr(field, 'rel'): # or isinstance? 344 return field.rel.to 345 else: 346 raise NotRelationField 347 348 349 def reverse_field_path(model, path): 350 """ Create a reversed field path. 351 352 E.g. Given (Order, "user__groups"), 353 return (Group, "user__order"). 354 355 Final field must be a related model, not a data field. 356 357 """ 358 reversed_path = [] 359 parent = model 360 pieces = path.split(LOOKUP_SEP) 361 for piece in pieces: 362 field, model, direct, m2m = parent._meta.get_field_by_name(piece) 363 # skip trailing data field if extant: 364 if len(reversed_path) == len(pieces)-1: # final iteration 365 try: 366 get_model_from_relation(field) 367 except NotRelationField: 368 break 369 if direct: 370 related_name = field.related_query_name() 371 parent = field.rel.to 372 else: 373 related_name = field.field.name 374 parent = field.model 375 reversed_path.insert(0, related_name) 376 return (parent, LOOKUP_SEP.join(reversed_path)) 377 378 379 def get_fields_from_path(model, path): 380 """ Return list of Fields given path relative to model. 381 382 e.g. (ModelX, "user__groups__name") -> [ 383 <django.db.models.fields.related.ForeignKey object at 0x...>, 384 <django.db.models.fields.related.ManyToManyField object at 0x...>, 385 <django.db.models.fields.CharField object at 0x...>, 386 ] 387 """ 388 pieces = path.split(LOOKUP_SEP) 389 fields = [] 390 for piece in pieces: 391 if fields: 392 parent = get_model_from_relation(fields[-1]) 393 else: 394 parent = model 395 fields.append(parent._meta.get_field_by_name(piece)[0]) 396 return fields 397 398 399 def remove_trailing_data_field(fields): 400 """ Discard trailing non-relation field if extant. """ 401 try: 402 get_model_from_relation(fields[-1]) 403 except NotRelationField: 404 fields = fields[:-1] 405 return fields 406 407 408 def get_limit_choices_to_from_path(model, path): 409 """ Return Q object for limiting choices if applicable. 410 411 If final model in path is linked via a ForeignKey or ManyToManyField which 412 has a `limit_choices_to` attribute, return it as a Q object. 413 """ 414 415 fields = get_fields_from_path(model, path) 416 fields = remove_trailing_data_field(fields) 417 limit_choices_to = ( 418 fields and hasattr(fields[-1], 'rel') and 419 getattr(fields[-1].rel, 'limit_choices_to', None)) 420 if not limit_choices_to: 421 return models.Q() # empty Q 422 elif isinstance(limit_choices_to, models.Q): 423 return limit_choices_to # already a Q 424 else: 425 return models.Q(**limit_choices_to) # convert dict to Q -
django/contrib/admin/validation.py
diff --git a/django/contrib/admin/validation.py b/django/contrib/admin/validation.py index 733f89d..bd99b84 100644
a b from django.db.models.fields import FieldDoesNotExist 4 4 from django.forms.models import (BaseModelForm, BaseModelFormSet, fields_for_model, 5 5 _get_foreign_key) 6 6 from django.contrib.admin import ListFilter, FieldListFilter 7 from django.contrib.admin.util import get_fields_from_path, NotRelationField7 from django.contrib.admin.utils import get_fields_from_path, NotRelationField 8 8 from django.contrib.admin.options import (flatten_fieldsets, BaseModelAdmin, 9 9 HORIZONTAL, VERTICAL) 10 10 -
django/contrib/admin/views/main.py
diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py index 32113f5..cb18a3a 100644
a b from django.utils.http import urlencode 10 10 11 11 from django.contrib.admin import FieldListFilter 12 12 from django.contrib.admin.options import IncorrectLookupParameters 13 from django.contrib.admin.util import (quote, get_fields_from_path,13 from django.contrib.admin.utils import (quote, get_fields_from_path, 14 14 lookup_needs_distinct, prepare_lookup_value) 15 15 16 16 # Changelist settings -
django/contrib/auth/admin.py
diff --git a/django/contrib/auth/admin.py b/django/contrib/auth/admin.py index f14b3d2..973bdb8 100644
a b class UserAdmin(admin.ModelAdmin): 71 71 if obj is None: 72 72 defaults.update({ 73 73 'form': self.add_form, 74 'fields': admin.util .flatten_fieldsets(self.add_fieldsets),74 'fields': admin.utils.flatten_fieldsets(self.add_fieldsets), 75 75 }) 76 76 defaults.update(kwargs) 77 77 return super(UserAdmin, self).get_form(request, obj, **defaults) -
docs/internals/deprecation.txt
diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 16dbec8..c0214ec 100644
a b these changes. 267 267 in 1.4. The backward compatibility will be removed -- 268 268 ``HttpRequest.raw_post_data`` will no longer work. 269 269 270 1.5 271 --- 272 273 * ``django.contrib.admin.util`` has been moved to 274 :mod:`django.contrib.admin.utils` as part of the goal to unify all util and 275 utils references across Django. The backwards-compatibility module will 276 be removed in the 2.0 release. 277 270 278 2.0 271 279 --- 272 280 -
deleted file tests/regressiontests/admin_util/models.py
diff --git a/tests/regressiontests/admin_util/__init__.py b/tests/regressiontests/admin_util/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/regressiontests/admin_util/models.py b/tests/regressiontests/admin_util/models.py deleted file mode 100644 index 0e81df3..0000000
+ - 1 from django.db import models2 3 4 class Article(models.Model):5 """6 A simple Article model for testing7 """8 site = models.ForeignKey('sites.Site', related_name="admin_articles")9 title = models.CharField(max_length=100)10 title2 = models.CharField(max_length=100, verbose_name="another name")11 created = models.DateTimeField()12 13 def test_from_model(self):14 return "nothing"15 16 def test_from_model_with_override(self):17 return "nothing"18 test_from_model_with_override.short_description = "not What you Expect"19 20 class Count(models.Model):21 num = models.PositiveSmallIntegerField()22 parent = models.ForeignKey('self', null=True)23 24 def __unicode__(self):25 return unicode(self.num)26 27 class Event(models.Model):28 date = models.DateTimeField(auto_now_add=True)29 30 class Location(models.Model):31 event = models.OneToOneField(Event, verbose_name='awesome event')32 33 class Guest(models.Model):34 event = models.OneToOneField(Event)35 name = models.CharField(max_length=255)36 37 class Meta:38 verbose_name = "awesome guest" -
deleted file tests/regressiontests/admin_util/tests.py
diff --git a/tests/regressiontests/admin_util/tests.py b/tests/regressiontests/admin_util/tests.py deleted file mode 100644 index 8113f2e..0000000
+ - 1 from __future__ import absolute_import2 3 from datetime import datetime4 5 from django.conf import settings6 from django.contrib import admin7 from django.contrib.admin import helpers8 from django.contrib.admin.util import (display_for_field, label_for_field,9 lookup_field, NestedObjects)10 from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE11 from django.contrib.sites.models import Site12 from django.db import models, DEFAULT_DB_ALIAS13 from django import forms14 from django.test import TestCase15 from django.utils import unittest16 from django.utils.formats import localize17 from django.utils.safestring import mark_safe18 19 from .models import Article, Count, Event, Location20 21 22 class NestedObjectsTests(TestCase):23 """24 Tests for ``NestedObject`` utility collection.25 26 """27 def setUp(self):28 self.n = NestedObjects(using=DEFAULT_DB_ALIAS)29 self.objs = [Count.objects.create(num=i) for i in range(5)]30 31 def _check(self, target):32 self.assertEqual(self.n.nested(lambda obj: obj.num), target)33 34 def _connect(self, i, j):35 self.objs[i].parent = self.objs[j]36 self.objs[i].save()37 38 def _collect(self, *indices):39 self.n.collect([self.objs[i] for i in indices])40 41 def test_unrelated_roots(self):42 self._connect(2, 1)43 self._collect(0)44 self._collect(1)45 self._check([0, 1, [2]])46 47 def test_siblings(self):48 self._connect(1, 0)49 self._connect(2, 0)50 self._collect(0)51 self._check([0, [1, 2]])52 53 def test_non_added_parent(self):54 self._connect(0, 1)55 self._collect(0)56 self._check([0])57 58 def test_cyclic(self):59 self._connect(0, 2)60 self._connect(1, 0)61 self._connect(2, 1)62 self._collect(0)63 self._check([0, [1, [2]]])64 65 def test_queries(self):66 self._connect(1, 0)67 self._connect(2, 0)68 # 1 query to fetch all children of 0 (1 and 2)69 # 1 query to fetch all children of 1 and 2 (none)70 # Should not require additional queries to populate the nested graph.71 self.assertNumQueries(2, self._collect, 0)72 73 class UtilTests(unittest.TestCase):74 def test_values_from_lookup_field(self):75 """76 Regression test for #12654: lookup_field77 """78 SITE_NAME = 'example.com'79 TITLE_TEXT = 'Some title'80 CREATED_DATE = datetime.min81 ADMIN_METHOD = 'admin method'82 SIMPLE_FUNCTION = 'function'83 INSTANCE_ATTRIBUTE = 'attr'84 85 class MockModelAdmin(object):86 def get_admin_value(self, obj):87 return ADMIN_METHOD88 89 simple_function = lambda obj: SIMPLE_FUNCTION90 91 article = Article(92 site=Site(domain=SITE_NAME),93 title=TITLE_TEXT,94 created=CREATED_DATE,95 )96 article.non_field = INSTANCE_ATTRIBUTE97 98 verifications = (99 ('site', SITE_NAME),100 ('created', localize(CREATED_DATE)),101 ('title', TITLE_TEXT),102 ('get_admin_value', ADMIN_METHOD),103 (simple_function, SIMPLE_FUNCTION),104 ('test_from_model', article.test_from_model()),105 ('non_field', INSTANCE_ATTRIBUTE)106 )107 108 mock_admin = MockModelAdmin()109 for name, value in verifications:110 field, attr, resolved_value = lookup_field(name, article, mock_admin)111 112 if field is not None:113 resolved_value = display_for_field(resolved_value, field)114 115 self.assertEqual(value, resolved_value)116 117 def test_null_display_for_field(self):118 """119 Regression test for #12550: display_for_field should handle None120 value.121 """122 display_value = display_for_field(None, models.CharField())123 self.assertEqual(display_value, EMPTY_CHANGELIST_VALUE)124 125 display_value = display_for_field(None, models.CharField(126 choices=(127 (None, "test_none"),128 )129 ))130 self.assertEqual(display_value, "test_none")131 132 display_value = display_for_field(None, models.DateField())133 self.assertEqual(display_value, EMPTY_CHANGELIST_VALUE)134 135 display_value = display_for_field(None, models.TimeField())136 self.assertEqual(display_value, EMPTY_CHANGELIST_VALUE)137 138 # Regression test for #13071: NullBooleanField has special139 # handling.140 display_value = display_for_field(None, models.NullBooleanField())141 expected = u'<img src="%sadmin/img/icon-unknown.gif" alt="None" />' % settings.STATIC_URL142 self.assertEqual(display_value, expected)143 144 display_value = display_for_field(None, models.DecimalField())145 self.assertEqual(display_value, EMPTY_CHANGELIST_VALUE)146 147 display_value = display_for_field(None, models.FloatField())148 self.assertEqual(display_value, EMPTY_CHANGELIST_VALUE)149 150 def test_label_for_field(self):151 """152 Tests for label_for_field153 """154 self.assertEqual(155 label_for_field("title", Article),156 "title"157 )158 self.assertEqual(159 label_for_field("title2", Article),160 "another name"161 )162 self.assertEqual(163 label_for_field("title2", Article, return_attr=True),164 ("another name", None)165 )166 167 self.assertEqual(168 label_for_field("__unicode__", Article),169 "article"170 )171 self.assertEqual(172 label_for_field("__str__", Article),173 "article"174 )175 176 self.assertRaises(177 AttributeError,178 lambda: label_for_field("unknown", Article)179 )180 181 def test_callable(obj):182 return "nothing"183 self.assertEqual(184 label_for_field(test_callable, Article),185 "Test callable"186 )187 self.assertEqual(188 label_for_field(test_callable, Article, return_attr=True),189 ("Test callable", test_callable)190 )191 192 self.assertEqual(193 label_for_field("test_from_model", Article),194 "Test from model"195 )196 self.assertEqual(197 label_for_field("test_from_model", Article, return_attr=True),198 ("Test from model", Article.test_from_model)199 )200 self.assertEqual(201 label_for_field("test_from_model_with_override", Article),202 "not What you Expect"203 )204 205 self.assertEqual(206 label_for_field(lambda x: "nothing", Article),207 "--"208 )209 210 class MockModelAdmin(object):211 def test_from_model(self, obj):212 return "nothing"213 test_from_model.short_description = "not Really the Model"214 215 self.assertEqual(216 label_for_field("test_from_model", Article, model_admin=MockModelAdmin),217 "not Really the Model"218 )219 self.assertEqual(220 label_for_field("test_from_model", Article,221 model_admin = MockModelAdmin,222 return_attr = True223 ),224 ("not Really the Model", MockModelAdmin.test_from_model)225 )226 227 def test_related_name(self):228 """229 Regression test for #13963230 """231 self.assertEqual(232 label_for_field('location', Event, return_attr=True),233 ('location', None),234 )235 self.assertEqual(236 label_for_field('event', Location, return_attr=True),237 ('awesome event', None),238 )239 self.assertEqual(240 label_for_field('guest', Event, return_attr=True),241 ('awesome guest', None),242 )243 244 def test_logentry_unicode(self):245 """246 Regression test for #15661247 """248 log_entry = admin.models.LogEntry()249 250 log_entry.action_flag = admin.models.ADDITION251 self.assertTrue(252 unicode(log_entry).startswith('Added ')253 )254 255 log_entry.action_flag = admin.models.CHANGE256 self.assertTrue(257 unicode(log_entry).startswith('Changed ')258 )259 260 log_entry.action_flag = admin.models.DELETION261 self.assertTrue(262 unicode(log_entry).startswith('Deleted ')263 )264 265 def test_safestring_in_field_label(self):266 # safestring should not be escaped267 class MyForm(forms.Form):268 text = forms.CharField(label=mark_safe('<i>text</i>'))269 cb = forms.BooleanField(label=mark_safe('<i>cb</i>'))270 271 form = MyForm()272 self.assertEqual(helpers.AdminField(form, 'text', is_first=False).label_tag(),273 '<label for="id_text" class="required inline"><i>text</i>:</label>')274 self.assertEqual(helpers.AdminField(form, 'cb', is_first=False).label_tag(),275 '<label for="id_cb" class="vCheckboxLabel required inline"><i>cb</i></label>')276 277 # normal strings needs to be escaped278 class MyForm(forms.Form):279 text = forms.CharField(label='&text')280 cb = forms.BooleanField(label='&cb')281 282 form = MyForm()283 self.assertEqual(helpers.AdminField(form, 'text', is_first=False).label_tag(),284 '<label for="id_text" class="required inline">&text:</label>')285 self.assertEqual(helpers.AdminField(form, 'cb', is_first=False).label_tag(),286 '<label for="id_cb" class="vCheckboxLabel required inline">&cb</label>') -
new file tests/regressiontests/admin_utils/models.py
diff --git a/tests/regressiontests/admin_utils/__init__.py b/tests/regressiontests/admin_utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/regressiontests/admin_utils/models.py b/tests/regressiontests/admin_utils/models.py new file mode 100644 index 0000000..0e81df3
- + 1 from django.db import models 2 3 4 class Article(models.Model): 5 """ 6 A simple Article model for testing 7 """ 8 site = models.ForeignKey('sites.Site', related_name="admin_articles") 9 title = models.CharField(max_length=100) 10 title2 = models.CharField(max_length=100, verbose_name="another name") 11 created = models.DateTimeField() 12 13 def test_from_model(self): 14 return "nothing" 15 16 def test_from_model_with_override(self): 17 return "nothing" 18 test_from_model_with_override.short_description = "not What you Expect" 19 20 class Count(models.Model): 21 num = models.PositiveSmallIntegerField() 22 parent = models.ForeignKey('self', null=True) 23 24 def __unicode__(self): 25 return unicode(self.num) 26 27 class Event(models.Model): 28 date = models.DateTimeField(auto_now_add=True) 29 30 class Location(models.Model): 31 event = models.OneToOneField(Event, verbose_name='awesome event') 32 33 class Guest(models.Model): 34 event = models.OneToOneField(Event) 35 name = models.CharField(max_length=255) 36 37 class Meta: 38 verbose_name = "awesome guest" -
new file tests/regressiontests/admin_utils/tests.py
diff --git a/tests/regressiontests/admin_utils/tests.py b/tests/regressiontests/admin_utils/tests.py new file mode 100644 index 0000000..d7eaabe
- + 1 from __future__ import absolute_import 2 3 from datetime import datetime 4 5 from django.conf import settings 6 from django.contrib import admin 7 from django.contrib.admin import helpers 8 from django.contrib.admin.utils import (display_for_field, label_for_field, 9 lookup_field, NestedObjects) 10 from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE 11 from django.contrib.sites.models import Site 12 from django.db import models, DEFAULT_DB_ALIAS 13 from django import forms 14 from django.test import TestCase 15 from django.utils import unittest 16 from django.utils.formats import localize 17 from django.utils.safestring import mark_safe 18 19 from .models import Article, Count, Event, Location 20 21 22 class NestedObjectsTests(TestCase): 23 """ 24 Tests for ``NestedObject`` utility collection. 25 26 """ 27 def setUp(self): 28 self.n = NestedObjects(using=DEFAULT_DB_ALIAS) 29 self.objs = [Count.objects.create(num=i) for i in range(5)] 30 31 def _check(self, target): 32 self.assertEqual(self.n.nested(lambda obj: obj.num), target) 33 34 def _connect(self, i, j): 35 self.objs[i].parent = self.objs[j] 36 self.objs[i].save() 37 38 def _collect(self, *indices): 39 self.n.collect([self.objs[i] for i in indices]) 40 41 def test_unrelated_roots(self): 42 self._connect(2, 1) 43 self._collect(0) 44 self._collect(1) 45 self._check([0, 1, [2]]) 46 47 def test_siblings(self): 48 self._connect(1, 0) 49 self._connect(2, 0) 50 self._collect(0) 51 self._check([0, [1, 2]]) 52 53 def test_non_added_parent(self): 54 self._connect(0, 1) 55 self._collect(0) 56 self._check([0]) 57 58 def test_cyclic(self): 59 self._connect(0, 2) 60 self._connect(1, 0) 61 self._connect(2, 1) 62 self._collect(0) 63 self._check([0, [1, [2]]]) 64 65 def test_queries(self): 66 self._connect(1, 0) 67 self._connect(2, 0) 68 # 1 query to fetch all children of 0 (1 and 2) 69 # 1 query to fetch all children of 1 and 2 (none) 70 # Should not require additional queries to populate the nested graph. 71 self.assertNumQueries(2, self._collect, 0) 72 73 class UtilTests(unittest.TestCase): 74 def test_values_from_lookup_field(self): 75 """ 76 Regression test for #12654: lookup_field 77 """ 78 SITE_NAME = 'example.com' 79 TITLE_TEXT = 'Some title' 80 CREATED_DATE = datetime.min 81 ADMIN_METHOD = 'admin method' 82 SIMPLE_FUNCTION = 'function' 83 INSTANCE_ATTRIBUTE = 'attr' 84 85 class MockModelAdmin(object): 86 def get_admin_value(self, obj): 87 return ADMIN_METHOD 88 89 simple_function = lambda obj: SIMPLE_FUNCTION 90 91 article = Article( 92 site=Site(domain=SITE_NAME), 93 title=TITLE_TEXT, 94 created=CREATED_DATE, 95 ) 96 article.non_field = INSTANCE_ATTRIBUTE 97 98 verifications = ( 99 ('site', SITE_NAME), 100 ('created', localize(CREATED_DATE)), 101 ('title', TITLE_TEXT), 102 ('get_admin_value', ADMIN_METHOD), 103 (simple_function, SIMPLE_FUNCTION), 104 ('test_from_model', article.test_from_model()), 105 ('non_field', INSTANCE_ATTRIBUTE) 106 ) 107 108 mock_admin = MockModelAdmin() 109 for name, value in verifications: 110 field, attr, resolved_value = lookup_field(name, article, mock_admin) 111 112 if field is not None: 113 resolved_value = display_for_field(resolved_value, field) 114 115 self.assertEqual(value, resolved_value) 116 117 def test_null_display_for_field(self): 118 """ 119 Regression test for #12550: display_for_field should handle None 120 value. 121 """ 122 display_value = display_for_field(None, models.CharField()) 123 self.assertEqual(display_value, EMPTY_CHANGELIST_VALUE) 124 125 display_value = display_for_field(None, models.CharField( 126 choices=( 127 (None, "test_none"), 128 ) 129 )) 130 self.assertEqual(display_value, "test_none") 131 132 display_value = display_for_field(None, models.DateField()) 133 self.assertEqual(display_value, EMPTY_CHANGELIST_VALUE) 134 135 display_value = display_for_field(None, models.TimeField()) 136 self.assertEqual(display_value, EMPTY_CHANGELIST_VALUE) 137 138 # Regression test for #13071: NullBooleanField has special 139 # handling. 140 display_value = display_for_field(None, models.NullBooleanField()) 141 expected = u'<img src="%sadmin/img/icon-unknown.gif" alt="None" />' % settings.STATIC_URL 142 self.assertEqual(display_value, expected) 143 144 display_value = display_for_field(None, models.DecimalField()) 145 self.assertEqual(display_value, EMPTY_CHANGELIST_VALUE) 146 147 display_value = display_for_field(None, models.FloatField()) 148 self.assertEqual(display_value, EMPTY_CHANGELIST_VALUE) 149 150 def test_label_for_field(self): 151 """ 152 Tests for label_for_field 153 """ 154 self.assertEqual( 155 label_for_field("title", Article), 156 "title" 157 ) 158 self.assertEqual( 159 label_for_field("title2", Article), 160 "another name" 161 ) 162 self.assertEqual( 163 label_for_field("title2", Article, return_attr=True), 164 ("another name", None) 165 ) 166 167 self.assertEqual( 168 label_for_field("__unicode__", Article), 169 "article" 170 ) 171 self.assertEqual( 172 label_for_field("__str__", Article), 173 "article" 174 ) 175 176 self.assertRaises( 177 AttributeError, 178 lambda: label_for_field("unknown", Article) 179 ) 180 181 def test_callable(obj): 182 return "nothing" 183 self.assertEqual( 184 label_for_field(test_callable, Article), 185 "Test callable" 186 ) 187 self.assertEqual( 188 label_for_field(test_callable, Article, return_attr=True), 189 ("Test callable", test_callable) 190 ) 191 192 self.assertEqual( 193 label_for_field("test_from_model", Article), 194 "Test from model" 195 ) 196 self.assertEqual( 197 label_for_field("test_from_model", Article, return_attr=True), 198 ("Test from model", Article.test_from_model) 199 ) 200 self.assertEqual( 201 label_for_field("test_from_model_with_override", Article), 202 "not What you Expect" 203 ) 204 205 self.assertEqual( 206 label_for_field(lambda x: "nothing", Article), 207 "--" 208 ) 209 210 class MockModelAdmin(object): 211 def test_from_model(self, obj): 212 return "nothing" 213 test_from_model.short_description = "not Really the Model" 214 215 self.assertEqual( 216 label_for_field("test_from_model", Article, model_admin=MockModelAdmin), 217 "not Really the Model" 218 ) 219 self.assertEqual( 220 label_for_field("test_from_model", Article, 221 model_admin = MockModelAdmin, 222 return_attr = True 223 ), 224 ("not Really the Model", MockModelAdmin.test_from_model) 225 ) 226 227 def test_related_name(self): 228 """ 229 Regression test for #13963 230 """ 231 self.assertEqual( 232 label_for_field('location', Event, return_attr=True), 233 ('location', None), 234 ) 235 self.assertEqual( 236 label_for_field('event', Location, return_attr=True), 237 ('awesome event', None), 238 ) 239 self.assertEqual( 240 label_for_field('guest', Event, return_attr=True), 241 ('awesome guest', None), 242 ) 243 244 def test_logentry_unicode(self): 245 """ 246 Regression test for #15661 247 """ 248 log_entry = admin.models.LogEntry() 249 250 log_entry.action_flag = admin.models.ADDITION 251 self.assertTrue( 252 unicode(log_entry).startswith('Added ') 253 ) 254 255 log_entry.action_flag = admin.models.CHANGE 256 self.assertTrue( 257 unicode(log_entry).startswith('Changed ') 258 ) 259 260 log_entry.action_flag = admin.models.DELETION 261 self.assertTrue( 262 unicode(log_entry).startswith('Deleted ') 263 ) 264 265 def test_safestring_in_field_label(self): 266 # safestring should not be escaped 267 class MyForm(forms.Form): 268 text = forms.CharField(label=mark_safe('<i>text</i>')) 269 cb = forms.BooleanField(label=mark_safe('<i>cb</i>')) 270 271 form = MyForm() 272 self.assertEqual(helpers.AdminField(form, 'text', is_first=False).label_tag(), 273 '<label for="id_text" class="required inline"><i>text</i>:</label>') 274 self.assertEqual(helpers.AdminField(form, 'cb', is_first=False).label_tag(), 275 '<label for="id_cb" class="vCheckboxLabel required inline"><i>cb</i></label>') 276 277 # normal strings needs to be escaped 278 class MyForm(forms.Form): 279 text = forms.CharField(label='&text') 280 cb = forms.BooleanField(label='&cb') 281 282 form = MyForm() 283 self.assertEqual(helpers.AdminField(form, 'text', is_first=False).label_tag(), 284 '<label for="id_text" class="required inline">&text:</label>') 285 self.assertEqual(helpers.AdminField(form, 'cb', is_first=False).label_tag(), 286 '<label for="id_cb" class="vCheckboxLabel required inline">&cb</label>') -
tests/regressiontests/admin_views/admin.py
diff --git a/tests/regressiontests/admin_views/admin.py b/tests/regressiontests/admin_views/admin.py index b10d178..a3692fb 100644
a b site.register(MainPrepopulated, MainPrepopulatedAdmin) 618 618 # related OneToOne object not registered in admin 619 619 # when deleting Book so as exercise all four troublesome (w.r.t escaping 620 620 # and calling force_unicode to avoid problems on Python 2.3) paths through 621 # contrib.admin.util 's get_deleted_objects function.621 # contrib.admin.utils's get_deleted_objects function. 622 622 site.register(Book, inlines=[ChapterInline]) 623 623 site.register(Promo) 624 624 site.register(ChapterXtra1, ChapterXtra1Admin) -
tests/regressiontests/admin_views/tests.py
diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index 760f31a..8264143 100755
a b from django.core.urlresolvers import reverse 15 15 from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME 16 16 from django.contrib.admin.models import LogEntry, DELETION 17 17 from django.contrib.admin.sites import LOGIN_FORM_KEY 18 from django.contrib.admin.util import quote18 from django.contrib.admin.utils import quote 19 19 from django.contrib.admin.views.main import IS_POPUP_VAR 20 20 from django.contrib.admin.tests import AdminSeleniumWebDriverTestCase 21 21 from django.contrib.auth import REDIRECT_FIELD_NAME