Ticket #17627: 17627.clean.diff

File 17627.clean.diff, 23.1 KB (added by Ethan Jucovy, 12 years ago)

remove email gunk from patch, and fix deprecation schedule

  • AUTHORS

     
    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
     
    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>
  • docs/internals/deprecation.txt

     
    267267  in 1.4. The backward compatibility will be removed --
    268268  ``HttpRequest.raw_post_data`` will no longer work.
    269269
     270* ``django.contrib.admin.util`` has been moved to
     271  :mod:`django.contrib.admin.utils` in 1.4, for consistency with other
     272  ``utils`` modules in the Django codebase. The backwards compatibility
     273  will be removed -- references to ``django.contrib.admin.util`` will
     274  no longer work and should be changed to ``django.contrib.admin.utils``.
     275
    2702762.0
    271277---
    272278
  • django/contrib/admin/validation.py

     
    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/options.py

     
    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/actions.py

     
    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/helpers.py

     
    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/templatetags/admin_list.py

     
    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/views/main.py

     
    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/admin/filters.py

     
    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/util.py

     
    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 *
  • django/contrib/admin/models.py

     
    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/auth/admin.py

     
    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)
  • tests/regressiontests/admin_views/admin.py

     
    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

     
    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
Back to Top