Negligible refactoring in django.core.meta.fields and django.contrib.admin.views...
[fdr-django.git] / django / contrib / admin / views / main.py
bloba3a7ddfb18f3f3e9ee219d7fb4192a4ef2443c1d
1 # Generic admin views.
2 from django.contrib.admin.views.decorators import staff_member_required
3 from django.contrib.admin.filterspecs import FilterSpec
4 from django.core import formfields, meta, template
5 from django.core.template import loader
6 from django.core.meta.fields import BoundField, BoundFieldLine, BoundFieldSet
7 from django.core.exceptions import Http404, ImproperlyConfigured, ObjectDoesNotExist, PermissionDenied
8 from django.core.extensions import DjangoContext as Context
9 from django.core.extensions import get_object_or_404, render_to_response
10 from django.core.paginator import ObjectPaginator, InvalidPage
11 from django.conf.settings import ADMIN_MEDIA_PREFIX
12 try:
13 from django.models.admin import log
14 except ImportError:
15 raise ImproperlyConfigured, "You don't have 'django.contrib.admin' in INSTALLED_APPS."
16 from django.utils.html import strip_tags
17 from django.utils.httpwrappers import HttpResponse, HttpResponseRedirect
18 from django.utils.text import capfirst, get_text_list
19 from django.utils import dateformat
20 from django.utils.dates import MONTHS
21 from django.utils.html import escape
22 import operator
24 # The system will display a "Show all" link only if the total result count
25 # is less than or equal to this setting.
26 MAX_SHOW_ALL_ALLOWED = 200
28 DEFAULT_RESULTS_PER_PAGE = 100
30 ALL_VAR = 'all'
31 ORDER_VAR = 'o'
32 ORDER_TYPE_VAR = 'ot'
33 PAGE_VAR = 'p'
34 SEARCH_VAR = 'q'
35 IS_POPUP_VAR = 'pop'
37 # Text to display within changelist table cells if the value is blank.
38 EMPTY_CHANGELIST_VALUE = '(None)'
40 def _get_mod_opts(app_label, module_name):
41 "Helper function that returns a tuple of (module, opts), raising Http404 if necessary."
42 try:
43 mod = meta.get_module(app_label, module_name)
44 except ImportError:
45 raise Http404 # Invalid app or module name. Maybe it's not in INSTALLED_APPS.
46 opts = mod.Klass._meta
47 if not opts.admin:
48 raise Http404 # This object is valid but has no admin interface.
49 return mod, opts
51 def index(request):
52 return render_to_response('admin/index', {'title': _('Site administration')}, context_instance=Context(request))
53 index = staff_member_required(index)
55 class IncorrectLookupParameters(Exception):
56 pass
58 class ChangeList(object):
59 def __init__(self, request, app_label, module_name):
60 self.get_modules_and_options(app_label, module_name, request)
61 self.get_search_parameters(request)
62 self.get_ordering()
63 self.query = request.GET.get(SEARCH_VAR, '')
64 self.get_lookup_params()
65 self.get_results(request)
66 self.title = (self.is_popup
67 and _('Select %s') % self.opts.verbose_name
68 or _('Select %s to change') % self.opts.verbose_name)
69 self.get_filters(request)
70 self.pk_attname = self.lookup_opts.pk.attname
72 def get_filters(self, request):
73 self.filter_specs = []
74 if self.lookup_opts.admin.list_filter and not self.opts.one_to_one_field:
75 filter_fields = [self.lookup_opts.get_field(field_name) \
76 for field_name in self.lookup_opts.admin.list_filter]
77 for f in filter_fields:
78 spec = FilterSpec.create(f, request, self.params)
79 if spec and spec.has_output():
80 self.filter_specs.append(spec)
81 self.has_filters = bool(self.filter_specs)
83 def get_query_string(self, new_params={}, remove=[]):
84 p = self.params.copy()
85 for r in remove:
86 for k in p.keys():
87 if k.startswith(r):
88 del p[k]
89 for k, v in new_params.items():
90 if p.has_key(k) and v is None:
91 del p[k]
92 elif v is not None:
93 p[k] = v
94 return '?' + '&'.join(['%s=%s' % (k, v) for k, v in p.items()]).replace(' ', '%20')
96 def get_modules_and_options(self, app_label, module_name, request):
97 self.mod, self.opts = _get_mod_opts(app_label, module_name)
98 if not request.user.has_perm(app_label + '.' + self.opts.get_change_permission()):
99 raise PermissionDenied
101 self.lookup_mod, self.lookup_opts = self.mod, self.opts
103 def get_search_parameters(self, request):
104 # Get search parameters from the query string.
105 try:
106 self.page_num = int(request.GET.get(PAGE_VAR, 0))
107 except ValueError:
108 self.page_num = 0
109 self.show_all = request.GET.has_key(ALL_VAR)
110 self.is_popup = request.GET.has_key(IS_POPUP_VAR)
111 self.params = dict(request.GET.items())
112 if self.params.has_key(PAGE_VAR):
113 del self.params[PAGE_VAR]
115 def get_results(self, request):
116 lookup_mod, lookup_params, show_all, page_num = \
117 self.lookup_mod, self.lookup_params, self.show_all, self.page_num
118 # Get the results.
119 try:
120 paginator = ObjectPaginator(lookup_mod, lookup_params, DEFAULT_RESULTS_PER_PAGE)
121 # Naked except! Because we don't have any other way of validating "params".
122 # They might be invalid if the keyword arguments are incorrect, or if the
123 # values are not in the correct type (which would result in a database
124 # error).
125 except:
126 raise IncorrectLookupParameters()
128 # Get the total number of objects, with no filters applied.
129 real_lookup_params = lookup_params.copy()
130 del real_lookup_params['order_by']
131 if real_lookup_params:
132 full_result_count = lookup_mod.get_count()
133 else:
134 full_result_count = paginator.hits
135 del real_lookup_params
136 result_count = paginator.hits
137 can_show_all = result_count <= MAX_SHOW_ALL_ALLOWED
138 multi_page = result_count > DEFAULT_RESULTS_PER_PAGE
140 # Get the list of objects to display on this page.
141 if (show_all and can_show_all) or not multi_page:
142 result_list = lookup_mod.get_list(**lookup_params)
143 else:
144 try:
145 result_list = paginator.get_page(page_num)
146 except InvalidPage:
147 result_list = []
148 (self.result_count, self.full_result_count, self.result_list,
149 self.can_show_all, self.multi_page, self.paginator) = (result_count,
150 full_result_count, result_list, can_show_all, multi_page, paginator )
152 def url_for_result(self, result):
153 return "%s/" % getattr(result, self.pk_attname)
155 def get_ordering(self):
156 lookup_opts, params = self.lookup_opts, self.params
157 # For ordering, first check the "ordering" parameter in the admin options,
158 # then check the object's default ordering. If neither of those exist,
159 # order descending by ID by default. Finally, look for manually-specified
160 # ordering from the query string.
161 ordering = lookup_opts.admin.ordering or lookup_opts.ordering or ['-' + lookup_opts.pk.name]
163 # Normalize it to new-style ordering.
164 ordering = meta.handle_legacy_orderlist(ordering)
166 if ordering[0].startswith('-'):
167 order_field, order_type = ordering[0][1:], 'desc'
168 else:
169 order_field, order_type = ordering[0], 'asc'
170 if params.has_key(ORDER_VAR):
171 try:
172 try:
173 f = lookup_opts.get_field(lookup_opts.admin.list_display[int(params[ORDER_VAR])])
174 except meta.FieldDoesNotExist:
175 pass
176 else:
177 if not isinstance(f.rel, meta.ManyToOne) or not f.null:
178 order_field = f.name
179 except (IndexError, ValueError):
180 pass # Invalid ordering specified. Just use the default.
181 if params.has_key(ORDER_TYPE_VAR) and params[ORDER_TYPE_VAR] in ('asc', 'desc'):
182 order_type = params[ORDER_TYPE_VAR]
183 self.order_field, self.order_type = order_field, order_type
185 def get_lookup_params(self):
186 # Prepare the lookup parameters for the API lookup.
187 (params, order_field, lookup_opts, order_type, opts, query) = \
188 (self.params, self.order_field, self.lookup_opts, self.order_type, self.opts, self.query)
190 lookup_params = params.copy()
191 for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR):
192 if lookup_params.has_key(i):
193 del lookup_params[i]
194 # If the order-by field is a field with a relationship, order by the value
195 # in the related table.
196 lookup_order_field = order_field
197 try:
198 f = lookup_opts.get_field(order_field)
199 except meta.FieldDoesNotExist:
200 pass
201 else:
202 if isinstance(lookup_opts.get_field(order_field).rel, meta.ManyToOne):
203 f = lookup_opts.get_field(order_field)
204 rel_ordering = f.rel.to.ordering and f.rel.to.ordering[0] or f.rel.to.pk.column
205 lookup_order_field = '%s.%s' % (f.rel.to.db_table, rel_ordering)
206 # Use select_related if one of the list_display options is a field with a
207 # relationship.
208 if lookup_opts.admin.list_select_related:
209 lookup_params['select_related'] = True
210 else:
211 for field_name in lookup_opts.admin.list_display:
212 try:
213 f = lookup_opts.get_field(field_name)
214 except meta.FieldDoesNotExist:
215 pass
216 else:
217 if isinstance(f.rel, meta.ManyToOne):
218 lookup_params['select_related'] = True
219 break
220 lookup_params['order_by'] = ((order_type == 'desc' and '-' or '') + lookup_order_field,)
221 if lookup_opts.admin.search_fields and query:
222 complex_queries = []
223 for bit in query.split():
224 or_queries = []
225 for field_name in lookup_opts.admin.search_fields:
226 or_queries.append(meta.Q(**{'%s__icontains' % field_name: bit}))
227 complex_queries.append(reduce(operator.or_, or_queries))
228 lookup_params['complex'] = reduce(operator.and_, complex_queries)
229 if opts.one_to_one_field:
230 lookup_params.update(opts.one_to_one_field.rel.limit_choices_to)
231 self.lookup_params = lookup_params
233 def change_list(request, app_label, module_name):
234 try:
235 cl = ChangeList(request, app_label, module_name)
236 except IncorrectLookupParameters:
237 return HttpResponseRedirect(request.path)
239 c = Context(request, {
240 'title': cl.title,
241 'is_popup': cl.is_popup,
242 'cl' : cl
244 c.update({'has_add_permission': c['perms'][app_label][cl.opts.get_add_permission()]}),
245 return render_to_response(['admin/%s/%s/change_list' % (app_label, cl.opts.object_name.lower()),
246 'admin/%s/change_list' % app_label,
247 'admin/change_list'], context_instance=c)
248 change_list = staff_member_required(change_list)
250 use_raw_id_admin = lambda field: isinstance(field.rel, (meta.ManyToOne, meta.ManyToMany)) and field.rel.raw_id_admin
252 def get_javascript_imports(opts,auto_populated_fields, ordered_objects, field_sets):
253 # Put in any necessary JavaScript imports.
254 js = ['js/core.js', 'js/admin/RelatedObjectLookups.js']
255 if auto_populated_fields:
256 js.append('js/urlify.js')
257 if opts.has_field_type(meta.DateTimeField) or opts.has_field_type(meta.TimeField) or opts.has_field_type(meta.DateField):
258 js.extend(['js/calendar.js', 'js/admin/DateTimeShortcuts.js'])
259 if ordered_objects:
260 js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js'])
261 if opts.admin.js:
262 js.extend(opts.admin.js)
263 seen_collapse = False
264 for field_set in field_sets:
265 if not seen_collapse and 'collapse' in field_set.classes:
266 seen_collapse = True
267 js.append('js/admin/CollapsedFieldsets.js' )
269 for field_line in field_set:
270 try:
271 for f in field_line:
272 if f.rel and isinstance(f, meta.ManyToManyField) and f.rel.filter_interface:
273 js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js'])
274 raise StopIteration
275 except StopIteration:
276 break
277 return js
279 class AdminBoundField(BoundField):
280 def __init__(self, field, field_mapping, original):
281 super(AdminBoundField, self).__init__(field, field_mapping, original)
283 self.element_id = self.form_fields[0].get_id()
284 self.has_label_first = not isinstance(self.field, meta.BooleanField)
285 self.raw_id_admin = use_raw_id_admin(field)
286 self.is_date_time = isinstance(field, meta.DateTimeField)
287 self.is_file_field = isinstance(field, meta.FileField)
288 self.needs_add_label = field.rel and isinstance(field.rel, meta.ManyToOne) or isinstance(field.rel, meta.ManyToMany) and field.rel.to.admin
289 self.hidden = isinstance(self.field, meta.AutoField)
290 self.first = False
292 classes = []
293 if self.raw_id_admin:
294 classes.append('nowrap')
295 if max([bool(f.errors()) for f in self.form_fields]):
296 classes.append('error')
297 if classes:
298 self.cell_class_attribute = ' class="%s" ' % ' '.join(classes)
299 self._repr_filled = False
301 def _fetch_existing_display(self, func_name):
302 class_dict = self.original.__class__.__dict__
303 func = class_dict.get(func_name)
304 return func(self.original)
306 def _fill_existing_display(self):
307 if getattr(self, '_display_filled', False):
308 return
309 # HACK
310 if isinstance(self.field.rel, meta.ManyToOne):
311 func_name = 'get_%s' % self.field.name
312 self._display = self._fetch_existing_display(func_name)
313 elif isinstance(self.field.rel, meta.ManyToMany):
314 func_name = 'get_%s_list' % self.field.rel.singular
315 self._display = ", ".join([str(obj) for obj in self._fetch_existing_display(func_name)])
316 self._display_filled = True
318 def existing_display(self):
319 self._fill_existing_display()
320 return self._display
322 def __repr__(self):
323 return repr(self.__dict__)
325 def html_error_list(self):
326 return " ".join([form_field.html_error_list() for form_field in self.form_fields if form_field.errors])
328 class AdminBoundFieldLine(BoundFieldLine):
329 def __init__(self, field_line, field_mapping, original):
330 super(AdminBoundFieldLine, self).__init__(field_line, field_mapping, original, AdminBoundField)
331 for bound_field in self:
332 bound_field.first = True
333 break
335 class AdminBoundFieldSet(BoundFieldSet):
336 def __init__(self, field_set, field_mapping, original):
337 super(AdminBoundFieldSet, self).__init__(field_set, field_mapping, original, AdminBoundFieldLine)
339 class BoundManipulator(object):
340 def __init__(self, opts, manipulator, field_mapping):
341 self.inline_related_objects = opts.get_followed_related_objects(manipulator.follow)
342 self.original = hasattr(manipulator, 'original_object') and manipulator.original_object or None
343 self.bound_field_sets = [field_set.bind(field_mapping, self.original, AdminBoundFieldSet)
344 for field_set in opts.admin.get_field_sets(opts)]
345 self.ordered_objects = opts.get_ordered_objects()[:]
347 class AdminBoundManipulator(BoundManipulator):
348 def __init__(self, opts, manipulator, field_mapping):
349 super(AdminBoundManipulator, self).__init__(opts, manipulator, field_mapping)
350 field_sets = opts.admin.get_field_sets(opts)
352 self.auto_populated_fields = [f for f in opts.fields if f.prepopulate_from]
353 self.javascript_imports = get_javascript_imports(opts, self.auto_populated_fields, self.ordered_objects, field_sets);
355 self.coltype = self.ordered_objects and 'colMS' or 'colM'
356 self.has_absolute_url = hasattr(opts.get_model_module().Klass, 'get_absolute_url')
357 self.form_enc_attrib = opts.has_field_type(meta.FileField) and \
358 'enctype="multipart/form-data" ' or ''
360 self.first_form_field_id = self.bound_field_sets[0].bound_field_lines[0].bound_fields[0].form_fields[0].get_id();
361 self.ordered_object_pk_names = [o.pk.name for o in self.ordered_objects]
363 self.save_on_top = opts.admin.save_on_top
364 self.save_as = opts.admin.save_as
366 self.content_type_id = opts.get_content_type_id()
367 self.verbose_name_plural = opts.verbose_name_plural
368 self.verbose_name = opts.verbose_name
369 self.object_name = opts.object_name
371 def get_ordered_object_pk(self, ordered_obj):
372 for name in self.ordered_object_pk_names:
373 if hasattr(ordered_obj, name):
374 return str(getattr(ordered_obj, name))
375 return ""
377 def render_change_form(opts, manipulator, app_label, context, add=False, change=False, show_delete=False, form_url=''):
378 extra_context = {
379 'add': add,
380 'change': change,
381 'bound_manipulator': AdminBoundManipulator(opts, manipulator, context['form']),
382 'has_delete_permission': context['perms'][app_label][opts.get_delete_permission()],
383 'form_url': form_url,
384 'app_label': app_label,
386 context.update(extra_context)
387 return render_to_response(["admin/%s/%s/change_form" % (app_label, opts.object_name.lower() ),
388 "admin/%s/change_form" % app_label ,
389 "admin/change_form"], context_instance=context)
391 def log_add_message(user, opts,manipulator,new_object):
392 pk_value = getattr(new_object, opts.pk.attname)
393 log.log_action(user.id, opts.get_content_type_id(), pk_value, str(new_object), log.ADDITION)
395 def add_stage(request, app_label, module_name, show_delete=False, form_url='', post_url='../', post_url_continue='../%s/', object_id_override=None):
396 mod, opts = _get_mod_opts(app_label, module_name)
397 if not request.user.has_perm(app_label + '.' + opts.get_add_permission()):
398 raise PermissionDenied
399 manipulator = mod.AddManipulator()
400 if request.POST:
401 new_data = request.POST.copy()
402 if opts.has_field_type(meta.FileField):
403 new_data.update(request.FILES)
404 errors = manipulator.get_validation_errors(new_data)
405 manipulator.do_html2python(new_data)
407 if not errors and not request.POST.has_key("_preview"):
408 new_object = manipulator.save(new_data)
409 log_add_message(request.user, opts,manipulator,new_object)
410 msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name':opts.verbose_name, 'obj':new_object}
411 pk_value = getattr(new_object,opts.pk.attname)
412 # Here, we distinguish between different save types by checking for
413 # the presence of keys in request.POST.
414 if request.POST.has_key("_continue"):
415 request.user.add_message(msg + ' ' + _("You may edit it again below."))
416 if request.POST.has_key("_popup"):
417 post_url_continue += "?_popup=1"
418 return HttpResponseRedirect(post_url_continue % pk_value)
419 if request.POST.has_key("_popup"):
420 return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, %s, "%s");</script>' % \
421 (pk_value, repr(new_object).replace('"', '\\"')))
422 elif request.POST.has_key("_addanother"):
423 request.user.add_message(msg + ' ' + (_("You may add another %s below.") % opts.verbose_name))
424 return HttpResponseRedirect(request.path)
425 else:
426 request.user.add_message(msg)
427 return HttpResponseRedirect(post_url)
428 else:
429 # Add default data.
430 new_data = manipulator.flatten_data()
432 # Override the defaults with request.GET, if it exists.
433 new_data.update(request.GET)
434 errors = {}
436 # Populate the FormWrapper.
437 form = formfields.FormWrapper(manipulator, new_data, errors, edit_inline=True)
439 c = Context(request, {
440 'title': _('Add %s') % opts.verbose_name,
441 'form': form,
442 'is_popup': request.REQUEST.has_key('_popup'),
443 'show_delete': show_delete,
445 if object_id_override is not None:
446 c['object_id'] = object_id_override
448 return render_change_form(opts, manipulator, app_label, c, add=True)
449 add_stage = staff_member_required(add_stage)
451 def log_change_message(user, opts,manipulator,new_object):
452 pk_value = getattr(new_object, opts.pk.column)
453 # Construct the change message.
454 change_message = []
455 if manipulator.fields_added:
456 change_message.append(_('Added %s.') % get_text_list(manipulator.fields_added, _('and')))
457 if manipulator.fields_changed:
458 change_message.append(_('Changed %s.') % get_text_list(manipulator.fields_changed, _('and')))
459 if manipulator.fields_deleted:
460 change_message.append(_('Deleted %s.') % get_text_list(manipulator.fields_deleted, _('and')))
461 change_message = ' '.join(change_message)
462 if not change_message:
463 change_message = _('No fields changed.')
464 log.log_action(user.id, opts.get_content_type_id(), pk_value, str(new_object), log.CHANGE, change_message)
466 def change_stage(request, app_label, module_name, object_id):
467 mod, opts = _get_mod_opts(app_label, module_name)
468 if not request.user.has_perm(app_label + '.' + opts.get_change_permission()):
469 raise PermissionDenied
470 if request.POST and request.POST.has_key("_saveasnew"):
471 return add_stage(request, app_label, module_name, form_url='../add/')
472 try:
473 manipulator = mod.ChangeManipulator(object_id)
474 except ObjectDoesNotExist:
475 raise Http404
477 if request.POST:
478 new_data = request.POST.copy()
479 if opts.has_field_type(meta.FileField):
480 new_data.update(request.FILES)
482 errors = manipulator.get_validation_errors(new_data)
484 manipulator.do_html2python(new_data)
485 if not errors and not request.POST.has_key("_preview"):
486 new_object = manipulator.save(new_data)
487 log_change_message(request.user,opts,manipulator,new_object)
488 msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': opts.verbose_name, 'obj':new_object}
489 pk_value = getattr(new_object,opts.pk.attname)
490 if request.POST.has_key("_continue"):
491 request.user.add_message(msg + ' ' + _("You may edit it again below."))
492 if request.REQUEST.has_key('_popup'):
493 return HttpResponseRedirect(request.path + "?_popup=1")
494 else:
495 return HttpResponseRedirect(request.path)
496 elif request.POST.has_key("_saveasnew"):
497 request.user.add_message(_('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': opts.verbose_name, 'obj': new_object})
498 return HttpResponseRedirect("../%s/" % pk_value)
499 elif request.POST.has_key("_addanother"):
500 request.user.add_message(msg + ' ' + (_("You may add another %s below.") % opts.verbose_name))
501 return HttpResponseRedirect("../add/")
502 else:
503 request.user.add_message(msg)
504 return HttpResponseRedirect("../")
505 else:
506 # Populate new_data with a "flattened" version of the current data.
507 new_data = manipulator.flatten_data()
509 # TODO: do this in flatten_data...
510 # If the object has ordered objects on its admin page, get the existing
511 # order and flatten it into a comma-separated list of IDs.
513 id_order_list = []
514 for rel_obj in opts.get_ordered_objects():
515 id_order_list.extend(getattr(manipulator.original_object, 'get_%s_order' % rel_obj.object_name.lower())())
516 if id_order_list:
517 new_data['order_'] = ','.join(map(str, id_order_list))
518 errors = {}
520 # Populate the FormWrapper.
521 form = formfields.FormWrapper(manipulator, new_data, errors, edit_inline = True)
522 form.original = manipulator.original_object
523 form.order_objects = []
525 #TODO Should be done in flatten_data / FormWrapper construction
526 for related in opts.get_followed_related_objects():
527 wrt = related.opts.order_with_respect_to
528 if wrt and wrt.rel and wrt.rel.to == opts:
529 func = getattr(manipulator.original_object, 'get_%s_list' %
530 related.get_method_name_part())
531 orig_list = func()
532 form.order_objects.extend(orig_list)
534 c = Context(request, {
535 'title': _('Change %s') % opts.verbose_name,
536 'form': form,
537 'object_id': object_id,
538 'original': manipulator.original_object,
539 'is_popup' : request.REQUEST.has_key('_popup')
542 return render_change_form(opts,manipulator, app_label, c, change=True)
544 def _nest_help(obj, depth, val):
545 current = obj
546 for i in range(depth):
547 current = current[-1]
548 current.append(val)
550 def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current_depth):
551 "Helper function that recursively populates deleted_objects."
552 nh = _nest_help # Bind to local variable for performance
553 if current_depth > 16:
554 return # Avoid recursing too deep.
555 opts_seen = []
556 for related in opts.get_all_related_objects():
557 if related.opts in opts_seen:
558 continue
559 opts_seen.append(related.opts)
560 rel_opts_name = related.get_method_name_part()
561 if isinstance(related.field.rel, meta.OneToOne):
562 try:
563 sub_obj = getattr(obj, 'get_%s' % rel_opts_name)()
564 except ObjectDoesNotExist:
565 pass
566 else:
567 if related.opts.admin:
568 p = '%s.%s' % (related.opts.app_label, related.opts.get_delete_permission())
569 if not user.has_perm(p):
570 perms_needed.add(related.opts.verbose_name)
571 # We don't care about populating deleted_objects now.
572 continue
573 if related.field.rel.edit_inline or not related.opts.admin:
574 # Don't display link to edit, because it either has no
575 # admin or is edited inline.
576 nh(deleted_objects, current_depth, ['%s: %s' % (capfirst(related.opts.verbose_name), sub_obj), []])
577 else:
578 # Display a link to the admin page.
579 nh(deleted_objects, current_depth, ['%s: <a href="../../../../%s/%s/%s/">%s</a>' % \
580 (capfirst(related.opts.verbose_name), related.opts.app_label, related.opts.module_name,
581 getattr(sub_obj, related.opts.pk.attname), sub_obj), []])
582 _get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2)
583 else:
584 has_related_objs = False
585 for sub_obj in getattr(obj, 'get_%s_list' % rel_opts_name)():
586 has_related_objs = True
587 if related.field.rel.edit_inline or not related.opts.admin:
588 # Don't display link to edit, because it either has no
589 # admin or is edited inline.
590 nh(deleted_objects, current_depth, ['%s: %s' % (capfirst(related.opts.verbose_name), strip_tags(str(sub_obj))), []])
591 else:
592 # Display a link to the admin page.
593 nh(deleted_objects, current_depth, ['%s: <a href="../../../../%s/%s/%s/">%s</a>' % \
594 (capfirst(related.opts.verbose_name), related.opts.app_label, related.opts.module_name, sub_obj.id, strip_tags(str(sub_obj))), []])
595 _get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2)
596 # If there were related objects, and the user doesn't have
597 # permission to delete them, add the missing perm to perms_needed.
598 if related.opts.admin and has_related_objs:
599 p = '%s.%s' % (related.opts.app_label, related.opts.get_delete_permission())
600 if not user.has_perm(p):
601 perms_needed.add(rel_opts.verbose_name)
602 for related in opts.get_all_related_many_to_many_objects():
603 if related.opts in opts_seen:
604 continue
605 opts_seen.append(related.opts)
606 rel_opts_name = related.get_method_name_part()
607 has_related_objs = False
608 for sub_obj in getattr(obj, 'get_%s_list' % rel_opts_name)():
609 has_related_objs = True
610 if related.field.rel.edit_inline or not related.opts.admin:
611 # Don't display link to edit, because it either has no
612 # admin or is edited inline.
613 nh(deleted_objects, current_depth, [_('One or more %(fieldname)s in %(name)s: %(obj)s') % \
614 {'fieldname': related.field.name, 'name': related.opts.verbose_name, 'obj': strip_tags(str(sub_obj))}, []])
615 else:
616 # Display a link to the admin page.
617 nh(deleted_objects, current_depth, [
618 (_('One or more %(fieldname)s in %(name)s:') % {'fieldname': related.field.name, 'name':related.opts.verbose_name}) + \
619 (' <a href="../../../../%s/%s/%s/">%s</a>' % \
620 (related.opts.app_label, related.opts.module_name, sub_obj.id, strip_tags(str(sub_obj)))), []])
621 # If there were related objects, and the user doesn't have
622 # permission to change them, add the missing perm to perms_needed.
623 if related.opts.admin and has_related_objs:
624 p = '%s.%s' % (related.opts.app_label, related.opts.get_change_permission())
625 if not user.has_perm(p):
626 perms_needed.add(related.opts.verbose_name)
628 def delete_stage(request, app_label, module_name, object_id):
629 import sets
630 mod, opts = _get_mod_opts(app_label, module_name)
631 if not request.user.has_perm(app_label + '.' + opts.get_delete_permission()):
632 raise PermissionDenied
633 obj = get_object_or_404(mod, pk=object_id)
635 # Populate deleted_objects, a data structure of all related objects that
636 # will also be deleted.
637 deleted_objects = ['%s: <a href="../../%s/">%s</a>' % (capfirst(opts.verbose_name), object_id, strip_tags(str(obj))), []]
638 perms_needed = sets.Set()
639 _get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1)
641 if request.POST: # The user has already confirmed the deletion.
642 if perms_needed:
643 raise PermissionDenied
644 obj_display = str(obj)
645 obj.delete()
646 log.log_action(request.user.id, opts.get_content_type_id(), object_id, obj_display, log.DELETION)
647 request.user.add_message(_('The %(name)s "%(obj)s" was deleted successfully.') % {'name':opts.verbose_name, 'obj':obj_display})
648 return HttpResponseRedirect("../../")
649 return render_to_response('admin/delete_confirmation', {
650 "title": _("Are you sure?"),
651 "object_name": opts.verbose_name,
652 "object": obj,
653 "deleted_objects": deleted_objects,
654 "perms_lacking": perms_needed,
655 }, context_instance=Context(request))
656 delete_stage = staff_member_required(delete_stage)
658 def history(request, app_label, module_name, object_id):
659 mod, opts = _get_mod_opts(app_label, module_name)
660 action_list = log.get_list(object_id__exact=object_id, content_type__id__exact=opts.get_content_type_id(),
661 order_by=("action_time",), select_related=True)
662 # If no history was found, see whether this object even exists.
663 obj = get_object_or_404(mod, pk=object_id)
664 return render_to_response('admin/object_history', {
665 'title': _('Change history: %s') % obj,
666 'action_list': action_list,
667 'module_name': capfirst(opts.verbose_name_plural),
668 'object': obj,
669 }, context_instance=Context(request))
670 history = staff_member_required(history)