Code

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 lukegb, 2 years ago)

Updated deprecation timeline to 1.7

  • AUTHORS

    diff --git a/AUTHORS b/AUTHORS
    index 5fa21d3..faa6126 100644
    a b answer newbie questions, and generally made Django that much better: 
    218218    David Gouldin <dgouldin@gmail.com> 
    219219    pradeep.gowda@gmail.com 
    220220    Collin Grady <collin@collingrady.com> 
     221    Luke Granger-Brown <django@lukegb.com> 
    221222    Gabriel Grant <g@briel.ca> 
    222223    Simon Greenhill <dev@simon.net.nz> 
    223224    Owen Griffiths 
    answer newbie questions, and generally made Django that much better: 
    295296    Cameron Knight (ckknight) 
    296297    Nena Kojadin <nena@kiberpipa.org> 
    297298    Igor Kolar <ike@email.si> 
     299    Wiktor Kołodziej 
    298300    Tomáš Kopeček <permonik@m6.cz> 
    299301    Gasper Koren 
    300302    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. 
    44 
    55from django.core.exceptions import PermissionDenied 
    66from django.contrib.admin import helpers 
    7 from django.contrib.admin.util import get_deleted_objects, model_ngettext 
     7from django.contrib.admin.utils import get_deleted_objects, model_ngettext 
    88from django.db import router 
    99from django.template.response import TemplateResponse 
    1010from 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 
    1212from django.utils.encoding import smart_unicode 
    1313from django.utils.translation import ugettext_lazy as _ 
    1414 
    15 from django.contrib.admin.util import (get_model_from_relation, 
     15from django.contrib.admin.utils import (get_model_from_relation, 
    1616    reverse_field_path, get_limit_choices_to_from_path, prepare_lookup_value) 
    1717 
    1818class 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  
    11from django import forms 
    2 from django.contrib.admin.util import (flatten_fieldsets, lookup_field, 
     2from django.contrib.admin.utils import (flatten_fieldsets, lookup_field, 
    33    display_for_field, label_for_field, help_text_for_field) 
    44from django.contrib.admin.templatetags.admin_static import static 
    55from 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  
    11from django.db import models 
    22from django.contrib.contenttypes.models import ContentType 
    33from django.contrib.auth.models import User 
    4 from django.contrib.admin.util import quote 
     4from django.contrib.admin.utils import quote 
    55from django.utils.translation import ugettext_lazy as _ 
    66from django.utils.encoding import smart_unicode 
    77from 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, 
    66    inlineformset_factory, BaseInlineFormSet) 
    77from django.contrib.contenttypes.models import ContentType 
    88from django.contrib.admin import widgets, helpers 
    9 from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_format_dict 
     9from django.contrib.admin.utils import unquote, flatten_fieldsets, get_deleted_objects, model_format_dict 
    1010from django.contrib.admin.templatetags.admin_static import static 
    1111from django.contrib import messages 
    1212from 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  
    11import datetime 
    22 
    3 from django.contrib.admin.util import lookup_field, display_for_field, label_for_field 
     3from django.contrib.admin.utils import lookup_field, display_for_field, label_for_field 
    44from django.contrib.admin.views.main import (ALL_VAR, EMPTY_CHANGELIST_VALUE, 
    55    ORDER_VAR, PAGE_VAR, SEARCH_VAR) 
    66from 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 
     1import warnings 
    142 
    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 
     3warnings.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) 
    276 
    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 
     7from 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
    - +  
     1from django.db import models 
     2from django.db.models.sql.constants import LOOKUP_SEP 
     3from django.db.models.deletion import Collector 
     4from django.db.models.related import RelatedObject 
     5from django.forms.forms import pretty_name 
     6from django.utils import formats 
     7from django.utils.html import escape 
     8from django.utils.safestring import mark_safe 
     9from django.utils.text import capfirst 
     10from django.utils import timezone 
     11from django.utils.encoding import force_unicode, smart_unicode, smart_str 
     12from django.utils.translation import ungettext 
     13from django.core.urlresolvers import reverse 
     14 
     15def 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 
     28def 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 
     43def 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 
     60def 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 
     81def 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 
     94def 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 
     139class 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 
     190def 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 
     210def 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 
     229def 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 
     256def 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 
     304def 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 
     312def 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 
     336class NotRelationField(Exception): 
     337    pass 
     338 
     339 
     340def 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 
     349def 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 
     379def 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 
     399def 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 
     408def 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 
    44from django.forms.models import (BaseModelForm, BaseModelFormSet, fields_for_model, 
    55    _get_foreign_key) 
    66from django.contrib.admin import ListFilter, FieldListFilter 
    7 from django.contrib.admin.util import get_fields_from_path, NotRelationField 
     7from django.contrib.admin.utils import get_fields_from_path, NotRelationField 
    88from django.contrib.admin.options import (flatten_fieldsets, BaseModelAdmin, 
    99    HORIZONTAL, VERTICAL) 
    1010 
  • 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 
    1010 
    1111from django.contrib.admin import FieldListFilter 
    1212from django.contrib.admin.options import IncorrectLookupParameters 
    13 from django.contrib.admin.util import (quote, get_fields_from_path, 
     13from django.contrib.admin.utils import (quote, get_fields_from_path, 
    1414    lookup_needs_distinct, prepare_lookup_value) 
    1515 
    1616# 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): 
    7171        if obj is None: 
    7272            defaults.update({ 
    7373                'form': self.add_form, 
    74                 'fields': admin.util.flatten_fieldsets(self.add_fieldsets), 
     74                'fields': admin.utils.flatten_fieldsets(self.add_fieldsets), 
    7575            }) 
    7676        defaults.update(kwargs) 
    7777        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. 
    267267  in 1.4. The backward compatibility will be removed -- 
    268268  ``HttpRequest.raw_post_data`` will no longer work. 
    269269 
     2701.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 
    2702782.0 
    271279--- 
    272280 
  • 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 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" 
  • 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_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.util 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">&amp;text:</label>') 
    285         self.assertEqual(helpers.AdminField(form, 'cb', is_first=False).label_tag(), 
    286                          '<label for="id_cb" class="vCheckboxLabel required inline">&amp;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
    - +  
     1from django.db import models 
     2 
     3 
     4class 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 
     20class 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 
     27class Event(models.Model): 
     28    date = models.DateTimeField(auto_now_add=True) 
     29 
     30class Location(models.Model): 
     31    event = models.OneToOneField(Event, verbose_name='awesome event') 
     32 
     33class 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
    - +  
     1from __future__ import absolute_import 
     2 
     3from datetime import datetime 
     4 
     5from django.conf import settings 
     6from django.contrib import admin 
     7from django.contrib.admin import helpers 
     8from django.contrib.admin.utils import (display_for_field, label_for_field, 
     9    lookup_field, NestedObjects) 
     10from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE 
     11from django.contrib.sites.models import Site 
     12from django.db import models, DEFAULT_DB_ALIAS 
     13from django import forms 
     14from django.test import TestCase 
     15from django.utils import unittest 
     16from django.utils.formats import localize 
     17from django.utils.safestring import mark_safe 
     18 
     19from .models import Article, Count, Event, Location 
     20 
     21 
     22class 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 
     73class 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">&amp;text:</label>') 
     285        self.assertEqual(helpers.AdminField(form, 'cb', is_first=False).label_tag(), 
     286                         '<label for="id_cb" class="vCheckboxLabel required inline">&amp;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) 
    618618#     related OneToOne object not registered in admin 
    619619# when deleting Book so as exercise all four troublesome (w.r.t escaping 
    620620# 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. 
    622622site.register(Book, inlines=[ChapterInline]) 
    623623site.register(Promo) 
    624624site.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 
    1515from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME 
    1616from django.contrib.admin.models import LogEntry, DELETION 
    1717from django.contrib.admin.sites import LOGIN_FORM_KEY 
    18 from django.contrib.admin.util import quote 
     18from django.contrib.admin.utils import quote 
    1919from django.contrib.admin.views.main import IS_POPUP_VAR 
    2020from django.contrib.admin.tests import AdminSeleniumWebDriverTestCase 
    2121from django.contrib.auth import REDIRECT_FIELD_NAME