Hello site
[auf_framonde.git] / eggs / Django-1.4.5-py2.7.egg / django / contrib / admin / util.py
CommitLineData
01b54c21
MN
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