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
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
33
34 MAX_SHOW_ALL_ALLOWED = 200
35
36
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
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
52
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
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
90
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
117 - def __init__(self, field, field_mapping, original):
143
145 if self.original:
146 return self.original.__dict__[self.field.attname]
147
157
159 return repr(self.__dict__)
160
163
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
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
179 for bound_field in self.bound_fields:
180 yield bound_field
181
183 return len(self.bound_fields)
184
186 - def __init__(self, field_set, field_mapping, original):
191
193 for bound_field_line in self.bound_field_lines:
194 yield bound_field_line
195
197 return len(self.bound_field_lines)
198
231
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
248 post_url = '../'
249 else:
250
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
269
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
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
287 new_data = manipulator.flatten_data()
288
289
290 new_data.update(dict(request.GET.items()))
291
292 errors = {}
293
294
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
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
372 new_data = manipulator.flatten_data()
373
374
375
376
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
386 form = oldforms.FormWrapper(manipulator, new_data, errors)
387 form.original = manipulator.original_object
388 form.order_objects = []
389
390
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
410 current = obj
411 for i in range(depth):
412 current = current[-1]
413 current.append(val)
414
416 "Helper function that recursively populates deleted_objects."
417 nh = _nest_help
418 if current_depth > 16:
419 return
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
437 continue
438 if related.field.rel.edit_inline or not related.opts.admin:
439
440
441 nh(deleted_objects, current_depth, [mark_safe(u'%s: %s' % (force_unicode(capfirst(related.opts.verbose_name)), sub_obj)), []])
442 else:
443
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
456
457 nh(deleted_objects, current_depth, [u'%s: %s' % (force_unicode(capfirst(related.opts.verbose_name)), escape(sub_obj)), []])
458 else:
459
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
464
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
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
486
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
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
496
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
513
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:
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
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
586
597
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('?' + '&'.join([u'%s=%s' % (k, v) for k, v in p.items()]).replace(' ', '%20'))
612
653
655 lookup_opts, params = self.lookup_opts, self.params
656
657
658
659
660 ordering = lookup_opts.admin.ordering or lookup_opts.ordering or ['-' + lookup_opts.pk.name]
661
662
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
676
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
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
692 qs = self.manager.get_query_set()
693 lookup_params = self.params.copy()
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
700
701 del lookup_params[key]
702 lookup_params[smart_str(key)] = value
703
704
705 qs = qs.filter(**lookup_params)
706
707
708
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
723
724
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
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
739 qs = qs.order_by((self.order_type == 'desc' and '-' or '') + lookup_order_field)
740
741
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
767 return "%s/" % quote(getattr(result, self.pk_attname))
768
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
779
780
781
782
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