diff options
Diffstat (limited to 'django/contrib/admin/views/main.py')
-rw-r--r-- | django/contrib/admin/views/main.py | 622 |
1 files changed, 40 insertions, 582 deletions
diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py index f1f620ed9e..926270cc68 100644 --- a/django/contrib/admin/views/main.py +++ b/django/contrib/admin/views/main.py @@ -1,20 +1,13 @@ -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.contrib.admin.options import IncorrectLookupParameters +from django.contrib.admin.util import quote from django.core.paginator import Paginator, 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.translation import ugettext from django.utils.safestring import mark_safe +from django.utils.http import urlencode import operator try: @@ -22,13 +15,6 @@ try: 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 @@ -45,523 +31,20 @@ 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('<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script>' % \ - # 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: <a href="../../../../%s/%s/%s/">%s</a>' % - (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: <a href="../../../../%s/%s/%s/">%s</a>' % \ - (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' <a href="../../../../%s/%s/%s/">%s</a>' % \ - (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: <a href="../../%s/">%s</a>' % (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): + def __init__(self, request, model, list_display, list_display_links, list_filter, date_hierarchy, search_fields, list_select_related, list_per_page, model_admin): self.model = model self.opts = model._meta self.lookup_opts = self.opts - self.manager = self.opts.admin.manager + self.root_query_set = model_admin.queryset(request) + self.list_display = list_display + self.list_display_links = list_display_links + self.list_filter = list_filter + self.date_hierarchy = date_hierarchy + self.search_fields = search_fields + self.list_select_related = list_select_related + self.list_per_page = list_per_page + self.model_admin = model_admin # Get search parameters from the query string. try: @@ -580,17 +63,16 @@ class ChangeList(object): 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.title = (self.is_popup and ugettext('Select %s') % force_unicode(self.opts.verbose_name) or ugettext('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] + if self.list_filter and not self.opts.one_to_one_field: + filter_fields = [self.lookup_opts.get_field(field_name) for field_name in self.list_filter] for f in filter_fields: - spec = FilterSpec.create(f, request, self.params, self.model) + spec = FilterSpec.create(f, request, self.params, self.model, self.model_admin) if spec and spec.has_output(): filter_specs.append(spec) return filter_specs, bool(filter_specs) @@ -604,15 +86,15 @@ class ChangeList(object): 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: + if v is None: + if k in p: + del p[k] + else: p[k] = v - return mark_safe('?' + '&'.join([u'%s=%s' % (k, v) for k, v in p.items()]).replace(' ', '%20')) + return '?%s' % urlencode(p) def get_results(self, request): - paginator = Paginator(self.query_set, self.lookup_opts.admin.list_per_page) - + paginator = Paginator(self.query_set, self.list_per_page) # Get the number of objects, with admin filters applied. try: result_count = paginator.count @@ -630,10 +112,10 @@ class ChangeList(object): if not self.query_set.query.where: full_result_count = result_count else: - full_result_count = self.manager.count() + full_result_count = self.root_query_set.count() can_show_all = result_count <= MAX_SHOW_ALL_ALLOWED - multi_page = result_count > self.lookup_opts.admin.list_per_page + multi_page = result_count > self.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: @@ -657,7 +139,7 @@ class ChangeList(object): # 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] + ordering = self.model_admin.ordering or lookup_opts.ordering or ['-' + lookup_opts.pk.name] if ordering[0].startswith('-'): order_field, order_type = ordering[0][1:], 'desc' @@ -665,14 +147,14 @@ class ChangeList(object): order_field, order_type = ordering[0], 'asc' if ORDER_VAR in params: try: - field_name = lookup_opts.admin.list_display[int(params[ORDER_VAR])] + field_name = self.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 + # See whether field_name is a name of a non-field + # that allows sorting. try: - attr = getattr(lookup_opts.admin.manager.model, field_name) + attr = getattr(self.model, field_name) order_field = attr.admin_order_field except AttributeError: pass @@ -686,7 +168,7 @@ class ChangeList(object): return order_field, order_type def get_query_set(self): - qs = self.manager.get_query_set() + qs = self.root_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: @@ -703,10 +185,10 @@ class ChangeList(object): # Use select_related() if one of the list_display options is a field # with a relationship. - if self.lookup_opts.admin.list_select_related: + if self.list_select_related: qs = qs.select_related() else: - for field_name in self.lookup_opts.admin.list_display: + for field_name in self.list_display: try: f = self.lookup_opts.get_field(field_name) except models.FieldDoesNotExist: @@ -731,13 +213,17 @@ class ChangeList(object): else: return "%s__icontains" % field_name - if self.lookup_opts.admin.search_fields and self.query: + if self.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] + or_queries = [models.Q(**{construct_search(field_name): bit}) for field_name in self.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 + for field_name in self.search_fields: + if '__' in field_name: + qs = qs.distinct() + break if self.opts.one_to_one_field: qs = qs.complex_filter(self.opts.one_to_one_field.rel.limit_choices_to) @@ -746,31 +232,3 @@ class ChangeList(object): 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)) |