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
13 from django
.models
.admin
import log
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
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
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."
43 mod
= meta
.get_module(app_label
, module_name
)
45 raise Http404
# Invalid app or module name. Maybe it's not in INSTALLED_APPS.
46 opts
= mod
.Klass
._meta
48 raise Http404
# This object is valid but has no admin interface.
52 return render_to_response('admin/index', {'title': _('Site administration')}, context_instance
=Context(request
))
53 index
= staff_member_required(index
)
55 class IncorrectLookupParameters(Exception):
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
)
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()
89 for k
, v
in new_params
.items():
90 if p
.has_key(k
) and v
is None:
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.
106 self
.page_num
= int(request
.GET
.get(PAGE_VAR
, 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
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
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()
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
)
145 result_list
= paginator
.get_page(page_num
)
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'
169 order_field
, order_type
= ordering
[0], 'asc'
170 if params
.has_key(ORDER_VAR
):
173 f
= lookup_opts
.get_field(lookup_opts
.admin
.list_display
[int(params
[ORDER_VAR
])])
174 except meta
.FieldDoesNotExist
:
177 if not isinstance(f
.rel
, meta
.ManyToOne
) or not f
.null
:
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
):
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
198 f
= lookup_opts
.get_field(order_field
)
199 except meta
.FieldDoesNotExist
:
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
208 if lookup_opts
.admin
.list_select_related
:
209 lookup_params
['select_related'] = True
211 for field_name
in lookup_opts
.admin
.list_display
:
213 f
= lookup_opts
.get_field(field_name
)
214 except meta
.FieldDoesNotExist
:
217 if isinstance(f
.rel
, meta
.ManyToOne
):
218 lookup_params
['select_related'] = True
220 lookup_params
['order_by'] = ((order_type
== 'desc' and '-' or '') + lookup_order_field
,)
221 if lookup_opts
.admin
.search_fields
and query
:
223 for bit
in query
.split():
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
):
235 cl
= ChangeList(request
, app_label
, module_name
)
236 except IncorrectLookupParameters
:
237 return HttpResponseRedirect(request
.path
)
239 c
= Context(request
, {
241 'is_popup': cl
.is_popup
,
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'])
260 js
.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.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
:
267 js
.append('js/admin/CollapsedFieldsets.js' )
269 for field_line
in field_set
:
272 if f
.rel
and isinstance(f
, meta
.ManyToManyField
) and f
.rel
.filter_interface
:
273 js
.extend(['js/SelectBox.js' , 'js/SelectFilter2.js'])
275 except StopIteration:
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
)
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')
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):
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
()
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
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
))
377 def render_change_form(opts
, manipulator
, app_label
, context
, add
=False, change
=False, show_delete
=False, form_url
=''):
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()
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
)
426 request
.user
.add_message(msg
)
427 return HttpResponseRedirect(post_url
)
430 new_data
= manipulator
.flatten_data()
432 # Override the defaults with request.GET, if it exists.
433 new_data
.update(request
.GET
)
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
,
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.
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/')
473 manipulator
= mod
.ChangeManipulator(object_id
)
474 except ObjectDoesNotExist
:
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")
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/")
503 request
.user
.add_message(msg
)
504 return HttpResponseRedirect("../")
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.
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())())
517 new_data
['order_'] = ','.join(map(str, id_order_list
))
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())
532 form
.order_objects
.extend(orig_list
)
534 c
= Context(request
, {
535 'title': _('Change %s') % opts
.verbose_name
,
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
):
546 for i
in range(depth
):
547 current
= current
[-1]
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.
556 for related
in opts
.get_all_related_objects():
557 if related
.opts
in opts_seen
:
559 opts_seen
.append(related
.opts
)
560 rel_opts_name
= related
.get_method_name_part()
561 if isinstance(related
.field
.rel
, meta
.OneToOne
):
563 sub_obj
= getattr(obj
, 'get_%s' % rel_opts_name
)()
564 except ObjectDoesNotExist
:
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.
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
), []])
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)
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
))), []])
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
:
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
))}, []])
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
):
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.
643 raise PermissionDenied
644 obj_display
= str(obj
)
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
,
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
),
669 }, context_instance
=Context(request
))
670 history
= staff_member_required(history
)