1 from django
.core
.exceptions
import ObjectDoesNotExist
2 from django
.db
import models
3 from django
.db
.models
.related
import RelatedObject
4 from django
.forms
.forms
import pretty_name
5 from django
.utils
import formats
6 from django
.utils
.html
import escape
7 from django
.utils
.safestring
import mark_safe
8 from django
.utils
.text
import capfirst
9 from django
.utils
.encoding
import force_unicode
, smart_unicode
, smart_str
10 from django
.utils
.translation
import ungettext
, ugettext
as _
11 from django
.core
.urlresolvers
import reverse
, NoReverseMatch
12 from django
.utils
.datastructures
import SortedDict
16 Ensure that primary key values do not confuse the admin URLs by escaping
17 any '/', '_' and ':' characters. Similar to urllib.quote, except that the
18 quoting is slightly different so that it doesn't get automatically
19 unquoted by the Web browser.
21 if not isinstance(s
, basestring
):
24 for i
in range(len(res
)):
26 if c
in """:/_#?;@&=+$,"<>%\\""":
27 res
[i
] = '_%02X' % ord(c
)
32 Undo the effects of quote(). Based heavily on urllib.unquote().
43 myappend(mychr(myatoi(item
[:2], 16)) + item
[2:])
50 def flatten_fieldsets(fieldsets
):
51 """Returns a list of field names from an admin fieldsets structure."""
53 for name
, opts
in fieldsets
:
54 for field
in opts
['fields']:
55 # type checking feels dirty, but it seems like the best way here
56 if type(field
) == tuple:
57 field_names
.extend(field
)
59 field_names
.append(field
)
62 def _format_callback(obj
, user
, admin_site
, levels_to_root
, perms_needed
):
63 has_admin
= obj
.__class
__ in admin_site
._registry
66 admin_url
= reverse('%s:%s_%s_change'
69 opts
.object_name
.lower()),
70 None, (quote(obj
._get
_pk
_val
()),))
71 except NoReverseMatch
:
72 admin_url
= '%s%s/%s/%s/' % ('../'*levels_to_root
,
74 opts
.object_name
.lower(),
75 quote(obj
._get
_pk
_val
()))
77 p
= '%s.%s' % (opts
.app_label
,
78 opts
.get_delete_permission())
79 if not user
.has_perm(p
):
80 perms_needed
.add(opts
.verbose_name
)
81 # Display a link to the admin page.
82 return mark_safe(u
'%s: <a href="%s">%s</a>' %
83 (escape(capfirst(opts
.verbose_name
)),
87 # Don't display link to edit, because it either has no
88 # admin or is edited inline.
89 return u
'%s: %s' % (capfirst(opts
.verbose_name
),
92 def get_deleted_objects(objs
, opts
, user
, admin_site
, levels_to_root
=4):
94 Find all objects related to ``objs`` that should also be
95 deleted. ``objs`` should be an iterable of objects.
97 Returns a nested list of strings suitable for display in the
98 template with the ``unordered_list`` filter.
100 `levels_to_root` defines the number of directories (../) to reach
101 the admin root path. In a change_view this is 4, in a change_list
104 This is for backwards compatibility since the options.delete_selected
105 method uses this function also from a change_list view.
106 This will not be used if we can reverse the URL.
108 collector
= NestedObjects()
110 # TODO using a private model API!
111 obj
._collect
_sub
_objects
(collector
)
115 to_delete
= collector
.nested(_format_callback
,
117 admin_site
=admin_site
,
118 levels_to_root
=levels_to_root
,
119 perms_needed
=perms_needed
)
121 return to_delete
, perms_needed
124 class NestedObjects(object):
126 A directed acyclic graph collection that exposes the add() API
127 expected by Model._collect_sub_objects and can present its data as
128 a nested list of objects.
132 # Use object keys of the form (model, pk) because actual model
133 # objects may not be unique
135 # maps object key to list of child keys
136 self
.children
= SortedDict()
138 # maps object key to parent key
139 self
.parents
= SortedDict()
141 # maps object key to actual object
142 self
.seen
= SortedDict()
144 def add(self
, model
, pk
, obj
,
145 parent_model
=None, parent_obj
=None, nullable
=False):
147 Add item ``obj`` to the graph. Returns True (and does nothing)
148 if the item has been seen already.
150 The ``parent_obj`` argument must already exist in the graph; if
151 not, it's ignored (but ``obj`` is still added with no
152 parent). In any case, Model._collect_sub_objects (for whom
153 this API exists) will never pass a parent that hasn't already
156 These restrictions in combination ensure the graph will remain
157 acyclic (but can have multiple roots).
159 ``model``, ``pk``, and ``parent_model`` arguments are ignored
160 in favor of the appropriate lookups on ``obj`` and
161 ``parent_obj``; unlike CollectedObjects, we can't maintain
162 independence from the knowledge that we're operating on model
163 instances, and we don't want to allow for inconsistency.
165 ``nullable`` arg is ignored: it doesn't affect how the tree of
166 collected objects should be nested for display.
168 model
, pk
= type(obj
), obj
._get
_pk
_val
()
170 # auto-created M2M models don't interest us
171 if model
._meta
.auto_created
:
178 self
.seen
.setdefault(key
, obj
)
180 if parent_obj
is not None:
181 parent_model
, parent_pk
= (type(parent_obj
),
182 parent_obj
._get
_pk
_val
())
183 parent_key
= (parent_model
, parent_pk
)
184 if parent_key
in self
.seen
:
185 self
.children
.setdefault(parent_key
, list()).append(key
)
186 self
.parents
.setdefault(key
, parent_key
)
188 def _nested(self
, key
, format_callback
=None, **kwargs
):
191 ret
= [format_callback(obj
, **kwargs
)]
196 for child
in self
.children
.get(key
, ()):
197 children
.extend(self
._nested
(child
, format_callback
, **kwargs
))
203 def nested(self
, format_callback
=None, **kwargs
):
205 Return the graph as a nested list.
207 Passes **kwargs back to the format_callback as kwargs.
211 for key
in self
.seen
.keys():
212 if key
not in self
.parents
:
213 roots
.extend(self
._nested
(key
, format_callback
, **kwargs
))
217 def model_format_dict(obj
):
219 Return a `dict` with keys 'verbose_name' and 'verbose_name_plural',
220 typically for use with string formatting.
222 `obj` may be a `Model` instance, `Model` subclass, or `QuerySet` instance.
225 if isinstance(obj
, (models
.Model
, models
.base
.ModelBase
)):
227 elif isinstance(obj
, models
.query
.QuerySet
):
228 opts
= obj
.model
._meta
232 'verbose_name': force_unicode(opts
.verbose_name
),
233 'verbose_name_plural': force_unicode(opts
.verbose_name_plural
)
236 def model_ngettext(obj
, n
=None):
238 Return the appropriate `verbose_name` or `verbose_name_plural` value for
239 `obj` depending on the count `n`.
241 `obj` may be a `Model` instance, `Model` subclass, or `QuerySet` instance.
242 If `obj` is a `QuerySet` instance, `n` is optional and the length of the
246 if isinstance(obj
, models
.query
.QuerySet
):
250 d
= model_format_dict(obj
)
251 singular
, plural
= d
["verbose_name"], d
["verbose_name_plural"]
252 return ungettext(singular
, plural
, n
or 0)
254 def lookup_field(name
, obj
, model_admin
=None):
257 f
= opts
.get_field(name
)
258 except models
.FieldDoesNotExist
:
259 # For non-field values, the value is either a method, property or
260 # returned via a callable.
264 elif (model_admin
is not None and hasattr(model_admin
, name
) and
265 not name
== '__str__' and not name
== '__unicode__'):
266 attr
= getattr(model_admin
, name
)
269 attr
= getattr(obj
, name
)
277 value
= getattr(obj
, name
)
278 return f
, attr
, value
280 def label_for_field(name
, model
, model_admin
=None, return_attr
=False):
283 field
= model
._meta
.get_field_by_name(name
)[0]
284 if isinstance(field
, RelatedObject
):
285 label
= field
.opts
.verbose_name
287 label
= field
.verbose_name
288 except models
.FieldDoesNotExist
:
289 if name
== "__unicode__":
290 label
= force_unicode(model
._meta
.verbose_name
)
291 elif name
== "__str__":
292 label
= smart_str(model
._meta
.verbose_name
)
296 elif model_admin
is not None and hasattr(model_admin
, name
):
297 attr
= getattr(model_admin
, name
)
298 elif hasattr(model
, name
):
299 attr
= getattr(model
, name
)
301 message
= "Unable to lookup '%s' on %s" % (name
, model
._meta
.object_name
)
303 message
+= " or %s" % (model_admin
.__name
__,)
304 raise AttributeError(message
)
306 if hasattr(attr
, "short_description"):
307 label
= attr
.short_description
309 if attr
.__name
__ == "<lambda>":
312 label
= pretty_name(attr
.__name
__)
314 label
= pretty_name(name
)
321 def display_for_field(value
, field
):
322 from django
.contrib
.admin
.templatetags
.admin_list
import _boolean_icon
323 from django
.contrib
.admin
.views
.main
import EMPTY_CHANGELIST_VALUE
325 if field
.flatchoices
:
326 return dict(field
.flatchoices
).get(value
, EMPTY_CHANGELIST_VALUE
)
327 # NullBooleanField needs special-case null-handling, so it comes
328 # before the general null test.
329 elif isinstance(field
, models
.BooleanField
) or isinstance(field
, models
.NullBooleanField
):
330 return _boolean_icon(value
)
332 return EMPTY_CHANGELIST_VALUE
333 elif isinstance(field
, models
.DateField
) or isinstance(field
, models
.TimeField
):
334 return formats
.localize(value
)
335 elif isinstance(field
, models
.DecimalField
):
336 return formats
.number_format(value
, field
.decimal_places
)
337 elif isinstance(field
, models
.FloatField
):
338 return formats
.number_format(value
)
340 return smart_unicode(value
)