web/lib/django/contrib/admin/util.py
changeset 0 0d40e90630ef
child 29 cc9b7e14412b
equal deleted inserted replaced
-1:000000000000 0:0d40e90630ef
       
     1 from django.core.exceptions import ObjectDoesNotExist
       
     2 from django.db import models
       
     3 from django.utils.html import escape
       
     4 from django.utils.safestring import mark_safe
       
     5 from django.utils.text import capfirst
       
     6 from django.utils.encoding import force_unicode
       
     7 from django.utils.translation import ungettext, ugettext as _
       
     8 from django.core.urlresolvers import reverse, NoReverseMatch
       
     9 
       
    10 def quote(s):
       
    11     """
       
    12     Ensure that primary key values do not confuse the admin URLs by escaping
       
    13     any '/', '_' and ':' characters. Similar to urllib.quote, except that the
       
    14     quoting is slightly different so that it doesn't get automatically
       
    15     unquoted by the Web browser.
       
    16     """
       
    17     if not isinstance(s, basestring):
       
    18         return s
       
    19     res = list(s)
       
    20     for i in range(len(res)):
       
    21         c = res[i]
       
    22         if c in """:/_#?;@&=+$,"<>%\\""":
       
    23             res[i] = '_%02X' % ord(c)
       
    24     return ''.join(res)
       
    25 
       
    26 def unquote(s):
       
    27     """
       
    28     Undo the effects of quote(). Based heavily on urllib.unquote().
       
    29     """
       
    30     mychr = chr
       
    31     myatoi = int
       
    32     list = s.split('_')
       
    33     res = [list[0]]
       
    34     myappend = res.append
       
    35     del list[0]
       
    36     for item in list:
       
    37         if item[1:2]:
       
    38             try:
       
    39                 myappend(mychr(myatoi(item[:2], 16)) + item[2:])
       
    40             except ValueError:
       
    41                 myappend('_' + item)
       
    42         else:
       
    43             myappend('_' + item)
       
    44     return "".join(res)
       
    45 
       
    46 def flatten_fieldsets(fieldsets):
       
    47     """Returns a list of field names from an admin fieldsets structure."""
       
    48     field_names = []
       
    49     for name, opts in fieldsets:
       
    50         for field in opts['fields']:
       
    51             # type checking feels dirty, but it seems like the best way here
       
    52             if type(field) == tuple:
       
    53                 field_names.extend(field)
       
    54             else:
       
    55                 field_names.append(field)
       
    56     return field_names
       
    57 
       
    58 def _nest_help(obj, depth, val):
       
    59     current = obj
       
    60     for i in range(depth):
       
    61         current = current[-1]
       
    62     current.append(val)
       
    63 
       
    64 def get_change_view_url(app_label, module_name, pk, admin_site, levels_to_root):
       
    65     """
       
    66     Returns the url to the admin change view for the given app_label,
       
    67     module_name and primary key.
       
    68     """
       
    69     try:
       
    70         return reverse('%sadmin_%s_%s_change' % (admin_site.name, app_label, module_name), None, (pk,))
       
    71     except NoReverseMatch:
       
    72         return '%s%s/%s/%s/' % ('../'*levels_to_root, app_label, module_name, pk)
       
    73 
       
    74 def get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current_depth, admin_site, levels_to_root=4):
       
    75     """
       
    76     Helper function that recursively populates deleted_objects.
       
    77 
       
    78     `levels_to_root` defines the number of directories (../) to reach the
       
    79     admin root path. In a change_view this is 4, in a change_list view 2.
       
    80 
       
    81     This is for backwards compatibility since the options.delete_selected
       
    82     method uses this function also from a change_list view.
       
    83     This will not be used if we can reverse the URL.
       
    84     """
       
    85     nh = _nest_help # Bind to local variable for performance
       
    86     if current_depth > 16:
       
    87         return # Avoid recursing too deep.
       
    88     opts_seen = []
       
    89     for related in opts.get_all_related_objects():
       
    90         has_admin = related.model in admin_site._registry
       
    91         if related.opts in opts_seen:
       
    92             continue
       
    93         opts_seen.append(related.opts)
       
    94         rel_opts_name = related.get_accessor_name()
       
    95         if isinstance(related.field.rel, models.OneToOneRel):
       
    96             try:
       
    97                 sub_obj = getattr(obj, rel_opts_name)
       
    98             except ObjectDoesNotExist:
       
    99                 pass
       
   100             else:
       
   101                 if has_admin:
       
   102                     p = '%s.%s' % (related.opts.app_label, related.opts.get_delete_permission())
       
   103                     if not user.has_perm(p):
       
   104                         perms_needed.add(related.opts.verbose_name)
       
   105                         # We don't care about populating deleted_objects now.
       
   106                         continue
       
   107                 if not has_admin:
       
   108                     # Don't display link to edit, because it either has no
       
   109                     # admin or is edited inline.
       
   110                     nh(deleted_objects, current_depth,
       
   111                         [u'%s: %s' % (capfirst(related.opts.verbose_name), force_unicode(sub_obj)), []])
       
   112                 else:
       
   113                     # Display a link to the admin page.
       
   114                     nh(deleted_objects, current_depth, [mark_safe(u'%s: <a href="%s">%s</a>' %
       
   115                         (escape(capfirst(related.opts.verbose_name)),
       
   116                         get_change_view_url(related.opts.app_label,
       
   117                                             related.opts.object_name.lower(),
       
   118                                             sub_obj._get_pk_val(),
       
   119                                             admin_site,
       
   120                                             levels_to_root),
       
   121                         escape(sub_obj))), []])
       
   122                 get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2, admin_site)
       
   123         else:
       
   124             has_related_objs = False
       
   125             for sub_obj in getattr(obj, rel_opts_name).all():
       
   126                 has_related_objs = True
       
   127                 if not has_admin:
       
   128                     # Don't display link to edit, because it either has no
       
   129                     # admin or is edited inline.
       
   130                     nh(deleted_objects, current_depth,
       
   131                         [u'%s: %s' % (capfirst(related.opts.verbose_name), force_unicode(sub_obj)), []])
       
   132                 else:
       
   133                     # Display a link to the admin page.
       
   134                     nh(deleted_objects, current_depth, [mark_safe(u'%s: <a href="%s">%s</a>' %
       
   135                         (escape(capfirst(related.opts.verbose_name)),
       
   136                         get_change_view_url(related.opts.app_label,
       
   137                                             related.opts.object_name.lower(),
       
   138                                             sub_obj._get_pk_val(),
       
   139                                             admin_site,
       
   140                                             levels_to_root),
       
   141                         escape(sub_obj))), []])
       
   142                 get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2, admin_site)
       
   143             # If there were related objects, and the user doesn't have
       
   144             # permission to delete them, add the missing perm to perms_needed.
       
   145             if has_admin and has_related_objs:
       
   146                 p = '%s.%s' % (related.opts.app_label, related.opts.get_delete_permission())
       
   147                 if not user.has_perm(p):
       
   148                     perms_needed.add(related.opts.verbose_name)
       
   149     for related in opts.get_all_related_many_to_many_objects():
       
   150         has_admin = related.model in admin_site._registry
       
   151         if related.opts in opts_seen:
       
   152             continue
       
   153         opts_seen.append(related.opts)
       
   154         rel_opts_name = related.get_accessor_name()
       
   155         has_related_objs = False
       
   156 
       
   157         # related.get_accessor_name() could return None for symmetrical relationships
       
   158         if rel_opts_name:
       
   159             rel_objs = getattr(obj, rel_opts_name, None)
       
   160             if rel_objs:
       
   161                 has_related_objs = True
       
   162 
       
   163         if has_related_objs:
       
   164             for sub_obj in rel_objs.all():
       
   165                 if not has_admin:
       
   166                     # Don't display link to edit, because it either has no
       
   167                     # admin or is edited inline.
       
   168                     nh(deleted_objects, current_depth, [_('One or more %(fieldname)s in %(name)s: %(obj)s') % \
       
   169                         {'fieldname': force_unicode(related.field.verbose_name), 'name': force_unicode(related.opts.verbose_name), 'obj': escape(sub_obj)}, []])
       
   170                 else:
       
   171                     # Display a link to the admin page.
       
   172                     nh(deleted_objects, current_depth, [
       
   173                         mark_safe((_('One or more %(fieldname)s in %(name)s:') % {'fieldname': escape(force_unicode(related.field.verbose_name)), 'name': escape(force_unicode(related.opts.verbose_name))}) + \
       
   174                         (u' <a href="%s">%s</a>' % \
       
   175                             (get_change_view_url(related.opts.app_label,
       
   176                                                  related.opts.object_name.lower(),
       
   177                                                  sub_obj._get_pk_val(),
       
   178                                                  admin_site,
       
   179                                                  levels_to_root),
       
   180                             escape(sub_obj)))), []])
       
   181         # If there were related objects, and the user doesn't have
       
   182         # permission to change them, add the missing perm to perms_needed.
       
   183         if has_admin and has_related_objs:
       
   184             p = u'%s.%s' % (related.opts.app_label, related.opts.get_change_permission())
       
   185             if not user.has_perm(p):
       
   186                 perms_needed.add(related.opts.verbose_name)
       
   187 
       
   188 def model_format_dict(obj):
       
   189     """
       
   190     Return a `dict` with keys 'verbose_name' and 'verbose_name_plural',
       
   191     typically for use with string formatting.
       
   192 
       
   193     `obj` may be a `Model` instance, `Model` subclass, or `QuerySet` instance.
       
   194 
       
   195     """
       
   196     if isinstance(obj, (models.Model, models.base.ModelBase)):
       
   197         opts = obj._meta
       
   198     elif isinstance(obj, models.query.QuerySet):
       
   199         opts = obj.model._meta
       
   200     else:
       
   201         opts = obj
       
   202     return {
       
   203         'verbose_name': force_unicode(opts.verbose_name),
       
   204         'verbose_name_plural': force_unicode(opts.verbose_name_plural)
       
   205     }
       
   206 
       
   207 def model_ngettext(obj, n=None):
       
   208     """
       
   209     Return the appropriate `verbose_name` or `verbose_name_plural` value for
       
   210     `obj` depending on the count `n`.
       
   211 
       
   212     `obj` may be a `Model` instance, `Model` subclass, or `QuerySet` instance.
       
   213     If `obj` is a `QuerySet` instance, `n` is optional and the length of the
       
   214     `QuerySet` is used.
       
   215 
       
   216     """
       
   217     if isinstance(obj, models.query.QuerySet):
       
   218         if n is None:
       
   219             n = obj.count()
       
   220         obj = obj.model
       
   221     d = model_format_dict(obj)
       
   222     singular, plural = d["verbose_name"], d["verbose_name_plural"]
       
   223     return ungettext(singular, plural, n or 0)