PageRenderTime 61ms CodeModel.GetById 48ms app.highlight 10ms RepoModel.GetById 0ms app.codeStats 0ms

/packages/Django/django/contrib/admin/util.py

https://github.com/lmorchard/home-snippets-server-lib
Python | 335 lines | 326 code | 1 blank | 8 comment | 1 complexity | 7d1e5d641f73a3ebf70e588bf7bf3ab5 MD5 | raw file
  1from django.core.exceptions import ObjectDoesNotExist
  2from django.db import models
  3from django.forms.forms import pretty_name
  4from django.utils import formats
  5from django.utils.html import escape
  6from django.utils.safestring import mark_safe
  7from django.utils.text import capfirst
  8from django.utils.encoding import force_unicode, smart_unicode, smart_str
  9from django.utils.translation import ungettext, ugettext as _
 10from django.core.urlresolvers import reverse, NoReverseMatch
 11from django.utils.datastructures import SortedDict
 12
 13def quote(s):
 14    """
 15    Ensure that primary key values do not confuse the admin URLs by escaping
 16    any '/', '_' and ':' characters. Similar to urllib.quote, except that the
 17    quoting is slightly different so that it doesn't get automatically
 18    unquoted by the Web browser.
 19    """
 20    if not isinstance(s, basestring):
 21        return s
 22    res = list(s)
 23    for i in range(len(res)):
 24        c = res[i]
 25        if c in """:/_#?;@&=+$,"<>%\\""":
 26            res[i] = '_%02X' % ord(c)
 27    return ''.join(res)
 28
 29def unquote(s):
 30    """
 31    Undo the effects of quote(). Based heavily on urllib.unquote().
 32    """
 33    mychr = chr
 34    myatoi = int
 35    list = s.split('_')
 36    res = [list[0]]
 37    myappend = res.append
 38    del list[0]
 39    for item in list:
 40        if item[1:2]:
 41            try:
 42                myappend(mychr(myatoi(item[:2], 16)) + item[2:])
 43            except ValueError:
 44                myappend('_' + item)
 45        else:
 46            myappend('_' + item)
 47    return "".join(res)
 48
 49def flatten_fieldsets(fieldsets):
 50    """Returns a list of field names from an admin fieldsets structure."""
 51    field_names = []
 52    for name, opts in fieldsets:
 53        for field in opts['fields']:
 54            # type checking feels dirty, but it seems like the best way here
 55            if type(field) == tuple:
 56                field_names.extend(field)
 57            else:
 58                field_names.append(field)
 59    return field_names
 60
 61def _format_callback(obj, user, admin_site, levels_to_root, perms_needed):
 62    has_admin = obj.__class__ in admin_site._registry
 63    opts = obj._meta
 64    try:
 65        admin_url = reverse('%s:%s_%s_change'
 66                            % (admin_site.name,
 67                               opts.app_label,
 68                               opts.object_name.lower()),
 69                            None, (quote(obj._get_pk_val()),))
 70    except NoReverseMatch:
 71        admin_url = '%s%s/%s/%s/' % ('../'*levels_to_root,
 72                                     opts.app_label,
 73                                     opts.object_name.lower(),
 74                                     quote(obj._get_pk_val()))
 75    if has_admin:
 76        p = '%s.%s' % (opts.app_label,
 77                       opts.get_delete_permission())
 78        if not user.has_perm(p):
 79            perms_needed.add(opts.verbose_name)
 80        # Display a link to the admin page.
 81        return mark_safe(u'%s: <a href="%s">%s</a>' %
 82                         (escape(capfirst(opts.verbose_name)),
 83                          admin_url,
 84                          escape(obj)))
 85    else:
 86        # Don't display link to edit, because it either has no
 87        # admin or is edited inline.
 88        return u'%s: %s' % (capfirst(opts.verbose_name),
 89                            force_unicode(obj))
 90
 91def get_deleted_objects(objs, opts, user, admin_site, levels_to_root=4):
 92    """
 93    Find all objects related to ``objs`` that should also be
 94    deleted. ``objs`` should be an iterable of objects.
 95
 96    Returns a nested list of strings suitable for display in the
 97    template with the ``unordered_list`` filter.
 98
 99    `levels_to_root` defines the number of directories (../) to reach
100    the admin root path. In a change_view this is 4, in a change_list
101    view 2.
102
103    This is for backwards compatibility since the options.delete_selected
104    method uses this function also from a change_list view.
105    This will not be used if we can reverse the URL.
106    """
107    collector = NestedObjects()
108    for obj in objs:
109        # TODO using a private model API!
110        obj._collect_sub_objects(collector)
111
112    perms_needed = set()
113
114    to_delete = collector.nested(_format_callback,
115                                 user=user,
116                                 admin_site=admin_site,
117                                 levels_to_root=levels_to_root,
118                                 perms_needed=perms_needed)
119
120    return to_delete, perms_needed
121
122
123class NestedObjects(object):
124    """
125    A directed acyclic graph collection that exposes the add() API
126    expected by Model._collect_sub_objects and can present its data as
127    a nested list of objects.
128
129    """
130    def __init__(self):
131        # Use object keys of the form (model, pk) because actual model
132        # objects may not be unique
133
134        # maps object key to list of child keys
135        self.children = SortedDict()
136
137        # maps object key to parent key
138        self.parents = SortedDict()
139
140        # maps object key to actual object
141        self.seen = SortedDict()
142
143    def add(self, model, pk, obj,
144            parent_model=None, parent_obj=None, nullable=False):
145        """
146        Add item ``obj`` to the graph. Returns True (and does nothing)
147        if the item has been seen already.
148
149        The ``parent_obj`` argument must already exist in the graph; if
150        not, it's ignored (but ``obj`` is still added with no
151        parent). In any case, Model._collect_sub_objects (for whom
152        this API exists) will never pass a parent that hasn't already
153        been added itself.
154
155        These restrictions in combination ensure the graph will remain
156        acyclic (but can have multiple roots).
157
158        ``model``, ``pk``, and ``parent_model`` arguments are ignored
159        in favor of the appropriate lookups on ``obj`` and
160        ``parent_obj``; unlike CollectedObjects, we can't maintain
161        independence from the knowledge that we're operating on model
162        instances, and we don't want to allow for inconsistency.
163
164        ``nullable`` arg is ignored: it doesn't affect how the tree of
165        collected objects should be nested for display.
166        """
167        model, pk = type(obj), obj._get_pk_val()
168
169        # auto-created M2M models don't interest us
170        if model._meta.auto_created:
171            return True
172
173        key = model, pk
174
175        if key in self.seen:
176            return True
177        self.seen.setdefault(key, obj)
178
179        if parent_obj is not None:
180            parent_model, parent_pk = (type(parent_obj),
181                                       parent_obj._get_pk_val())
182            parent_key = (parent_model, parent_pk)
183            if parent_key in self.seen:
184                self.children.setdefault(parent_key, list()).append(key)
185                self.parents.setdefault(key, parent_key)
186
187    def _nested(self, key, format_callback=None, **kwargs):
188        obj = self.seen[key]
189        if format_callback:
190            ret = [format_callback(obj, **kwargs)]
191        else:
192            ret = [obj]
193
194        children = []
195        for child in self.children.get(key, ()):
196            children.extend(self._nested(child, format_callback, **kwargs))
197        if children:
198            ret.append(children)
199
200        return ret
201
202    def nested(self, format_callback=None, **kwargs):
203        """
204        Return the graph as a nested list.
205
206        Passes **kwargs back to the format_callback as kwargs.
207
208        """
209        roots = []
210        for key in self.seen.keys():
211            if key not in self.parents:
212                roots.extend(self._nested(key, format_callback, **kwargs))
213        return roots
214
215
216def model_format_dict(obj):
217    """
218    Return a `dict` with keys 'verbose_name' and 'verbose_name_plural',
219    typically for use with string formatting.
220
221    `obj` may be a `Model` instance, `Model` subclass, or `QuerySet` instance.
222
223    """
224    if isinstance(obj, (models.Model, models.base.ModelBase)):
225        opts = obj._meta
226    elif isinstance(obj, models.query.QuerySet):
227        opts = obj.model._meta
228    else:
229        opts = obj
230    return {
231        'verbose_name': force_unicode(opts.verbose_name),
232        'verbose_name_plural': force_unicode(opts.verbose_name_plural)
233    }
234
235def model_ngettext(obj, n=None):
236    """
237    Return the appropriate `verbose_name` or `verbose_name_plural` value for
238    `obj` depending on the count `n`.
239
240    `obj` may be a `Model` instance, `Model` subclass, or `QuerySet` instance.
241    If `obj` is a `QuerySet` instance, `n` is optional and the length of the
242    `QuerySet` is used.
243
244    """
245    if isinstance(obj, models.query.QuerySet):
246        if n is None:
247            n = obj.count()
248        obj = obj.model
249    d = model_format_dict(obj)
250    singular, plural = d["verbose_name"], d["verbose_name_plural"]
251    return ungettext(singular, plural, n or 0)
252
253def lookup_field(name, obj, model_admin=None):
254    opts = obj._meta
255    try:
256        f = opts.get_field(name)
257    except models.FieldDoesNotExist:
258        # For non-field values, the value is either a method, property or
259        # returned via a callable.
260        if callable(name):
261            attr = name
262            value = attr(obj)
263        elif (model_admin is not None and hasattr(model_admin, name) and
264          not name == '__str__' and not name == '__unicode__'):
265            attr = getattr(model_admin, name)
266            value = attr(obj)
267        else:
268            attr = getattr(obj, name)
269            if callable(attr):
270                value = attr()
271            else:
272                value = attr
273        f = None
274    else:
275        attr = None
276        value = getattr(obj, name)
277    return f, attr, value
278
279def label_for_field(name, model, model_admin=None, return_attr=False):
280    attr = None
281    try:
282        label = model._meta.get_field_by_name(name)[0].verbose_name
283    except models.FieldDoesNotExist:
284        if name == "__unicode__":
285            label = force_unicode(model._meta.verbose_name)
286        elif name == "__str__":
287            label = smart_str(model._meta.verbose_name)
288        else:
289            if callable(name):
290                attr = name
291            elif model_admin is not None and hasattr(model_admin, name):
292                attr = getattr(model_admin, name)
293            elif hasattr(model, name):
294                attr = getattr(model, name)
295            else:
296                message = "Unable to lookup '%s' on %s" % (name, model._meta.object_name)
297                if model_admin:
298                    message += " or %s" % (model_admin.__name__,)
299                raise AttributeError(message)
300
301            if hasattr(attr, "short_description"):
302                label = attr.short_description
303            elif callable(attr):
304                if attr.__name__ == "<lambda>":
305                    label = "--"
306                else:
307                    label = pretty_name(attr.__name__)
308            else:
309                label = pretty_name(name)
310    if return_attr:
311        return (label, attr)
312    else:
313        return label
314
315
316def display_for_field(value, field):
317    from django.contrib.admin.templatetags.admin_list import _boolean_icon
318    from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE
319
320    if field.flatchoices:
321        return dict(field.flatchoices).get(value, EMPTY_CHANGELIST_VALUE)
322    # NullBooleanField needs special-case null-handling, so it comes
323    # before the general null test.
324    elif isinstance(field, models.BooleanField) or isinstance(field, models.NullBooleanField):
325        return _boolean_icon(value)
326    elif value is None:
327        return EMPTY_CHANGELIST_VALUE
328    elif isinstance(field, models.DateField) or isinstance(field, models.TimeField):
329        return formats.localize(value)
330    elif isinstance(field, models.DecimalField):
331        return formats.number_format(value, field.decimal_places)
332    elif isinstance(field, models.FloatField):
333        return formats.number_format(value)
334    else:
335        return smart_unicode(value)