diff -r 57b4279d8c4e -r 03e267d67478 app/django/contrib/admin/views/main.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/django/contrib/admin/views/main.py Fri Jul 18 18:22:23 2008 +0000 @@ -0,0 +1,776 @@ +from django import oldforms, template +from django.conf import settings +from django.contrib.admin.filterspecs import FilterSpec +from django.contrib.admin.views.decorators import staff_member_required +from django.views.decorators.cache import never_cache +from django.contrib.contenttypes.models import ContentType +from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist, PermissionDenied +from django.core.paginator import QuerySetPaginator, InvalidPage +from django.shortcuts import get_object_or_404, render_to_response +from django.db import models +from django.db.models.query import QuerySet +from django.http import Http404, HttpResponse, HttpResponseRedirect +from django.utils.html import escape +from django.utils.text import capfirst, get_text_list +from django.utils.encoding import force_unicode, smart_str +from django.utils.translation import ugettext as _ +from django.utils.safestring import mark_safe +import operator + +try: + set +except NameError: + from sets import Set as set # Python 2.3 fallback + +from django.contrib.admin.models import LogEntry, ADDITION, CHANGE, DELETION +if not LogEntry._meta.installed: + raise ImproperlyConfigured, "You'll need to put 'django.contrib.admin' in your INSTALLED_APPS setting before you can use the admin application." + +if 'django.core.context_processors.auth' not in settings.TEMPLATE_CONTEXT_PROCESSORS: + 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." + +# The system will display a "Show all" link on the change list only if the +# total result count is less than or equal to this setting. +MAX_SHOW_ALL_ALLOWED = 200 + +# Changelist settings +ALL_VAR = 'all' +ORDER_VAR = 'o' +ORDER_TYPE_VAR = 'ot' +PAGE_VAR = 'p' +SEARCH_VAR = 'q' +IS_POPUP_VAR = 'pop' +ERROR_FLAG = 'e' + +# Text to display within change-list table cells if the value is blank. +EMPTY_CHANGELIST_VALUE = '(None)' + +use_raw_id_admin = lambda field: isinstance(field.rel, (models.ManyToOneRel, models.ManyToManyRel)) and field.rel.raw_id_admin + +class IncorrectLookupParameters(Exception): + pass + +def quote(s): + """ + Ensure that primary key values do not confuse the admin URLs by escaping + any '/', '_' and ':' characters. Similar to urllib.quote, except that the + quoting is slightly different so that it doesn't get automatically + unquoted by the Web browser. + """ + if type(s) != type(''): + return s + res = list(s) + for i in range(len(res)): + c = res[i] + if c in ':/_': + res[i] = '_%02X' % ord(c) + return ''.join(res) + +def unquote(s): + """ + Undo the effects of quote(). Based heavily on urllib.unquote(). + """ + mychr = chr + myatoi = int + list = s.split('_') + res = [list[0]] + myappend = res.append + del list[0] + for item in list: + if item[1:2]: + try: + myappend(mychr(myatoi(item[:2], 16)) + item[2:]) + except ValueError: + myappend('_' + item) + else: + myappend('_' + item) + return "".join(res) + +def get_javascript_imports(opts, auto_populated_fields, field_sets): +# Put in any necessary JavaScript imports. + js = ['js/core.js', 'js/admin/RelatedObjectLookups.js'] + if auto_populated_fields: + js.append('js/urlify.js') + if opts.has_field_type(models.DateTimeField) or opts.has_field_type(models.TimeField) or opts.has_field_type(models.DateField): + js.extend(['js/calendar.js', 'js/admin/DateTimeShortcuts.js']) + if opts.get_ordered_objects(): + js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js']) + if opts.admin.js: + js.extend(opts.admin.js) + seen_collapse = False + for field_set in field_sets: + if not seen_collapse and 'collapse' in field_set.classes: + seen_collapse = True + js.append('js/admin/CollapsedFieldsets.js') + + for field_line in field_set: + try: + for f in field_line: + if f.rel and isinstance(f, models.ManyToManyField) and f.rel.filter_interface: + js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js']) + raise StopIteration + except StopIteration: + break + return js + +class AdminBoundField(object): + def __init__(self, field, field_mapping, original): + self.field = field + self.original = original + self.form_fields = [field_mapping[name] for name in self.field.get_manipulator_field_names('')] + self.element_id = self.form_fields[0].get_id() + self.has_label_first = not isinstance(self.field, models.BooleanField) + self.raw_id_admin = use_raw_id_admin(field) + self.is_date_time = isinstance(field, models.DateTimeField) + self.is_file_field = isinstance(field, models.FileField) + self.needs_add_label = field.rel and (isinstance(field.rel, models.ManyToOneRel) or isinstance(field.rel, models.ManyToManyRel)) and field.rel.to._meta.admin + self.hidden = isinstance(self.field, models.AutoField) + self.first = False + + classes = [] + if self.raw_id_admin: + classes.append('nowrap') + if max([bool(f.errors()) for f in self.form_fields]): + classes.append('error') + if classes: + self.cell_class_attribute = u' class="%s" ' % ' '.join(classes) + self._repr_filled = False + + if field.rel: + self.related_url = mark_safe(u'../../../%s/%s/' + % (field.rel.to._meta.app_label, + field.rel.to._meta.object_name.lower())) + + def original_value(self): + if self.original: + return self.original.__dict__[self.field.attname] + + def existing_display(self): + try: + return self._display + except AttributeError: + if isinstance(self.field.rel, models.ManyToOneRel): + self._display = force_unicode(getattr(self.original, self.field.name), strings_only=True) + elif isinstance(self.field.rel, models.ManyToManyRel): + self._display = u", ".join([force_unicode(obj) for obj in getattr(self.original, self.field.name).all()]) + return self._display + + def __repr__(self): + return repr(self.__dict__) + + def html_error_list(self): + return mark_safe(" ".join([form_field.html_error_list() for form_field in self.form_fields if form_field.errors])) + + def original_url(self): + if self.is_file_field and self.original and self.field.attname: + url_method = getattr(self.original, 'get_%s_url' % self.field.attname) + if callable(url_method): + return url_method() + return '' + +class AdminBoundFieldLine(object): + def __init__(self, field_line, field_mapping, original): + self.bound_fields = [field.bind(field_mapping, original, AdminBoundField) for field in field_line] + for bound_field in self: + bound_field.first = True + break + + def __iter__(self): + for bound_field in self.bound_fields: + yield bound_field + + def __len__(self): + return len(self.bound_fields) + +class AdminBoundFieldSet(object): + def __init__(self, field_set, field_mapping, original): + self.name = field_set.name + self.classes = field_set.classes + self.description = field_set.description + self.bound_field_lines = [field_line.bind(field_mapping, original, AdminBoundFieldLine) for field_line in field_set] + + def __iter__(self): + for bound_field_line in self.bound_field_lines: + yield bound_field_line + + def __len__(self): + return len(self.bound_field_lines) + +def render_change_form(model, manipulator, context, add=False, change=False, form_url=''): + opts = model._meta + app_label = opts.app_label + auto_populated_fields = [f for f in opts.fields if f.prepopulate_from] + field_sets = opts.admin.get_field_sets(opts) + original = getattr(manipulator, 'original_object', None) + bound_field_sets = [field_set.bind(context['form'], original, AdminBoundFieldSet) for field_set in field_sets] + first_form_field_id = bound_field_sets[0].bound_field_lines[0].bound_fields[0].form_fields[0].get_id(); + ordered_objects = opts.get_ordered_objects() + inline_related_objects = opts.get_followed_related_objects(manipulator.follow) + extra_context = { + 'add': add, + 'change': change, + 'has_delete_permission': context['perms'][app_label][opts.get_delete_permission()], + 'has_change_permission': context['perms'][app_label][opts.get_change_permission()], + 'has_file_field': opts.has_field_type(models.FileField), + 'has_absolute_url': hasattr(model, 'get_absolute_url'), + 'auto_populated_fields': auto_populated_fields, + 'bound_field_sets': bound_field_sets, + 'first_form_field_id': first_form_field_id, + 'javascript_imports': get_javascript_imports(opts, auto_populated_fields, field_sets), + 'ordered_objects': ordered_objects, + 'inline_related_objects': inline_related_objects, + 'form_url': mark_safe(form_url), + 'opts': opts, + 'content_type_id': ContentType.objects.get_for_model(model).id, + } + context.update(extra_context) + return render_to_response([ + "admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()), + "admin/%s/change_form.html" % app_label, + "admin/change_form.html"], context_instance=context) + +def index(request): + return render_to_response('admin/index.html', {'title': _('Site administration')}, context_instance=template.RequestContext(request)) +index = staff_member_required(never_cache(index)) + +def add_stage(request, app_label, model_name, show_delete=False, form_url='', post_url=None, post_url_continue='../%s/', object_id_override=None): + model = models.get_model(app_label, model_name) + if model is None: + raise Http404("App %r, model %r, not found" % (app_label, model_name)) + opts = model._meta + + if not request.user.has_perm(app_label + '.' + opts.get_add_permission()): + raise PermissionDenied + + if post_url is None: + if request.user.has_perm(app_label + '.' + opts.get_change_permission()): + # redirect to list view + post_url = '../' + else: + # Object list will give 'Permission Denied', so go back to admin home + post_url = '../../../' + + manipulator = model.AddManipulator() + if request.POST: + new_data = request.POST.copy() + + if opts.has_field_type(models.FileField): + new_data.update(request.FILES) + + errors = manipulator.get_validation_errors(new_data) + manipulator.do_html2python(new_data) + + if not errors: + new_object = manipulator.save(new_data) + pk_value = new_object._get_pk_val() + LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(model).id, pk_value, force_unicode(new_object), ADDITION) + msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(new_object)} + # Here, we distinguish between different save types by checking for + # the presence of keys in request.POST. + if "_continue" in request.POST: + request.user.message_set.create(message=msg + ' ' + _("You may edit it again below.")) + if "_popup" in request.POST: + post_url_continue += "?_popup=1" + return HttpResponseRedirect(post_url_continue % pk_value) + if "_popup" in request.POST: + return HttpResponse('' % \ + # escape() calls force_unicode. + (escape(pk_value), escape(new_object))) + elif "_addanother" in request.POST: + request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name))) + return HttpResponseRedirect(request.path) + else: + request.user.message_set.create(message=msg) + return HttpResponseRedirect(post_url) + else: + # Add default data. + new_data = manipulator.flatten_data() + + # Override the defaults with GET params, if they exist. + new_data.update(dict(request.GET.items())) + + errors = {} + + # Populate the FormWrapper. + form = oldforms.FormWrapper(manipulator, new_data, errors) + + c = template.RequestContext(request, { + 'title': _('Add %s') % force_unicode(opts.verbose_name), + 'form': form, + 'is_popup': '_popup' in request.REQUEST, + 'show_delete': show_delete, + }) + + if object_id_override is not None: + c['object_id'] = object_id_override + + return render_change_form(model, manipulator, c, add=True) +add_stage = staff_member_required(never_cache(add_stage)) + +def change_stage(request, app_label, model_name, object_id): + model = models.get_model(app_label, model_name) + object_id = unquote(object_id) + if model is None: + raise Http404("App %r, model %r, not found" % (app_label, model_name)) + opts = model._meta + + if not request.user.has_perm(app_label + '.' + opts.get_change_permission()): + raise PermissionDenied + + if request.POST and "_saveasnew" in request.POST: + return add_stage(request, app_label, model_name, form_url='../../add/') + + try: + manipulator = model.ChangeManipulator(object_id) + except model.DoesNotExist: + raise Http404('%s object with primary key %r does not exist' % (model_name, escape(object_id))) + + if request.POST: + new_data = request.POST.copy() + + if opts.has_field_type(models.FileField): + new_data.update(request.FILES) + + errors = manipulator.get_validation_errors(new_data) + manipulator.do_html2python(new_data) + + if not errors: + new_object = manipulator.save(new_data) + pk_value = new_object._get_pk_val() + + # Construct the change message. + change_message = [] + if manipulator.fields_added: + change_message.append(_('Added %s.') % get_text_list(manipulator.fields_added, _('and'))) + if manipulator.fields_changed: + change_message.append(_('Changed %s.') % get_text_list(manipulator.fields_changed, _('and'))) + if manipulator.fields_deleted: + change_message.append(_('Deleted %s.') % get_text_list(manipulator.fields_deleted, _('and'))) + change_message = ' '.join(change_message) + if not change_message: + change_message = _('No fields changed.') + LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(model).id, pk_value, force_unicode(new_object), CHANGE, change_message) + + msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(new_object)} + if "_continue" in request.POST: + request.user.message_set.create(message=msg + ' ' + _("You may edit it again below.")) + if '_popup' in request.REQUEST: + return HttpResponseRedirect(request.path + "?_popup=1") + else: + return HttpResponseRedirect(request.path) + elif "_saveasnew" in request.POST: + 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)}) + return HttpResponseRedirect("../%s/" % pk_value) + elif "_addanother" in request.POST: + request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name))) + return HttpResponseRedirect("../add/") + else: + request.user.message_set.create(message=msg) + return HttpResponseRedirect("../") + else: + # Populate new_data with a "flattened" version of the current data. + new_data = manipulator.flatten_data() + + # TODO: do this in flatten_data... + # If the object has ordered objects on its admin page, get the existing + # order and flatten it into a comma-separated list of IDs. + + id_order_list = [] + for rel_obj in opts.get_ordered_objects(): + id_order_list.extend(getattr(manipulator.original_object, 'get_%s_order' % rel_obj.object_name.lower())()) + if id_order_list: + new_data['order_'] = ','.join(map(str, id_order_list)) + errors = {} + + # Populate the FormWrapper. + form = oldforms.FormWrapper(manipulator, new_data, errors) + form.original = manipulator.original_object + form.order_objects = [] + + #TODO Should be done in flatten_data / FormWrapper construction + for related in opts.get_followed_related_objects(): + wrt = related.opts.order_with_respect_to + if wrt and wrt.rel and wrt.rel.to == opts: + func = getattr(manipulator.original_object, 'get_%s_list' % + related.get_accessor_name()) + orig_list = func() + form.order_objects.extend(orig_list) + + c = template.RequestContext(request, { + 'title': _('Change %s') % force_unicode(opts.verbose_name), + 'form': form, + 'object_id': object_id, + 'original': manipulator.original_object, + 'is_popup': '_popup' in request.REQUEST, + }) + return render_change_form(model, manipulator, c, change=True) +change_stage = staff_member_required(never_cache(change_stage)) + +def _nest_help(obj, depth, val): + current = obj + for i in range(depth): + current = current[-1] + current.append(val) + +def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current_depth): + "Helper function that recursively populates deleted_objects." + nh = _nest_help # Bind to local variable for performance + if current_depth > 16: + return # Avoid recursing too deep. + opts_seen = [] + for related in opts.get_all_related_objects(): + if related.opts in opts_seen: + continue + opts_seen.append(related.opts) + rel_opts_name = related.get_accessor_name() + if isinstance(related.field.rel, models.OneToOneRel): + try: + sub_obj = getattr(obj, rel_opts_name) + except ObjectDoesNotExist: + pass + else: + if related.opts.admin: + p = '%s.%s' % (related.opts.app_label, related.opts.get_delete_permission()) + if not user.has_perm(p): + perms_needed.add(related.opts.verbose_name) + # We don't care about populating deleted_objects now. + continue + if related.field.rel.edit_inline or not related.opts.admin: + # Don't display link to edit, because it either has no + # admin or is edited inline. + nh(deleted_objects, current_depth, [mark_safe(u'%s: %s' % (force_unicode(capfirst(related.opts.verbose_name)), sub_obj)), []]) + else: + # Display a link to the admin page. + nh(deleted_objects, current_depth, [mark_safe(u'%s: %s' % + (escape(force_unicode(capfirst(related.opts.verbose_name))), + related.opts.app_label, + related.opts.object_name.lower(), + sub_obj._get_pk_val(), sub_obj)), []]) + _get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2) + else: + has_related_objs = False + for sub_obj in getattr(obj, rel_opts_name).all(): + has_related_objs = True + if related.field.rel.edit_inline or not related.opts.admin: + # Don't display link to edit, because it either has no + # admin or is edited inline. + nh(deleted_objects, current_depth, [u'%s: %s' % (force_unicode(capfirst(related.opts.verbose_name)), escape(sub_obj)), []]) + else: + # Display a link to the admin page. + nh(deleted_objects, current_depth, [mark_safe(u'%s: %s' % \ + (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))), []]) + _get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2) + # If there were related objects, and the user doesn't have + # permission to delete them, add the missing perm to perms_needed. + if related.opts.admin and has_related_objs: + p = '%s.%s' % (related.opts.app_label, related.opts.get_delete_permission()) + if not user.has_perm(p): + perms_needed.add(related.opts.verbose_name) + for related in opts.get_all_related_many_to_many_objects(): + if related.opts in opts_seen: + continue + opts_seen.append(related.opts) + rel_opts_name = related.get_accessor_name() + has_related_objs = False + + # related.get_accessor_name() could return None for symmetrical relationships + if rel_opts_name: + rel_objs = getattr(obj, rel_opts_name, None) + if rel_objs: + has_related_objs = True + + if has_related_objs: + for sub_obj in rel_objs.all(): + if related.field.rel.edit_inline or not related.opts.admin: + # Don't display link to edit, because it either has no + # admin or is edited inline. + nh(deleted_objects, current_depth, [_('One or more %(fieldname)s in %(name)s: %(obj)s') % \ + {'fieldname': force_unicode(related.field.verbose_name), 'name': force_unicode(related.opts.verbose_name), 'obj': escape(sub_obj)}, []]) + else: + # Display a link to the admin page. + nh(deleted_objects, current_depth, [ + 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))}) + \ + (u' %s' % \ + (related.opts.app_label, related.opts.module_name, sub_obj._get_pk_val(), escape(sub_obj)))), []]) + # If there were related objects, and the user doesn't have + # permission to change them, add the missing perm to perms_needed. + if related.opts.admin and has_related_objs: + p = u'%s.%s' % (related.opts.app_label, related.opts.get_change_permission()) + if not user.has_perm(p): + perms_needed.add(related.opts.verbose_name) + +def delete_stage(request, app_label, model_name, object_id): + model = models.get_model(app_label, model_name) + object_id = unquote(object_id) + if model is None: + raise Http404("App %r, model %r, not found" % (app_label, model_name)) + opts = model._meta + if not request.user.has_perm(app_label + '.' + opts.get_delete_permission()): + raise PermissionDenied + obj = get_object_or_404(model, pk=object_id) + + # Populate deleted_objects, a data structure of all related objects that + # will also be deleted. + deleted_objects = [mark_safe(u'%s: %s' % (escape(force_unicode(capfirst(opts.verbose_name))), force_unicode(object_id), escape(obj))), []] + perms_needed = set() + _get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1) + + if request.POST: # The user has already confirmed the deletion. + if perms_needed: + raise PermissionDenied + obj_display = force_unicode(obj) + obj.delete() + LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(model).id, object_id, obj_display, DELETION) + request.user.message_set.create(message=_('The %(name)s "%(obj)s" was deleted successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': obj_display}) + return HttpResponseRedirect("../../") + extra_context = { + "title": _("Are you sure?"), + "object_name": force_unicode(opts.verbose_name), + "object": obj, + "deleted_objects": deleted_objects, + "perms_lacking": perms_needed, + "opts": model._meta, + } + return render_to_response(["admin/%s/%s/delete_confirmation.html" % (app_label, opts.object_name.lower() ), + "admin/%s/delete_confirmation.html" % app_label , + "admin/delete_confirmation.html"], extra_context, context_instance=template.RequestContext(request)) +delete_stage = staff_member_required(never_cache(delete_stage)) + +def history(request, app_label, model_name, object_id): + model = models.get_model(app_label, model_name) + object_id = unquote(object_id) + if model is None: + raise Http404("App %r, model %r, not found" % (app_label, model_name)) + action_list = LogEntry.objects.filter(object_id=object_id, + content_type__id__exact=ContentType.objects.get_for_model(model).id).select_related().order_by('action_time') + # If no history was found, see whether this object even exists. + obj = get_object_or_404(model, pk=object_id) + extra_context = { + 'title': _('Change history: %s') % obj, + 'action_list': action_list, + 'module_name': force_unicode(capfirst(model._meta.verbose_name_plural)), + 'object': obj, + } + return render_to_response(["admin/%s/%s/object_history.html" % (app_label, model._meta.object_name.lower()), + "admin/%s/object_history.html" % app_label , + "admin/object_history.html"], extra_context, context_instance=template.RequestContext(request)) +history = staff_member_required(never_cache(history)) + +class ChangeList(object): + def __init__(self, request, model): + self.model = model + self.opts = model._meta + self.lookup_opts = self.opts + self.manager = self.opts.admin.manager + + # Get search parameters from the query string. + try: + self.page_num = int(request.GET.get(PAGE_VAR, 0)) + except ValueError: + self.page_num = 0 + self.show_all = ALL_VAR in request.GET + self.is_popup = IS_POPUP_VAR in request.GET + self.params = dict(request.GET.items()) + if PAGE_VAR in self.params: + del self.params[PAGE_VAR] + if ERROR_FLAG in self.params: + del self.params[ERROR_FLAG] + + self.order_field, self.order_type = self.get_ordering() + self.query = request.GET.get(SEARCH_VAR, '') + self.query_set = self.get_query_set() + self.get_results(request) + 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)) + self.filter_specs, self.has_filters = self.get_filters(request) + self.pk_attname = self.lookup_opts.pk.attname + + def get_filters(self, request): + filter_specs = [] + if self.lookup_opts.admin.list_filter and not self.opts.one_to_one_field: + filter_fields = [self.lookup_opts.get_field(field_name) \ + for field_name in self.lookup_opts.admin.list_filter] + for f in filter_fields: + spec = FilterSpec.create(f, request, self.params, self.model) + if spec and spec.has_output(): + filter_specs.append(spec) + return filter_specs, bool(filter_specs) + + def get_query_string(self, new_params=None, remove=None): + if new_params is None: new_params = {} + if remove is None: remove = [] + p = self.params.copy() + for r in remove: + for k in p.keys(): + if k.startswith(r): + del p[k] + for k, v in new_params.items(): + if k in p and v is None: + del p[k] + elif v is not None: + p[k] = v + return mark_safe('?' + '&'.join([u'%s=%s' % (k, v) for k, v in p.items()]).replace(' ', '%20')) + + def get_results(self, request): + paginator = QuerySetPaginator(self.query_set, self.lookup_opts.admin.list_per_page) + + # Get the number of objects, with admin filters applied. + try: + result_count = paginator.count + # Naked except! Because we don't have any other way of validating + # "params". They might be invalid if the keyword arguments are + # incorrect, or if the values are not in the correct type (which would + # result in a database error). + except: + raise IncorrectLookupParameters + + # Get the total number of objects, with no admin filters applied. + # Perform a slight optimization: Check to see whether any filters were + # given. If not, use paginator.hits to calculate the number of objects, + # because we've already done paginator.hits and the value is cached. + if not self.query_set.query.where: + full_result_count = result_count + else: + full_result_count = self.manager.count() + + can_show_all = result_count <= MAX_SHOW_ALL_ALLOWED + multi_page = result_count > self.lookup_opts.admin.list_per_page + + # Get the list of objects to display on this page. + if (self.show_all and can_show_all) or not multi_page: + result_list = list(self.query_set) + else: + try: + result_list = paginator.page(self.page_num+1).object_list + except InvalidPage: + result_list = () + + self.result_count = result_count + self.full_result_count = full_result_count + self.result_list = result_list + self.can_show_all = can_show_all + self.multi_page = multi_page + self.paginator = paginator + + def get_ordering(self): + lookup_opts, params = self.lookup_opts, self.params + # For ordering, first check the "ordering" parameter in the admin + # options, then check the object's default ordering. If neither of + # those exist, order descending by ID by default. Finally, look for + # manually-specified ordering from the query string. + ordering = lookup_opts.admin.ordering or lookup_opts.ordering or ['-' + lookup_opts.pk.name] + + if ordering[0].startswith('-'): + order_field, order_type = ordering[0][1:], 'desc' + else: + order_field, order_type = ordering[0], 'asc' + if ORDER_VAR in params: + try: + field_name = lookup_opts.admin.list_display[int(params[ORDER_VAR])] + try: + f = lookup_opts.get_field(field_name) + except models.FieldDoesNotExist: + # see if field_name is a name of a non-field + # that allows sorting + try: + attr = getattr(lookup_opts.admin.manager.model, field_name) + order_field = attr.admin_order_field + except AttributeError: + pass + else: + if not isinstance(f.rel, models.ManyToOneRel) or not f.null: + order_field = f.name + except (IndexError, ValueError): + pass # Invalid ordering specified. Just use the default. + if ORDER_TYPE_VAR in params and params[ORDER_TYPE_VAR] in ('asc', 'desc'): + order_type = params[ORDER_TYPE_VAR] + return order_field, order_type + + def get_query_set(self): + qs = self.manager.get_query_set() + lookup_params = self.params.copy() # a dictionary of the query string + for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR): + if i in lookup_params: + del lookup_params[i] + for key, value in lookup_params.items(): + if not isinstance(key, str): + # 'key' will be used as a keyword argument later, so Python + # requires it to be a string. + del lookup_params[key] + lookup_params[smart_str(key)] = value + + # Apply lookup parameters from the query string. + qs = qs.filter(**lookup_params) + + # Use select_related() if one of the list_display options is a field + # with a relationship. + if self.lookup_opts.admin.list_select_related: + qs = qs.select_related() + else: + for field_name in self.lookup_opts.admin.list_display: + try: + f = self.lookup_opts.get_field(field_name) + except models.FieldDoesNotExist: + pass + else: + if isinstance(f.rel, models.ManyToOneRel): + qs = qs.select_related() + break + + # Set ordering. + if self.order_field: + qs = qs.order_by('%s%s' % ((self.order_type == 'desc' and '-' or ''), self.order_field)) + + # Apply keyword searches. + def construct_search(field_name): + if field_name.startswith('^'): + return "%s__istartswith" % field_name[1:] + elif field_name.startswith('='): + return "%s__iexact" % field_name[1:] + elif field_name.startswith('@'): + return "%s__search" % field_name[1:] + else: + return "%s__icontains" % field_name + + if self.lookup_opts.admin.search_fields and self.query: + for bit in self.query.split(): + or_queries = [models.Q(**{construct_search(field_name): bit}) for field_name in self.lookup_opts.admin.search_fields] + other_qs = QuerySet(self.model) + other_qs.dup_select_related(qs) + other_qs = other_qs.filter(reduce(operator.or_, or_queries)) + qs = qs & other_qs + + if self.opts.one_to_one_field: + qs = qs.complex_filter(self.opts.one_to_one_field.rel.limit_choices_to) + + return qs + + def url_for_result(self, result): + return "%s/" % quote(getattr(result, self.pk_attname)) + +def change_list(request, app_label, model_name): + model = models.get_model(app_label, model_name) + if model is None: + raise Http404("App %r, model %r, not found" % (app_label, model_name)) + if not request.user.has_perm(app_label + '.' + model._meta.get_change_permission()): + raise PermissionDenied + try: + cl = ChangeList(request, model) + except IncorrectLookupParameters: + # Wacky lookup parameters were given, so redirect to the main + # changelist page, without parameters, and pass an 'invalid=1' + # parameter via the query string. If wacky parameters were given and + # the 'invalid=1' parameter was already in the query string, something + # is screwed up with the database, so display an error page. + if ERROR_FLAG in request.GET.keys(): + return render_to_response('admin/invalid_setup.html', {'title': _('Database error')}) + return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1') + c = template.RequestContext(request, { + 'title': cl.title, + 'is_popup': cl.is_popup, + 'cl': cl, + }) + c.update({'has_add_permission': c['perms'][app_label][cl.opts.get_add_permission()]}), + return render_to_response(['admin/%s/%s/change_list.html' % (app_label, cl.opts.object_name.lower()), + 'admin/%s/change_list.html' % app_label, + 'admin/change_list.html'], context_instance=c) +change_list = staff_member_required(never_cache(change_list))