Package django :: Package contrib :: Package admin :: Package views :: Module main
[hide private]
[frames] | no frames]

Source Code for Module django.contrib.admin.views.main

  1  from django import oldforms, template 
  2  from django.conf import settings 
  3  from django.contrib.admin.filterspecs import FilterSpec 
  4  from django.contrib.admin.views.decorators import staff_member_required 
  5  from django.views.decorators.cache import never_cache 
  6  from django.contrib.contenttypes.models import ContentType 
  7  from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist, PermissionDenied 
  8  from django.core.paginator import ObjectPaginator, InvalidPage 
  9  from django.shortcuts import get_object_or_404, render_to_response 
 10  from django.db import models 
 11  from django.db.models.query import handle_legacy_orderlist, QuerySet 
 12  from django.http import Http404, HttpResponse, HttpResponseRedirect 
 13  from django.utils.html import escape 
 14  from django.utils.text import capfirst, get_text_list 
 15  from django.utils.encoding import force_unicode, smart_str 
 16  from django.utils.translation import ugettext as _ 
 17  from django.utils.safestring import mark_safe 
 18  import operator 
 19   
 20  try: 
 21      set 
 22  except NameError: 
 23      from sets import Set as set   # Python 2.3 fallback 
 24   
 25  from django.contrib.admin.models import LogEntry, ADDITION, CHANGE, DELETION 
 26  if not LogEntry._meta.installed: 
 27      raise ImproperlyConfigured, "You'll need to put 'django.contrib.admin' in your INSTALLED_APPS setting before you can use the admin application." 
 28   
 29  if 'django.core.context_processors.auth' not in settings.TEMPLATE_CONTEXT_PROCESSORS: 
 30      raise ImproperlyConfigured, "You'll need to put 'django.core.context_processors.auth' in your TEMPLATE_CONTEXT_PROCESSORS setting before you can use the admin application." 
 31   
 32  # The system will display a "Show all" link on the change list only if the 
 33  # total result count is less than or equal to this setting. 
 34  MAX_SHOW_ALL_ALLOWED = 200 
 35   
 36  # Changelist settings 
 37  ALL_VAR = 'all' 
 38  ORDER_VAR = 'o' 
 39  ORDER_TYPE_VAR = 'ot' 
 40  PAGE_VAR = 'p' 
 41  SEARCH_VAR = 'q' 
 42  IS_POPUP_VAR = 'pop' 
 43  ERROR_FLAG = 'e' 
 44   
 45  # Text to display within change-list table cells if the value is blank. 
 46  EMPTY_CHANGELIST_VALUE = '(None)' 
 47   
 48  use_raw_id_admin = lambda field: isinstance(field.rel, (models.ManyToOneRel, models.ManyToManyRel)) and field.rel.raw_id_admin 
 49   
50 -class IncorrectLookupParameters(Exception):
51 pass
52
53 -def quote(s):
54 """ 55 Ensure that primary key values do not confuse the admin URLs by escaping 56 any '/', '_' and ':' characters. Similar to urllib.quote, except that the 57 quoting is slightly different so that it doesn't get automatically 58 unquoted by the Web browser. 59 """ 60 if type(s) != type(''): 61 return s 62 res = list(s) 63 for i in range(len(res)): 64 c = res[i] 65 if c in ':/_': 66 res[i] = '_%02X' % ord(c) 67 return ''.join(res)
68
69 -def unquote(s):
70 """ 71 Undo the effects of quote(). Based heavily on urllib.unquote(). 72 """ 73 mychr = chr 74 myatoi = int 75 list = s.split('_') 76 res = [list[0]] 77 myappend = res.append 78 del list[0] 79 for item in list: 80 if item[1:2]: 81 try: 82 myappend(mychr(myatoi(item[:2], 16)) + item[2:]) 83 except ValueError: 84 myappend('_' + item) 85 else: 86 myappend('_' + item) 87 return "".join(res)
88
89 -def get_javascript_imports(opts, auto_populated_fields, field_sets):
90 # Put in any necessary JavaScript imports. 91 js = ['js/core.js', 'js/admin/RelatedObjectLookups.js'] 92 if auto_populated_fields: 93 js.append('js/urlify.js') 94 if opts.has_field_type(models.DateTimeField) or opts.has_field_type(models.TimeField) or opts.has_field_type(models.DateField): 95 js.extend(['js/calendar.js', 'js/admin/DateTimeShortcuts.js']) 96 if opts.get_ordered_objects(): 97 js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js']) 98 if opts.admin.js: 99 js.extend(opts.admin.js) 100 seen_collapse = False 101 for field_set in field_sets: 102 if not seen_collapse and 'collapse' in field_set.classes: 103 seen_collapse = True 104 js.append('js/admin/CollapsedFieldsets.js') 105 106 for field_line in field_set: 107 try: 108 for f in field_line: 109 if f.rel and isinstance(f, models.ManyToManyField) and f.rel.filter_interface: 110 js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js']) 111 raise StopIteration 112 except StopIteration: 113 break 114 return js
115
116 -class AdminBoundField(object):
117 - def __init__(self, field, field_mapping, original):
118 self.field = field 119 self.original = original 120 self.form_fields = [field_mapping[name] for name in self.field.get_manipulator_field_names('')] 121 self.element_id = self.form_fields[0].get_id() 122 self.has_label_first = not isinstance(self.field, models.BooleanField) 123 self.raw_id_admin = use_raw_id_admin(field) 124 self.is_date_time = isinstance(field, models.DateTimeField) 125 self.is_file_field = isinstance(field, models.FileField) 126 self.needs_add_label = field.rel and (isinstance(field.rel, models.ManyToOneRel) or isinstance(field.rel, models.ManyToManyRel)) and field.rel.to._meta.admin 127 self.hidden = isinstance(self.field, models.AutoField) 128 self.first = False 129 130 classes = [] 131 if self.raw_id_admin: 132 classes.append('nowrap') 133 if max([bool(f.errors()) for f in self.form_fields]): 134 classes.append('error') 135 if classes: 136 self.cell_class_attribute = u' class="%s" ' % ' '.join(classes) 137 self._repr_filled = False 138 139 if field.rel: 140 self.related_url = mark_safe(u'../../../%s/%s/' 141 % (field.rel.to._meta.app_label, 142 field.rel.to._meta.object_name.lower()))
143
144 - def original_value(self):
145 if self.original: 146 return self.original.__dict__[self.field.attname]
147
148 - def existing_display(self):
149 try: 150 return self._display 151 except AttributeError: 152 if isinstance(self.field.rel, models.ManyToOneRel): 153 self._display = force_unicode(getattr(self.original, self.field.name), strings_only=True) 154 elif isinstance(self.field.rel, models.ManyToManyRel): 155 self._display = u", ".join([force_unicode(obj) for obj in getattr(self.original, self.field.name).all()]) 156 return self._display
157
158 - def __repr__(self):
159 return repr(self.__dict__)
160
161 - def html_error_list(self):
162 return mark_safe(" ".join([form_field.html_error_list() for form_field in self.form_fields if form_field.errors]))
163
164 - def original_url(self):
165 if self.is_file_field and self.original and self.field.attname: 166 url_method = getattr(self.original, 'get_%s_url' % self.field.attname) 167 if callable(url_method): 168 return url_method() 169 return ''
170
171 -class AdminBoundFieldLine(object):
172 - def __init__(self, field_line, field_mapping, original):
173 self.bound_fields = [field.bind(field_mapping, original, AdminBoundField) for field in field_line] 174 for bound_field in self: 175 bound_field.first = True 176 break
177
178 - def __iter__(self):
179 for bound_field in self.bound_fields: 180 yield bound_field
181
182 - def __len__(self):
183 return len(self.bound_fields)
184
185 -class AdminBoundFieldSet(object):
186 - def __init__(self, field_set, field_mapping, original):
187 self.name = field_set.name 188 self.classes = field_set.classes 189 self.description = field_set.description 190 self.bound_field_lines = [field_line.bind(field_mapping, original, AdminBoundFieldLine) for field_line in field_set]
191
192 - def __iter__(self):
193 for bound_field_line in self.bound_field_lines: 194 yield bound_field_line
195
196 - def __len__(self):
197 return len(self.bound_field_lines)
198
199 -def render_change_form(model, manipulator, context, add=False, change=False, form_url=''):
200 opts = model._meta 201 app_label = opts.app_label 202 auto_populated_fields = [f for f in opts.fields if f.prepopulate_from] 203 field_sets = opts.admin.get_field_sets(opts) 204 original = getattr(manipulator, 'original_object', None) 205 bound_field_sets = [field_set.bind(context['form'], original, AdminBoundFieldSet) for field_set in field_sets] 206 first_form_field_id = bound_field_sets[0].bound_field_lines[0].bound_fields[0].form_fields[0].get_id(); 207 ordered_objects = opts.get_ordered_objects() 208 inline_related_objects = opts.get_followed_related_objects(manipulator.follow) 209 extra_context = { 210 'add': add, 211 'change': change, 212 'has_delete_permission': context['perms'][app_label][opts.get_delete_permission()], 213 'has_change_permission': context['perms'][app_label][opts.get_change_permission()], 214 'has_file_field': opts.has_field_type(models.FileField), 215 'has_absolute_url': hasattr(model, 'get_absolute_url'), 216 'auto_populated_fields': auto_populated_fields, 217 'bound_field_sets': bound_field_sets, 218 'first_form_field_id': first_form_field_id, 219 'javascript_imports': get_javascript_imports(opts, auto_populated_fields, field_sets), 220 'ordered_objects': ordered_objects, 221 'inline_related_objects': inline_related_objects, 222 'form_url': mark_safe(form_url), 223 'opts': opts, 224 'content_type_id': ContentType.objects.get_for_model(model).id, 225 } 226 context.update(extra_context) 227 return render_to_response([ 228 "admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()), 229 "admin/%s/change_form.html" % app_label, 230 "admin/change_form.html"], context_instance=context)
231
232 -def index(request):
233 return render_to_response('admin/index.html', {'title': _('Site administration')}, context_instance=template.RequestContext(request))
234 index = staff_member_required(never_cache(index)) 235
236 -def add_stage(request, app_label, model_name, show_delete=False, form_url='', post_url=None, post_url_continue='../%s/', object_id_override=None):
237 model = models.get_model(app_label, model_name) 238 if model is None: 239 raise Http404("App %r, model %r, not found" % (app_label, model_name)) 240 opts = model._meta 241 242 if not request.user.has_perm(app_label + '.' + opts.get_add_permission()): 243 raise PermissionDenied 244 245 if post_url is None: 246 if request.user.has_perm(app_label + '.' + opts.get_change_permission()): 247 # redirect to list view 248 post_url = '../' 249 else: 250 # Object list will give 'Permission Denied', so go back to admin home 251 post_url = '../../../' 252 253 manipulator = model.AddManipulator() 254 if request.POST: 255 new_data = request.POST.copy() 256 257 if opts.has_field_type(models.FileField): 258 new_data.update(request.FILES) 259 260 errors = manipulator.get_validation_errors(new_data) 261 manipulator.do_html2python(new_data) 262 263 if not errors: 264 new_object = manipulator.save(new_data) 265 pk_value = new_object._get_pk_val() 266 LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(model).id, pk_value, force_unicode(new_object), ADDITION) 267 msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(new_object)} 268 # Here, we distinguish between different save types by checking for 269 # the presence of keys in request.POST. 270 if "_continue" in request.POST: 271 request.user.message_set.create(message=msg + ' ' + _("You may edit it again below.")) 272 if "_popup" in request.POST: 273 post_url_continue += "?_popup=1" 274 return HttpResponseRedirect(post_url_continue % pk_value) 275 if "_popup" in request.POST: 276 return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script>' % \ 277 # escape() calls force_unicode. 278 (escape(pk_value), escape(new_object))) 279 elif "_addanother" in request.POST: 280 request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name))) 281 return HttpResponseRedirect(request.path) 282 else: 283 request.user.message_set.create(message=msg) 284 return HttpResponseRedirect(post_url) 285 else: 286 # Add default data. 287 new_data = manipulator.flatten_data() 288 289 # Override the defaults with GET params, if they exist. 290 new_data.update(dict(request.GET.items())) 291 292 errors = {} 293 294 # Populate the FormWrapper. 295 form = oldforms.FormWrapper(manipulator, new_data, errors) 296 297 c = template.RequestContext(request, { 298 'title': _('Add %s') % force_unicode(opts.verbose_name), 299 'form': form, 300 'is_popup': '_popup' in request.REQUEST, 301 'show_delete': show_delete, 302 }) 303 304 if object_id_override is not None: 305 c['object_id'] = object_id_override 306 307 return render_change_form(model, manipulator, c, add=True)
308 add_stage = staff_member_required(never_cache(add_stage)) 309
310 -def change_stage(request, app_label, model_name, object_id):
311 model = models.get_model(app_label, model_name) 312 object_id = unquote(object_id) 313 if model is None: 314 raise Http404("App %r, model %r, not found" % (app_label, model_name)) 315 opts = model._meta 316 317 if not request.user.has_perm(app_label + '.' + opts.get_change_permission()): 318 raise PermissionDenied 319 320 if request.POST and "_saveasnew" in request.POST: 321 return add_stage(request, app_label, model_name, form_url='../../add/') 322 323 try: 324 manipulator = model.ChangeManipulator(object_id) 325 except model.DoesNotExist: 326 raise Http404('%s object with primary key %r does not exist' % (model_name, escape(object_id))) 327 328 if request.POST: 329 new_data = request.POST.copy() 330 331 if opts.has_field_type(models.FileField): 332 new_data.update(request.FILES) 333 334 errors = manipulator.get_validation_errors(new_data) 335 manipulator.do_html2python(new_data) 336 337 if not errors: 338 new_object = manipulator.save(new_data) 339 pk_value = new_object._get_pk_val() 340 341 # Construct the change message. 342 change_message = [] 343 if manipulator.fields_added: 344 change_message.append(_('Added %s.') % get_text_list(manipulator.fields_added, _('and'))) 345 if manipulator.fields_changed: 346 change_message.append(_('Changed %s.') % get_text_list(manipulator.fields_changed, _('and'))) 347 if manipulator.fields_deleted: 348 change_message.append(_('Deleted %s.') % get_text_list(manipulator.fields_deleted, _('and'))) 349 change_message = ' '.join(change_message) 350 if not change_message: 351 change_message = _('No fields changed.') 352 LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(model).id, pk_value, force_unicode(new_object), CHANGE, change_message) 353 354 msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(new_object)} 355 if "_continue" in request.POST: 356 request.user.message_set.create(message=msg + ' ' + _("You may edit it again below.")) 357 if '_popup' in request.REQUEST: 358 return HttpResponseRedirect(request.path + "?_popup=1") 359 else: 360 return HttpResponseRedirect(request.path) 361 elif "_saveasnew" in request.POST: 362 request.user.message_set.create(message=_('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(new_object)}) 363 return HttpResponseRedirect("../%s/" % pk_value) 364 elif "_addanother" in request.POST: 365 request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name))) 366 return HttpResponseRedirect("../add/") 367 else: 368 request.user.message_set.create(message=msg) 369 return HttpResponseRedirect("../") 370 else: 371 # Populate new_data with a "flattened" version of the current data. 372 new_data = manipulator.flatten_data() 373 374 # TODO: do this in flatten_data... 375 # If the object has ordered objects on its admin page, get the existing 376 # order and flatten it into a comma-separated list of IDs. 377 378 id_order_list = [] 379 for rel_obj in opts.get_ordered_objects(): 380 id_order_list.extend(getattr(manipulator.original_object, 'get_%s_order' % rel_obj.object_name.lower())()) 381 if id_order_list: 382 new_data['order_'] = ','.join(map(str, id_order_list)) 383 errors = {} 384 385 # Populate the FormWrapper. 386 form = oldforms.FormWrapper(manipulator, new_data, errors) 387 form.original = manipulator.original_object 388 form.order_objects = [] 389 390 #TODO Should be done in flatten_data / FormWrapper construction 391 for related in opts.get_followed_related_objects(): 392 wrt = related.opts.order_with_respect_to 393 if wrt and wrt.rel and wrt.rel.to == opts: 394 func = getattr(manipulator.original_object, 'get_%s_list' % 395 related.get_accessor_name()) 396 orig_list = func() 397 form.order_objects.extend(orig_list) 398 399 c = template.RequestContext(request, { 400 'title': _('Change %s') % force_unicode(opts.verbose_name), 401 'form': form, 402 'object_id': object_id, 403 'original': manipulator.original_object, 404 'is_popup': '_popup' in request.REQUEST, 405 }) 406 return render_change_form(model, manipulator, c, change=True)
407 change_stage = staff_member_required(never_cache(change_stage)) 408
409 -def _nest_help(obj, depth, val):
410 current = obj 411 for i in range(depth): 412 current = current[-1] 413 current.append(val)
414
415 -def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current_depth):
416 "Helper function that recursively populates deleted_objects." 417 nh = _nest_help # Bind to local variable for performance 418 if current_depth > 16: 419 return # Avoid recursing too deep. 420 opts_seen = [] 421 for related in opts.get_all_related_objects(): 422 if related.opts in opts_seen: 423 continue 424 opts_seen.append(related.opts) 425 rel_opts_name = related.get_accessor_name() 426 if isinstance(related.field.rel, models.OneToOneRel): 427 try: 428 sub_obj = getattr(obj, rel_opts_name) 429 except ObjectDoesNotExist: 430 pass 431 else: 432 if related.opts.admin: 433 p = '%s.%s' % (related.opts.app_label, related.opts.get_delete_permission()) 434 if not user.has_perm(p): 435 perms_needed.add(related.opts.verbose_name) 436 # We don't care about populating deleted_objects now. 437 continue 438 if related.field.rel.edit_inline or not related.opts.admin: 439 # Don't display link to edit, because it either has no 440 # admin or is edited inline. 441 nh(deleted_objects, current_depth, [mark_safe(u'%s: %s' % (force_unicode(capfirst(related.opts.verbose_name)), sub_obj)), []]) 442 else: 443 # Display a link to the admin page. 444 nh(deleted_objects, current_depth, [mark_safe(u'%s: <a href="../../../../%s/%s/%s/">%s</a>' % 445 (escape(force_unicode(capfirst(related.opts.verbose_name))), 446 related.opts.app_label, 447 related.opts.object_name.lower(), 448 sub_obj._get_pk_val(), sub_obj)), []]) 449 _get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2) 450 else: 451 has_related_objs = False 452 for sub_obj in getattr(obj, rel_opts_name).all(): 453 has_related_objs = True 454 if related.field.rel.edit_inline or not related.opts.admin: 455 # Don't display link to edit, because it either has no 456 # admin or is edited inline. 457 nh(deleted_objects, current_depth, [u'%s: %s' % (force_unicode(capfirst(related.opts.verbose_name)), escape(sub_obj)), []]) 458 else: 459 # Display a link to the admin page. 460 nh(deleted_objects, current_depth, [mark_safe(u'%s: <a href="../../../../%s/%s/%s/">%s</a>' % \ 461 (escape(force_unicode(capfirst(related.opts.verbose_name))), related.opts.app_label, related.opts.object_name.lower(), sub_obj._get_pk_val(), escape(sub_obj))), []]) 462 _get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2) 463 # If there were related objects, and the user doesn't have 464 # permission to delete them, add the missing perm to perms_needed. 465 if related.opts.admin and has_related_objs: 466 p = '%s.%s' % (related.opts.app_label, related.opts.get_delete_permission()) 467 if not user.has_perm(p): 468 perms_needed.add(related.opts.verbose_name) 469 for related in opts.get_all_related_many_to_many_objects(): 470 if related.opts in opts_seen: 471 continue 472 opts_seen.append(related.opts) 473 rel_opts_name = related.get_accessor_name() 474 has_related_objs = False 475 476 # related.get_accessor_name() could return None for symmetrical relationships 477 if rel_opts_name: 478 rel_objs = getattr(obj, rel_opts_name, None) 479 if rel_objs: 480 has_related_objs = True 481 482 if has_related_objs: 483 for sub_obj in rel_objs.all(): 484 if related.field.rel.edit_inline or not related.opts.admin: 485 # Don't display link to edit, because it either has no 486 # admin or is edited inline. 487 nh(deleted_objects, current_depth, [_('One or more %(fieldname)s in %(name)s: %(obj)s') % \ 488 {'fieldname': force_unicode(related.field.verbose_name), 'name': force_unicode(related.opts.verbose_name), 'obj': escape(sub_obj)}, []]) 489 else: 490 # Display a link to the admin page. 491 nh(deleted_objects, current_depth, [ 492 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))}) + \ 493 (u' <a href="../../../../%s/%s/%s/">%s</a>' % \ 494 (related.opts.app_label, related.opts.module_name, sub_obj._get_pk_val(), escape(sub_obj)))), []]) 495 # If there were related objects, and the user doesn't have 496 # permission to change them, add the missing perm to perms_needed. 497 if related.opts.admin and has_related_objs: 498 p = u'%s.%s' % (related.opts.app_label, related.opts.get_change_permission()) 499 if not user.has_perm(p): 500 perms_needed.add(related.opts.verbose_name)
501
502 -def delete_stage(request, app_label, model_name, object_id):
503 model = models.get_model(app_label, model_name) 504 object_id = unquote(object_id) 505 if model is None: 506 raise Http404("App %r, model %r, not found" % (app_label, model_name)) 507 opts = model._meta 508 if not request.user.has_perm(app_label + '.' + opts.get_delete_permission()): 509 raise PermissionDenied 510 obj = get_object_or_404(model, pk=object_id) 511 512 # Populate deleted_objects, a data structure of all related objects that 513 # will also be deleted. 514 deleted_objects = [mark_safe(u'%s: <a href="../../%s/">%s</a>' % (escape(force_unicode(capfirst(opts.verbose_name))), force_unicode(object_id), escape(obj))), []] 515 perms_needed = set() 516 _get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1) 517 518 if request.POST: # The user has already confirmed the deletion. 519 if perms_needed: 520 raise PermissionDenied 521 obj_display = force_unicode(obj) 522 obj.delete() 523 LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(model).id, object_id, obj_display, DELETION) 524 request.user.message_set.create(message=_('The %(name)s "%(obj)s" was deleted successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': obj_display}) 525 return HttpResponseRedirect("../../") 526 extra_context = { 527 "title": _("Are you sure?"), 528 "object_name": force_unicode(opts.verbose_name), 529 "object": obj, 530 "deleted_objects": deleted_objects, 531 "perms_lacking": perms_needed, 532 "opts": model._meta, 533 } 534 return render_to_response(["admin/%s/%s/delete_confirmation.html" % (app_label, opts.object_name.lower() ), 535 "admin/%s/delete_confirmation.html" % app_label , 536 "admin/delete_confirmation.html"], extra_context, context_instance=template.RequestContext(request))
537 delete_stage = staff_member_required(never_cache(delete_stage)) 538
539 -def history(request, app_label, model_name, object_id):
540 model = models.get_model(app_label, model_name) 541 object_id = unquote(object_id) 542 if model is None: 543 raise Http404("App %r, model %r, not found" % (app_label, model_name)) 544 action_list = LogEntry.objects.filter(object_id=object_id, 545 content_type__id__exact=ContentType.objects.get_for_model(model).id).select_related().order_by('action_time') 546 # If no history was found, see whether this object even exists. 547 obj = get_object_or_404(model, pk=object_id) 548 extra_context = { 549 'title': _('Change history: %s') % obj, 550 'action_list': action_list, 551 'module_name': force_unicode(capfirst(model._meta.verbose_name_plural)), 552 'object': obj, 553 } 554 return render_to_response(["admin/%s/%s/object_history.html" % (app_label, model._meta.object_name.lower()), 555 "admin/%s/object_history.html" % app_label , 556 "admin/object_history.html"], extra_context, context_instance=template.RequestContext(request))
557 history = staff_member_required(never_cache(history)) 558
559 -class ChangeList(object):
560 - def __init__(self, request, model):
561 self.model = model 562 self.opts = model._meta 563 self.lookup_opts = self.opts 564 self.manager = self.opts.admin.manager 565 566 # Get search parameters from the query string. 567 try: 568 self.page_num = int(request.GET.get(PAGE_VAR, 0)) 569 except ValueError: 570 self.page_num = 0 571 self.show_all = ALL_VAR in request.GET 572 self.is_popup = IS_POPUP_VAR in request.GET 573 self.params = dict(request.GET.items()) 574 if PAGE_VAR in self.params: 575 del self.params[PAGE_VAR] 576 if ERROR_FLAG in self.params: 577 del self.params[ERROR_FLAG] 578 579 self.order_field, self.order_type = self.get_ordering() 580 self.query = request.GET.get(SEARCH_VAR, '') 581 self.query_set = self.get_query_set() 582 self.get_results(request) 583 self.title = (self.is_popup and _('Select %s') % force_unicode(self.opts.verbose_name) or _('Select %s to change') % force_unicode(self.opts.verbose_name)) 584 self.filter_specs, self.has_filters = self.get_filters(request) 585 self.pk_attname = self.lookup_opts.pk.attname
586
587 - def get_filters(self, request):
588 filter_specs = [] 589 if self.lookup_opts.admin.list_filter and not self.opts.one_to_one_field: 590 filter_fields = [self.lookup_opts.get_field(field_name) \ 591 for field_name in self.lookup_opts.admin.list_filter] 592 for f in filter_fields: 593 spec = FilterSpec.create(f, request, self.params, self.model) 594 if spec and spec.has_output(): 595 filter_specs.append(spec) 596 return filter_specs, bool(filter_specs)
597
598 - def get_query_string(self, new_params=None, remove=None):
599 if new_params is None: new_params = {} 600 if remove is None: remove = [] 601 p = self.params.copy() 602 for r in remove: 603 for k in p.keys(): 604 if k.startswith(r): 605 del p[k] 606 for k, v in new_params.items(): 607 if k in p and v is None: 608 del p[k] 609 elif v is not None: 610 p[k] = v 611 return mark_safe('?' + '&amp;'.join([u'%s=%s' % (k, v) for k, v in p.items()]).replace(' ', '%20'))
612
613 - def get_results(self, request):
614 paginator = ObjectPaginator(self.query_set, self.lookup_opts.admin.list_per_page) 615 616 # Get the number of objects, with admin filters applied. 617 try: 618 result_count = paginator.hits 619 # Naked except! Because we don't have any other way of validating 620 # "params". They might be invalid if the keyword arguments are 621 # incorrect, or if the values are not in the correct type (which would 622 # result in a database error). 623 except: 624 raise IncorrectLookupParameters 625 626 # Get the total number of objects, with no admin filters applied. 627 # Perform a slight optimization: Check to see whether any filters were 628 # given. If not, use paginator.hits to calculate the number of objects, 629 # because we've already done paginator.hits and the value is cached. 630 if isinstance(self.query_set._filters, models.Q) and not self.query_set._filters.kwargs: 631 full_result_count = result_count 632 else: 633 full_result_count = self.manager.count() 634 635 can_show_all = result_count <= MAX_SHOW_ALL_ALLOWED 636 multi_page = result_count > self.lookup_opts.admin.list_per_page 637 638 # Get the list of objects to display on this page. 639 if (self.show_all and can_show_all) or not multi_page: 640 result_list = list(self.query_set) 641 else: 642 try: 643 result_list = paginator.get_page(self.page_num) 644 except InvalidPage: 645 result_list = () 646 647 self.result_count = result_count 648 self.full_result_count = full_result_count 649 self.result_list = result_list 650 self.can_show_all = can_show_all 651 self.multi_page = multi_page 652 self.paginator = paginator
653
654 - def get_ordering(self):
655 lookup_opts, params = self.lookup_opts, self.params 656 # For ordering, first check the "ordering" parameter in the admin options, 657 # then check the object's default ordering. If neither of those exist, 658 # order descending by ID by default. Finally, look for manually-specified 659 # ordering from the query string. 660 ordering = lookup_opts.admin.ordering or lookup_opts.ordering or ['-' + lookup_opts.pk.name] 661 662 # Normalize it to new-style ordering. 663 ordering = handle_legacy_orderlist(ordering) 664 665 if ordering[0].startswith('-'): 666 order_field, order_type = ordering[0][1:], 'desc' 667 else: 668 order_field, order_type = ordering[0], 'asc' 669 if ORDER_VAR in params: 670 try: 671 field_name = lookup_opts.admin.list_display[int(params[ORDER_VAR])] 672 try: 673 f = lookup_opts.get_field(field_name) 674 except models.FieldDoesNotExist: 675 # see if field_name is a name of a non-field 676 # that allows sorting 677 try: 678 attr = getattr(lookup_opts.admin.manager.model, field_name) 679 order_field = attr.admin_order_field 680 except AttributeError: 681 pass 682 else: 683 if not isinstance(f.rel, models.ManyToOneRel) or not f.null: 684 order_field = f.name 685 except (IndexError, ValueError): 686 pass # Invalid ordering specified. Just use the default. 687 if ORDER_TYPE_VAR in params and params[ORDER_TYPE_VAR] in ('asc', 'desc'): 688 order_type = params[ORDER_TYPE_VAR] 689 return order_field, order_type
690
691 - def get_query_set(self):
692 qs = self.manager.get_query_set() 693 lookup_params = self.params.copy() # a dictionary of the query string 694 for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR): 695 if i in lookup_params: 696 del lookup_params[i] 697 for key, value in lookup_params.items(): 698 if not isinstance(key, str): 699 # 'key' will be used as a keyword argument later, so Python 700 # requires it to be a string. 701 del lookup_params[key] 702 lookup_params[smart_str(key)] = value 703 704 # Apply lookup parameters from the query string. 705 qs = qs.filter(**lookup_params) 706 707 # Use select_related() if one of the list_display options is a field 708 # with a relationship. 709 if self.lookup_opts.admin.list_select_related: 710 qs = qs.select_related() 711 else: 712 for field_name in self.lookup_opts.admin.list_display: 713 try: 714 f = self.lookup_opts.get_field(field_name) 715 except models.FieldDoesNotExist: 716 pass 717 else: 718 if isinstance(f.rel, models.ManyToOneRel): 719 qs = qs.select_related() 720 break 721 722 # Calculate lookup_order_field. 723 # If the order-by field is a field with a relationship, order by the 724 # value in the related table. 725 lookup_order_field = self.order_field 726 try: 727 f = self.lookup_opts.get_field(self.order_field, many_to_many=False) 728 except models.FieldDoesNotExist: 729 pass 730 else: 731 if isinstance(f.rel, models.OneToOneRel): 732 # For OneToOneFields, don't try to order by the related object's ordering criteria. 733 pass 734 elif isinstance(f.rel, models.ManyToOneRel): 735 rel_ordering = f.rel.to._meta.ordering and f.rel.to._meta.ordering[0] or f.rel.to._meta.pk.column 736 lookup_order_field = '%s.%s' % (f.rel.to._meta.db_table, rel_ordering) 737 738 # Set ordering. 739 qs = qs.order_by((self.order_type == 'desc' and '-' or '') + lookup_order_field) 740 741 # Apply keyword searches. 742 def construct_search(field_name): 743 if field_name.startswith('^'): 744 return "%s__istartswith" % field_name[1:] 745 elif field_name.startswith('='): 746 return "%s__iexact" % field_name[1:] 747 elif field_name.startswith('@'): 748 return "%s__search" % field_name[1:] 749 else: 750 return "%s__icontains" % field_name
751 752 if self.lookup_opts.admin.search_fields and self.query: 753 for bit in self.query.split(): 754 or_queries = [models.Q(**{construct_search(field_name): bit}) for field_name in self.lookup_opts.admin.search_fields] 755 other_qs = QuerySet(self.model) 756 if qs._select_related: 757 other_qs = other_qs.select_related() 758 other_qs = other_qs.filter(reduce(operator.or_, or_queries)) 759 qs = qs & other_qs 760 761 if self.opts.one_to_one_field: 762 qs = qs.complex_filter(self.opts.one_to_one_field.rel.limit_choices_to) 763 764 return qs
765
766 - def url_for_result(self, result):
767 return "%s/" % quote(getattr(result, self.pk_attname))
768
769 -def change_list(request, app_label, model_name):
770 model = models.get_model(app_label, model_name) 771 if model is None: 772 raise Http404("App %r, model %r, not found" % (app_label, model_name)) 773 if not request.user.has_perm(app_label + '.' + model._meta.get_change_permission()): 774 raise PermissionDenied 775 try: 776 cl = ChangeList(request, model) 777 except IncorrectLookupParameters: 778 # Wacky lookup parameters were given, so redirect to the main 779 # changelist page, without parameters, and pass an 'invalid=1' 780 # parameter via the query string. If wacky parameters were given and 781 # the 'invalid=1' parameter was already in the query string, something 782 # is screwed up with the database, so display an error page. 783 if ERROR_FLAG in request.GET.keys(): 784 return render_to_response('admin/invalid_setup.html', {'title': _('Database error')}) 785 return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1') 786 c = template.RequestContext(request, { 787 'title': cl.title, 788 'is_popup': cl.is_popup, 789 'cl': cl, 790 }) 791 c.update({'has_add_permission': c['perms'][app_label][cl.opts.get_add_permission()]}), 792 return render_to_response(['admin/%s/%s/change_list.html' % (app_label, cl.opts.object_name.lower()), 793 'admin/%s/change_list.html' % app_label, 794 'admin/change_list.html'], context_instance=c)
795 change_list = staff_member_required(never_cache(change_list)) 796