diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py index 69f7fa41cb..d2f7a97557 100644 --- a/django/contrib/admin/templatetags/admin_list.py +++ b/django/contrib/admin/templatetags/admin_list.py @@ -1,6 +1,6 @@ -from django.contrib.admin.views.main import MAX_SHOW_ALL_ALLOWED, DEFAULT_RESULTS_PER_PAGE, ALL_VAR -from django.contrib.admin.views.main import ORDER_VAR, ORDER_TYPE_VAR, PAGE_VAR, SEARCH_VAR -from django.contrib.admin.views.main import IS_POPUP_VAR, EMPTY_CHANGELIST_VALUE, MONTHS +from django.contrib.admin.views.changelist import MAX_SHOW_ALL_ALLOWED, DEFAULT_RESULTS_PER_PAGE, ALL_VAR +from django.contrib.admin.views.changelist import ORDER_VAR, ORDER_TYPE_VAR, PAGE_VAR, SEARCH_VAR +from django.contrib.admin.views.changelist import IS_POPUP_VAR, EMPTY_CHANGELIST_VALUE, MONTHS from django.core import template from django.core.exceptions import ObjectDoesNotExist from django.db import models @@ -82,7 +82,7 @@ def result_headers(cl): if field_name == '__repr__': header = lookup_opts.verbose_name else: - func = getattr(cl.mod.Klass, field_name) # Let AttributeErrors propogate. + func = getattr(cl.model, field_name) # Let AttributeErrors propogate. try: header = func.short_description except AttributeError: diff --git a/django/contrib/admin/templatetags/admin_modify.py b/django/contrib/admin/templatetags/admin_modify.py index f3ef74d8a2..7f8c8753e2 100644 --- a/django/contrib/admin/templatetags/admin_modify.py +++ b/django/contrib/admin/templatetags/admin_modify.py @@ -2,7 +2,7 @@ from django.core import template, template_loader from django.utils.html import escape from django.utils.text import capfirst from django.utils.functional import curry -from django.contrib.admin.views.main import AdminBoundField +from django.contrib.admin.views.stages.modify import AdminBoundField from django.db.models.fields import BoundField, Field from django.db.models.related import BoundRelatedObject from django.db.models import TABULAR, STACKED diff --git a/django/contrib/admin/urls/admin.py b/django/contrib/admin/urls/admin.py index c08aaf47fe..8f6e799f8e 100644 --- a/django/contrib/admin/urls/admin.py +++ b/django/contrib/admin/urls/admin.py @@ -48,10 +48,10 @@ if 'ellington.media' in INSTALLED_APPS: ) urlpatterns += ( - ('^((?:[^/]+/)+?)add/$', 'django.contrib.admin.views.main.add_stage'), + ('^((?:[^/]+/)+?)add/$', 'django.contrib.admin.views.stages.add.add_stage'), ('^((?:[^/]+/)+?)([^/]+)/history/$', 'django.contrib.admin.views.main.history'), - ('^((?:[^/]+/)+?)([^/]+)/delete/$', 'django.contrib.admin.views.main.delete_stage'), - ('^((?:[^/]+/)+?)([^/]+)/change/$', 'django.contrib.admin.views.main.change_stage'), - ('^((?:[^/]+/)+?)$', 'django.contrib.admin.views.main.change_list' ), + ('^((?:[^/]+/)+?)([^/]+)/delete/$', 'django.contrib.admin.views.stages.delete.delete_stage'), + ('^((?:[^/]+/)+?)([^/]+)/change/$', 'django.contrib.admin.views.stages.change.change_stage'), + ('^((?:[^/]+/)+?)$', 'django.contrib.admin.views.changelist.change_list' ), ) urlpatterns = patterns('', *urlpatterns) diff --git a/django/contrib/admin/views/changelist.py b/django/contrib/admin/views/changelist.py new file mode 100644 index 0000000000..bfcbc3f43c --- /dev/null +++ b/django/contrib/admin/views/changelist.py @@ -0,0 +1,226 @@ +from django.contrib.admin.views.decorators import staff_member_required +from django.contrib.admin.views.main import get_model_and_app +from django.contrib.admin.filterspecs import FilterSpec +from django.db import models +from django.db.models.query import handle_legacy_orderlist +from django.utils.httpwrappers import HttpResponse, HttpResponseRedirect +from django.core.paginator import ObjectPaginator, InvalidPage +from django.core.extensions import DjangoContext as Context +from django.core.extensions import render_to_response +from django.utils.dates import MONTHS +# The system will display a "Show all" link only if the total result count +# is less than or equal to this setting. +MAX_SHOW_ALL_ALLOWED = 200 + +DEFAULT_RESULTS_PER_PAGE = 100 + +ALL_VAR = 'all' +ORDER_VAR = 'o' +ORDER_TYPE_VAR = 'ot' +PAGE_VAR = 'p' +SEARCH_VAR = 'q' +IS_POPUP_VAR = 'pop' + +# Text to display within changelist table cells if the value is blank. +EMPTY_CHANGELIST_VALUE = '(None)' + +class IncorrectLookupParameters(Exception): + pass + +class ChangeList(object): + def __init__(self, request, path): + self.resolve_model(path, request) + self.get_search_parameters(request) + self.get_ordering() + self.query = request.GET.get(SEARCH_VAR, '') + self.get_lookup_params() + self.get_results(request) + self.title = (self.is_popup + and _('Select %s') % self.opts.verbose_name + or _('Select %s to change') % self.opts.verbose_name) + self.get_filters(request) + self.pk_attname = self.lookup_opts.pk.attname + + def get_filters(self, request): + self.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) + if spec and spec.has_output(): + self.filter_specs.append(spec) + self.has_filters = bool(self.filter_specs) + + def get_query_string(self, new_params={}, 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 p.has_key(k) and v is None: + del p[k] + elif v is not None: + p[k] = v + return '?' + '&'.join(['%s=%s' % (k, v) for k, v in p.items()]).replace(' ', '%20') + + def resolve_model(self, path, request): + self.model, self.app_label = get_model_and_app(path) + # _get_mod_opts(app_label, module_name) + self.opts = self.model._meta + + if not request.user.has_perm(self.app_label + '.' + self.opts.get_change_permission()): + raise PermissionDenied + + self.lookup_opts = self.opts + self.manager = self.model._default_manager + + def get_search_parameters(self, request): + # 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 = request.GET.has_key(ALL_VAR) + self.is_popup = request.GET.has_key(IS_POPUP_VAR) + self.params = dict(request.GET.items()) + if self.params.has_key(PAGE_VAR): + del self.params[PAGE_VAR] + + def get_results(self, request): + manager, lookup_params, show_all, page_num = \ + self.manager, self.lookup_params, self.show_all, self.page_num + # Get the results. + try: + paginator = ObjectPaginator(manager, lookup_params, DEFAULT_RESULTS_PER_PAGE) + # 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 Exception: + raise + raise IncorrectLookupParameters() + + # Get the total number of objects, with no filters applied. + real_lookup_params = lookup_params.copy() + del real_lookup_params['order_by'] + if real_lookup_params: + full_result_count = manager.get_count() + else: + full_result_count = paginator.hits + del real_lookup_params + result_count = paginator.hits + can_show_all = result_count <= MAX_SHOW_ALL_ALLOWED + multi_page = result_count > DEFAULT_RESULTS_PER_PAGE + + # Get the list of objects to display on this page. + if (show_all and can_show_all) or not multi_page: + result_list = manager.get_list(**lookup_params) + else: + try: + result_list = paginator.get_page(page_num) + except InvalidPage: + result_list = [] + (self.result_count, self.full_result_count, self.result_list, + self.can_show_all, self.multi_page, self.paginator) = (result_count, + full_result_count, result_list, can_show_all, multi_page, paginator ) + + def url_for_result(self, result): + return "%s/change" % getattr(result, self.pk_attname) + + 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] + + # Normalize it to new-style ordering. + ordering = handle_legacy_orderlist(ordering) + + if ordering[0].startswith('-'): + order_field, order_type = ordering[0][1:], 'desc' + else: + order_field, order_type = ordering[0], 'asc' + if params.has_key(ORDER_VAR): + try: + try: + f = lookup_opts.get_field(lookup_opts.admin.list_display[int(params[ORDER_VAR])]) + except models.FieldDoesNotExist: + pass + else: + if not isinstance(f.rel, models.ManyToOne) or not f.null: + order_field = f.name + except (IndexError, ValueError): + pass # Invalid ordering specified. Just use the default. + if params.has_key(ORDER_TYPE_VAR) and params[ORDER_TYPE_VAR] in ('asc', 'desc'): + order_type = params[ORDER_TYPE_VAR] + self.order_field, self.order_type = order_field, order_type + + def get_lookup_params(self): + # Prepare the lookup parameters for the API lookup. + (params, order_field, lookup_opts, order_type, opts, query) = \ + (self.params, self.order_field, self.lookup_opts, self.order_type, self.opts, self.query) + + lookup_params = params.copy() + for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR): + if lookup_params.has_key(i): + del lookup_params[i] + # If the order-by field is a field with a relationship, order by the value + # in the related table. + lookup_order_field = order_field + try: + f = lookup_opts.get_field(order_field) + except models.FieldDoesNotExist: + pass + else: + if isinstance(lookup_opts.get_field(order_field).rel, models.ManyToOne): + f = lookup_opts.get_field(order_field) + rel_ordering = f.rel.to._meta.ordering and f.rel.to._meta.ordering[0] or f.rel.to._meta.pk.column + lookup_order_field = '%s.%s' % (f.rel.to._meta.db_table, rel_ordering) + # Use select_related if one of the list_display options is a field with a + # relationship. + if lookup_opts.admin.list_select_related: + lookup_params['select_related'] = True + else: + for field_name in lookup_opts.admin.list_display: + try: + f = lookup_opts.get_field(field_name) + except models.FieldDoesNotExist: + pass + else: + if isinstance(f.rel, models.ManyToOne): + lookup_params['select_related'] = True + break + lookup_params['order_by'] = ((order_type == 'desc' and '-' or '') + lookup_order_field,) + if lookup_opts.admin.search_fields and query: + complex_queries = [] + for bit in query.split(): + or_queries = [] + for field_name in lookup_opts.admin.search_fields: + or_queries.append(models.Q(**{'%s__icontains' % field_name: bit})) + complex_queries.append(reduce(operator.or_, or_queries)) + lookup_params['complex'] = reduce(operator.and_, complex_queries) + if opts.one_to_one_field: + lookup_params.update(opts.one_to_one_field.rel.limit_choices_to) + self.lookup_params = lookup_params + +def change_list(request, path): + try: + cl = ChangeList(request, path) + except IncorrectLookupParameters: + return HttpResponseRedirect(request.path) + + c = Context(request, { + 'title': cl.title, + 'is_popup': cl.is_popup, + 'cl': cl, + 'path': path[:path.rindex('/')] + }) + c.update({'has_add_permission': c['perms'][cl.app_label][cl.opts.get_add_permission()]}), + return render_to_response(['admin/%s/%s/change_list' % (cl.app_label, cl.opts.object_name.lower()), + 'admin/%s/change_list' % cl.app_label, + 'admin/change_list'], context_instance=c) +change_list = staff_member_required(change_list) diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py index 08d233507b..4848131743 100644 --- a/django/contrib/admin/views/main.py +++ b/django/contrib/admin/views/main.py @@ -1,43 +1,26 @@ # Generic admin views. from django.conf import settings from django.contrib.admin.views.decorators import staff_member_required -from django.contrib.admin.filterspecs import FilterSpec + from django.core import formfields, template from django.core.template import loader + from django.db import models -from django.db.models.fields import BoundField, BoundFieldLine, BoundFieldSet -from django.db.models.query import handle_legacy_orderlist + + from django.core.exceptions import Http404, ImproperlyConfigured, ObjectDoesNotExist, PermissionDenied from django.core.extensions import DjangoContext as Context from django.core.extensions import get_object_or_404, render_to_response -from django.core.paginator import ObjectPaginator, InvalidPage -try: - from django.contrib.admin.models import LogEntry, ADDITION, CHANGE, DELETION -except ImportError: - raise ImproperlyConfigured, "You don't have 'django.contrib.admin' in INSTALLED_APPS." + + from django.utils import dateformat -from django.utils.dates import MONTHS + from django.utils.html import escape, strip_tags from django.utils.httpwrappers import HttpResponse, HttpResponseRedirect from django.utils.text import capfirst, get_text_list import operator from itertools import izip -# The system will display a "Show all" link only if the total result count -# is less than or equal to this setting. -MAX_SHOW_ALL_ALLOWED = 200 - -DEFAULT_RESULTS_PER_PAGE = 100 - -ALL_VAR = 'all' -ORDER_VAR = 'o' -ORDER_TYPE_VAR = 'ot' -PAGE_VAR = 'p' -SEARCH_VAR = 'q' -IS_POPUP_VAR = 'pop' - -# Text to display within changelist table cells if the value is blank. -EMPTY_CHANGELIST_VALUE = '(None)' ADMIN_PREFIX = "/admin/" @@ -118,635 +101,6 @@ def index(request): return render_to_response('admin/index', {'title': _('Site administration')}, context_instance=Context(request)) index = staff_member_required(index) -class IncorrectLookupParameters(Exception): - pass - -class ChangeList(object): - def __init__(self, request, path): - self.resolve_model(path, request) - self.get_search_parameters(request) - self.get_ordering() - self.query = request.GET.get(SEARCH_VAR, '') - self.get_lookup_params() - self.get_results(request) - self.title = (self.is_popup - and _('Select %s') % self.opts.verbose_name - or _('Select %s to change') % self.opts.verbose_name) - self.get_filters(request) - self.pk_attname = self.lookup_opts.pk.attname - - def get_filters(self, request): - self.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) - if spec and spec.has_output(): - self.filter_specs.append(spec) - self.has_filters = bool(self.filter_specs) - - def get_query_string(self, new_params={}, 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 p.has_key(k) and v is None: - del p[k] - elif v is not None: - p[k] = v - return '?' + '&'.join(['%s=%s' % (k, v) for k, v in p.items()]).replace(' ', '%20') - - def resolve_model(self, path, request): - self.model, self.app_label = get_model_and_app(path) - # _get_mod_opts(app_label, module_name) - self.opts = self.model._meta - - if not request.user.has_perm(self.app_label + '.' + self.opts.get_change_permission()): - raise PermissionDenied - - self.lookup_opts = self.opts - self.manager = self.model._default_manager - - def get_search_parameters(self, request): - # 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 = request.GET.has_key(ALL_VAR) - self.is_popup = request.GET.has_key(IS_POPUP_VAR) - self.params = dict(request.GET.items()) - if self.params.has_key(PAGE_VAR): - del self.params[PAGE_VAR] - - def get_results(self, request): - manager, lookup_params, show_all, page_num = \ - self.manager, self.lookup_params, self.show_all, self.page_num - # Get the results. - try: - paginator = ObjectPaginator(manager, lookup_params, DEFAULT_RESULTS_PER_PAGE) - # 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 filters applied. - real_lookup_params = lookup_params.copy() - del real_lookup_params['order_by'] - if real_lookup_params: - full_result_count = manager.get_count() - else: - full_result_count = paginator.hits - del real_lookup_params - result_count = paginator.hits - can_show_all = result_count <= MAX_SHOW_ALL_ALLOWED - multi_page = result_count > DEFAULT_RESULTS_PER_PAGE - - # Get the list of objects to display on this page. - if (show_all and can_show_all) or not multi_page: - result_list = manager.get_list(**lookup_params) - else: - try: - result_list = paginator.get_page(page_num) - except InvalidPage: - result_list = [] - (self.result_count, self.full_result_count, self.result_list, - self.can_show_all, self.multi_page, self.paginator) = (result_count, - full_result_count, result_list, can_show_all, multi_page, paginator ) - - def url_for_result(self, result): - return "%s/change" % getattr(result, self.pk_attname) - - 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] - - # Normalize it to new-style ordering. - ordering = handle_legacy_orderlist(ordering) - - if ordering[0].startswith('-'): - order_field, order_type = ordering[0][1:], 'desc' - else: - order_field, order_type = ordering[0], 'asc' - if params.has_key(ORDER_VAR): - try: - try: - f = lookup_opts.get_field(lookup_opts.admin.list_display[int(params[ORDER_VAR])]) - except models.FieldDoesNotExist: - pass - else: - if not isinstance(f.rel, models.ManyToOne) or not f.null: - order_field = f.name - except (IndexError, ValueError): - pass # Invalid ordering specified. Just use the default. - if params.has_key(ORDER_TYPE_VAR) and params[ORDER_TYPE_VAR] in ('asc', 'desc'): - order_type = params[ORDER_TYPE_VAR] - self.order_field, self.order_type = order_field, order_type - - def get_lookup_params(self): - # Prepare the lookup parameters for the API lookup. - (params, order_field, lookup_opts, order_type, opts, query) = \ - (self.params, self.order_field, self.lookup_opts, self.order_type, self.opts, self.query) - - lookup_params = params.copy() - for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR): - if lookup_params.has_key(i): - del lookup_params[i] - # If the order-by field is a field with a relationship, order by the value - # in the related table. - lookup_order_field = order_field - try: - f = lookup_opts.get_field(order_field) - except models.FieldDoesNotExist: - pass - else: - if isinstance(lookup_opts.get_field(order_field).rel, models.ManyToOne): - f = lookup_opts.get_field(order_field) - rel_ordering = f.rel.to._meta.ordering and f.rel.to._meta.ordering[0] or f.rel.to._meta.pk.column - lookup_order_field = '%s.%s' % (f.rel.to._meta.db_table, rel_ordering) - # Use select_related if one of the list_display options is a field with a - # relationship. - if lookup_opts.admin.list_select_related: - lookup_params['select_related'] = True - else: - for field_name in lookup_opts.admin.list_display: - try: - f = lookup_opts.get_field(field_name) - except models.FieldDoesNotExist: - pass - else: - if isinstance(f.rel, models.ManyToOne): - lookup_params['select_related'] = True - break - lookup_params['order_by'] = ((order_type == 'desc' and '-' or '') + lookup_order_field,) - if lookup_opts.admin.search_fields and query: - complex_queries = [] - for bit in query.split(): - or_queries = [] - for field_name in lookup_opts.admin.search_fields: - or_queries.append(models.Q(**{'%s__icontains' % field_name: bit})) - complex_queries.append(reduce(operator.or_, or_queries)) - lookup_params['complex'] = reduce(operator.and_, complex_queries) - if opts.one_to_one_field: - lookup_params.update(opts.one_to_one_field.rel.limit_choices_to) - self.lookup_params = lookup_params - -def change_list(request, path): - try: - cl = ChangeList(request, path) - except IncorrectLookupParameters: - return HttpResponseRedirect(request.path) - - c = Context(request, { - 'title': cl.title, - 'is_popup': cl.is_popup, - 'cl': cl, - 'path': path[:path.rindex('/')] - }) - c.update({'has_add_permission': c['perms'][cl.app_label][cl.opts.get_add_permission()]}), - return render_to_response(['admin/%s/%s/change_list' % (cl.app_label, cl.opts.object_name.lower()), - 'admin/%s/change_list' % cl.app_label, - 'admin/change_list'], context_instance=c) -change_list = staff_member_required(change_list) - -use_raw_id_admin = lambda field: isinstance(field.rel, (models.ManyToOne, models.ManyToMany)) and field.rel.raw_id_admin - -def get_javascript_imports(opts, auto_populated_fields, ordered_objects, 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 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(BoundField): - def __init__(self, field, field_mapping, original): - super(AdminBoundField, self).__init__(field, field_mapping, original) - - 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.ManyToOne) or isinstance(field.rel, models.ManyToMany) 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 = ' class="%s" ' % ' '.join(classes) - self._repr_filled = False - - if field.rel: - self.related_url = url_for_model(field.rel.to) - - def _fetch_existing_display(self, func_name): - class_dict = self.original.__class__.__dict__ - func = class_dict.get(func_name) - return func(self.original) - - def _fill_existing_display(self): - if getattr(self, '_display_filled', False): - return - # HACK - if isinstance(self.field.rel, models.ManyToOne): - func_name = 'get_%s' % self.field.name - self._display = self._fetch_existing_display(func_name) - elif isinstance(self.field.rel, models.ManyToMany): - func_name = 'get_%s_list' % self.field.rel.singular - self._display = ", ".join([str(obj) for obj in self._fetch_existing_display(func_name)]) - self._display_filled = True - - def existing_display(self): - self._fill_existing_display() - return self._display - - def __repr__(self): - return repr(self.__dict__) - - def html_error_list(self): - return " ".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(BoundFieldLine): - def __init__(self, field_line, field_mapping, original): - super(AdminBoundFieldLine, self).__init__(field_line, field_mapping, original, AdminBoundField) - for bound_field in self: - bound_field.first = True - break - -class AdminBoundFieldSet(BoundFieldSet): - def __init__(self, field_set, field_mapping, original): - super(AdminBoundFieldSet, self).__init__(field_set, field_mapping, original, AdminBoundFieldLine) - -class BoundManipulator(object): - def __init__(self, model, manipulator, field_mapping): - self.model = model - self.opts = model._meta - self.inline_related_objects = self.opts.get_followed_related_objects(manipulator.follow) - self.original = hasattr(manipulator, 'original_object') and manipulator.original_object or None - self.bound_field_sets = [field_set.bind(field_mapping, self.original, AdminBoundFieldSet) - for field_set in self.opts.admin.get_field_sets(self.opts)] - self.ordered_objects = self.opts.get_ordered_objects()[:] - -class AdminBoundManipulator(BoundManipulator): - def __init__(self, model, manipulator, field_mapping): - super(AdminBoundManipulator, self).__init__(model, manipulator, field_mapping) - field_sets = self.opts.admin.get_field_sets(self.opts) - - self.auto_populated_fields = [f for f in self.opts.fields if f.prepopulate_from] - self.javascript_imports = get_javascript_imports(self.opts, self.auto_populated_fields, self.ordered_objects, field_sets); - - self.coltype = self.ordered_objects and 'colMS' or 'colM' - self.has_absolute_url = hasattr(model, 'get_absolute_url') - self.form_enc_attrib = self.opts.has_field_type(models.FileField) and \ - 'enctype="multipart/form-data" ' or '' - - self.first_form_field_id = self.bound_field_sets[0].bound_field_lines[0].bound_fields[0].form_fields[0].get_id(); - self.ordered_object_pk_names = [o.pk.name for o in self.ordered_objects] - - opts = self.opts - self.save_on_top = opts.admin.save_on_top - self.save_as = opts.admin.save_as - - self.content_type_id = opts.get_content_type_id() - self.verbose_name_plural = opts.verbose_name_plural - self.verbose_name = opts.verbose_name - self.object_name = opts.object_name - - def get_ordered_object_pk(self, ordered_obj): - for name in self.ordered_object_pk_names: - if hasattr(ordered_obj, name): - return str(getattr(ordered_obj, name)) - return "" - -def render_change_form(model, manipulator, app_label, context, add=False, change=False, show_delete=False, form_url=''): - opts = model._meta - extra_context = { - 'add': add, - 'change': change, - 'bound_manipulator': AdminBoundManipulator(model, manipulator, context['form']), - 'has_delete_permission': context['perms'][app_label][opts.get_delete_permission()], - 'form_url': form_url, - 'app_label': app_label, - } - context.update(extra_context) - return render_to_response(["admin/%s/%s/change_form" % (app_label, opts.object_name.lower() ), - "admin/%s/change_form" % app_label , - "admin/change_form"], context_instance=context) - -def log_add_message(user, opts, manipulator, new_object): - pk_value = getattr(new_object, opts.pk.attname) - LogEntry.objects.log_action(user.id, opts.get_content_type_id(), pk_value, str(new_object), ADDITION) - -def add_stage(request, path, show_delete=False, form_url='', post_url='../change/', post_url_continue='../%s/', object_id_override=None): - model, app_label = get_model_and_app(path) - opts = model._meta - - if not request.user.has_perm(app_label + '.' + opts.get_add_permission()): - raise PermissionDenied - 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 and not request.POST.has_key("_preview"): - new_object = manipulator.save(new_data) - log_add_message(request.user, opts, manipulator, new_object) - msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': opts.verbose_name, 'obj': new_object} - pk_value = getattr(new_object, opts.pk.attname) - # Here, we distinguish between different save types by checking for - # the presence of keys in request.POST. - if request.POST.has_key("_continue"): - request.user.add_message(msg + ' ' + _("You may edit it again below.")) - if request.POST.has_key("_popup"): - post_url_continue += "?_popup=1" - return HttpResponseRedirect(post_url_continue % pk_value) - if request.POST.has_key("_popup"): - return HttpResponse('' % \ - (pk_value, repr(new_object).replace('"', '\\"'))) - elif request.POST.has_key("_addanother"): - request.user.add_message(msg + ' ' + (_("You may add another %s below.") % opts.verbose_name)) - return HttpResponseRedirect(request.path) - else: - request.user.add_message(msg) - return HttpResponseRedirect(post_url) - else: - # Add default data. - new_data = manipulator.flatten_data() - - # Override the defaults with request.GET, if it exists. - new_data.update(request.GET) - errors = {} - - # Populate the FormWrapper. - form = formfields.FormWrapper(manipulator, new_data, errors, edit_inline=True) - - c = Context(request, { - 'title': _('Add %s') % opts.verbose_name, - 'form': form, - 'is_popup': request.REQUEST.has_key('_popup'), - 'show_delete': show_delete, - 'path': path , - }) - - if object_id_override is not None: - c['object_id'] = object_id_override - - return render_change_form(model, manipulator, app_label, c, add=True) -add_stage = staff_member_required(add_stage) - -def log_change_message(user, opts, manipulator, new_object): - pk_value = getattr(new_object, opts.pk.column) - # 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(user.id, opts.get_content_type_id(), pk_value, str(new_object), CHANGE, change_message) - -def change_stage(request, path, object_id): - model, app_label = get_model_and_app(path) - opts = model._meta - #mod, opts = _get_mod_opts(app_label, module_name) - if not request.user.has_perm(app_label + '.' + opts.get_change_permission()): - raise PermissionDenied - if request.POST and request.POST.has_key("_saveasnew"): - return add_stage(request, path, form_url='../add/') - try: - manipulator_class = model.ChangeManipulator - manipulator = manipulator_class(object_id) - except ObjectDoesNotExist: - raise Http404 - - 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 and not request.POST.has_key("_preview"): - new_object = manipulator.save(new_data) - log_change_message(request.user, opts, manipulator, new_object) - msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': opts.verbose_name, 'obj': new_object} - pk_value = getattr(new_object, opts.pk.attname) - if request.POST.has_key("_continue"): - request.user.add_message(msg + ' ' + _("You may edit it again below.")) - if request.REQUEST.has_key('_popup'): - return HttpResponseRedirect(request.path + "?_popup=1") - else: - return HttpResponseRedirect(request.path) - elif request.POST.has_key("_saveasnew"): - request.user.add_message(_('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': opts.verbose_name, 'obj': new_object}) - return HttpResponseRedirect("../%s/" % pk_value) - elif request.POST.has_key("_addanother"): - request.user.add_message(msg + ' ' + (_("You may add another %s below.") % opts.verbose_name)) - return HttpResponseRedirect("../add/") - else: - request.user.add_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 = formfields.FormWrapper(manipulator, new_data, errors, edit_inline = True) - 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._meta == opts: - func = getattr(manipulator.original_object, 'get_%s_list' % - related.get_method_name_part()) - orig_list = func() - form.order_objects.extend(orig_list) - - c = Context(request, { - 'title': _('Change %s') % opts.verbose_name, - 'form': form, - 'object_id': object_id, - 'original': manipulator.original_object, - 'is_popup': request.REQUEST.has_key('_popup'), - 'path': path , - }) - return render_change_form(model, manipulator, app_label, c, change=True) - -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_method_name_part() - if isinstance(related.field.rel, models.OneToOne): - try: - sub_obj = getattr(obj, 'get_%s' % 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, ['%s: %s' % (capfirst(related.opts.verbose_name), sub_obj), []]) - else: - # Display a link to the admin page. - nh(deleted_objects, current_depth, ['%s: %s' % \ - (capfirst(related.opts.verbose_name), related.opts.app_label, related.opts.module_name, - getattr(sub_obj, related.opts.pk.attname), 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, 'get_%s_list' % rel_opts_name)(): - 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, ['%s: %s' % (capfirst(related.opts.verbose_name), strip_tags(str(sub_obj))), []]) - else: - # Display a link to the admin page. - nh(deleted_objects, current_depth, ['%s: %s' % \ - (capfirst(related.opts.verbose_name), related.opts.app_label, related.opts.module_name, sub_obj.id, strip_tags(str(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(rel_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_method_name_part() - has_related_objs = False - for sub_obj in getattr(obj, 'get_%s_list' % rel_opts_name)(): - 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, [_('One or more %(fieldname)s in %(name)s: %(obj)s') % \ - {'fieldname': related.field.name, 'name': related.opts.verbose_name, 'obj': strip_tags(str(sub_obj))}, []]) - else: - # Display a link to the admin page. - nh(deleted_objects, current_depth, [ - (_('One or more %(fieldname)s in %(name)s:') % {'fieldname': related.field.name, 'name':related.opts.verbose_name}) + \ - (' %s' % \ - (related.opts.app_label, related.opts.module_name, sub_obj.id, strip_tags(str(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 = '%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, module_name, object_id): - import sets - mod, opts = _get_mod_opts(app_label, module_name) - if not request.user.has_perm(app_label + '.' + opts.get_delete_permission()): - raise PermissionDenied - obj = get_object_or_404(mod, pk=object_id) - - # Populate deleted_objects, a data structure of all related objects that - # will also be deleted. - deleted_objects = ['%s: %s' % (capfirst(opts.verbose_name), object_id, strip_tags(str(obj))), []] - perms_needed = sets.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 = str(obj) - obj.delete() - LogEntry.objects.log_action(request.user.id, opts.get_content_type_id(), object_id, obj_display, DELETION) - request.user.add_message(_('The %(name)s "%(obj)s" was deleted successfully.') % {'name':opts.verbose_name, 'obj':obj_display}) - return HttpResponseRedirect("../../") - return render_to_response('admin/delete_confirmation', { - "title": _("Are you sure?"), - "object_name": opts.verbose_name, - "object": obj, - "deleted_objects": deleted_objects, - "perms_lacking": perms_needed, - }, context_instance=Context(request)) -delete_stage = staff_member_required(delete_stage) - def history(request, app_label, module_name, object_id): mod, opts = _get_mod_opts(app_label, module_name) action_list = LogEntry.objects.get_list(object_id__exact=object_id, content_type__id__exact=opts.get_content_type_id(), diff --git a/django/contrib/admin/views/stages/__init__.py b/django/contrib/admin/views/stages/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/django/contrib/admin/views/stages/add.py b/django/contrib/admin/views/stages/add.py new file mode 100644 index 0000000000..672757663e --- /dev/null +++ b/django/contrib/admin/views/stages/add.py @@ -0,0 +1,74 @@ +from django.contrib.admin.views.main import get_model_and_app +from django.core import formfields, template +from django.core.extensions import DjangoContext as Context +from django.contrib.admin.views.stages.modify import render_change_form +from django.db import models +from django.utils.text import capfirst, get_text_list +from django.utils.httpwrappers import HttpResponse, HttpResponseRedirect +from django.contrib.admin.views.decorators import staff_member_required + + + +def log_add_message(user, opts, manipulator, new_object): + pk_value = getattr(new_object, opts.pk.attname) + LogEntry.objects.log_action(user.id, opts.get_content_type_id(), pk_value, str(new_object), ADDITION) + +def add_stage(request, path, show_delete=False, form_url='', post_url='../change/', post_url_continue='../%s/', object_id_override=None): + model, app_label = get_model_and_app(path) + opts = model._meta + + if not request.user.has_perm(app_label + '.' + opts.get_add_permission()): + raise PermissionDenied + 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 and not request.POST.has_key("_preview"): + new_object = manipulator.save(new_data) + log_add_message(request.user, opts, manipulator, new_object) + msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': opts.verbose_name, 'obj': new_object} + pk_value = getattr(new_object, opts.pk.attname) + # Here, we distinguish between different save types by checking for + # the presence of keys in request.POST. + if request.POST.has_key("_continue"): + request.user.add_message(msg + ' ' + _("You may edit it again below.")) + if request.POST.has_key("_popup"): + post_url_continue += "?_popup=1" + return HttpResponseRedirect(post_url_continue % pk_value) + if request.POST.has_key("_popup"): + return HttpResponse('' % \ + (pk_value, repr(new_object).replace('"', '\\"'))) + elif request.POST.has_key("_addanother"): + request.user.add_message(msg + ' ' + (_("You may add another %s below.") % opts.verbose_name)) + return HttpResponseRedirect(request.path) + else: + request.user.add_message(msg) + return HttpResponseRedirect(post_url) + else: + # Add default data. + new_data = manipulator.flatten_data() + + # Override the defaults with request.GET, if it exists. + new_data.update(request.GET) + errors = {} + + # Populate the FormWrapper. + form = formfields.FormWrapper(manipulator, new_data, errors, edit_inline=True) + + c = Context(request, { + 'title': _('Add %s') % opts.verbose_name, + 'form': form, + 'is_popup': request.REQUEST.has_key('_popup'), + 'show_delete': show_delete, + 'path': path , + }) + + if object_id_override is not None: + c['object_id'] = object_id_override + + return render_change_form(model, manipulator, app_label, c, add=True) +add_stage = staff_member_required(add_stage) diff --git a/django/contrib/admin/views/stages/change.py b/django/contrib/admin/views/stages/change.py new file mode 100644 index 0000000000..f67151b6e1 --- /dev/null +++ b/django/contrib/admin/views/stages/change.py @@ -0,0 +1,108 @@ +from django.contrib.admin.views.main import get_model_and_app +from django.core import formfields, template +from django.core.extensions import DjangoContext as Context +from django.contrib.admin.views.stages.modify import render_change_form +from django.db import models +from django.utils.text import capfirst, get_text_list +from django.utils.httpwrappers import HttpResponse, HttpResponseRedirect +from django.contrib.admin.views.decorators import staff_member_required +try: + from django.contrib.admin.models import LogEntry, ADDITION, CHANGE, DELETION +except ImportError: + raise ImproperlyConfigured, "You don't have 'django.contrib.admin' in INSTALLED_APPS." + +def log_change_message(user, opts, manipulator, new_object): + pk_value = getattr(new_object, opts.pk.column) + # 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(user.id, opts.get_content_type_id(), pk_value, str(new_object), CHANGE, change_message) + +def change_stage(request, path, object_id): + model, app_label = get_model_and_app(path) + opts = model._meta + #mod, opts = _get_mod_opts(app_label, module_name) + if not request.user.has_perm(app_label + '.' + opts.get_change_permission()): + raise PermissionDenied + if request.POST and request.POST.has_key("_saveasnew"): + return add_stage(request, path, form_url='../add/') + try: + manipulator_class = model.ChangeManipulator + manipulator = manipulator_class(object_id) + except ObjectDoesNotExist: + raise Http404 + + 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 and not request.POST.has_key("_preview"): + new_object = manipulator.save(new_data) + log_change_message(request.user, opts, manipulator, new_object) + msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': opts.verbose_name, 'obj': new_object} + pk_value = getattr(new_object, opts.pk.attname) + if request.POST.has_key("_continue"): + request.user.add_message(msg + ' ' + _("You may edit it again below.")) + if request.REQUEST.has_key('_popup'): + return HttpResponseRedirect(request.path + "?_popup=1") + else: + return HttpResponseRedirect(request.path) + elif request.POST.has_key("_saveasnew"): + request.user.add_message(_('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': opts.verbose_name, 'obj': new_object}) + return HttpResponseRedirect("../%s/" % pk_value) + elif request.POST.has_key("_addanother"): + request.user.add_message(msg + ' ' + (_("You may add another %s below.") % opts.verbose_name)) + return HttpResponseRedirect("../add/") + else: + request.user.add_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 = formfields.FormWrapper(manipulator, new_data, errors, edit_inline = True) + 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._meta == opts: + func = getattr(manipulator.original_object, 'get_%s_list' % + related.get_method_name_part()) + orig_list = func() + form.order_objects.extend(orig_list) + + c = Context(request, { + 'title': _('Change %s') % opts.verbose_name, + 'form': form, + 'object_id': object_id, + 'original': manipulator.original_object, + 'is_popup': request.REQUEST.has_key('_popup'), + 'path': path , + }) + return render_change_form(model, manipulator, app_label, c, change=True) diff --git a/django/contrib/admin/views/stages/delete.py b/django/contrib/admin/views/stages/delete.py new file mode 100644 index 0000000000..9ec51ffe9a --- /dev/null +++ b/django/contrib/admin/views/stages/delete.py @@ -0,0 +1,114 @@ + +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_method_name_part() + if isinstance(related.field.rel, models.OneToOne): + try: + sub_obj = getattr(obj, 'get_%s' % 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, ['%s: %s' % (capfirst(related.opts.verbose_name), sub_obj), []]) + else: + # Display a link to the admin page. + nh(deleted_objects, current_depth, ['%s: %s' % \ + (capfirst(related.opts.verbose_name), related.opts.app_label, related.opts.module_name, + getattr(sub_obj, related.opts.pk.attname), 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, 'get_%s_list' % rel_opts_name)(): + 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, ['%s: %s' % (capfirst(related.opts.verbose_name), strip_tags(str(sub_obj))), []]) + else: + # Display a link to the admin page. + nh(deleted_objects, current_depth, ['%s: %s' % \ + (capfirst(related.opts.verbose_name), related.opts.app_label, related.opts.module_name, sub_obj.id, strip_tags(str(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(rel_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_method_name_part() + has_related_objs = False + for sub_obj in getattr(obj, 'get_%s_list' % rel_opts_name)(): + 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, [_('One or more %(fieldname)s in %(name)s: %(obj)s') % \ + {'fieldname': related.field.name, 'name': related.opts.verbose_name, 'obj': strip_tags(str(sub_obj))}, []]) + else: + # Display a link to the admin page. + nh(deleted_objects, current_depth, [ + (_('One or more %(fieldname)s in %(name)s:') % {'fieldname': related.field.name, 'name':related.opts.verbose_name}) + \ + (' %s' % \ + (related.opts.app_label, related.opts.module_name, sub_obj.id, strip_tags(str(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 = '%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, module_name, object_id): + import sets + mod, opts = _get_mod_opts(app_label, module_name) + if not request.user.has_perm(app_label + '.' + opts.get_delete_permission()): + raise PermissionDenied + obj = get_object_or_404(mod, pk=object_id) + + # Populate deleted_objects, a data structure of all related objects that + # will also be deleted. + deleted_objects = ['%s: %s' % (capfirst(opts.verbose_name), object_id, strip_tags(str(obj))), []] + perms_needed = sets.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 = str(obj) + obj.delete() + LogEntry.objects.log_action(request.user.id, opts.get_content_type_id(), object_id, obj_display, DELETION) + request.user.add_message(_('The %(name)s "%(obj)s" was deleted successfully.') % {'name':opts.verbose_name, 'obj':obj_display}) + return HttpResponseRedirect("../../") + return render_to_response('admin/delete_confirmation', { + "title": _("Are you sure?"), + "object_name": opts.verbose_name, + "object": obj, + "deleted_objects": deleted_objects, + "perms_lacking": perms_needed, + }, context_instance=Context(request)) +delete_stage = staff_member_required(delete_stage) diff --git a/django/contrib/admin/views/stages/modify.py b/django/contrib/admin/views/stages/modify.py new file mode 100644 index 0000000000..dd65588758 --- /dev/null +++ b/django/contrib/admin/views/stages/modify.py @@ -0,0 +1,160 @@ +from django.db.models.fields import BoundField, BoundFieldLine, BoundFieldSet +from django.db import models + +from django.core.extensions import render_to_response +from django.contrib.admin.views.main import url_for_model + +use_raw_id_admin = lambda field: isinstance(field.rel, (models.ManyToOne, models.ManyToMany)) and field.rel.raw_id_admin + +def get_javascript_imports(opts, auto_populated_fields, ordered_objects, 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 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(BoundField): + def __init__(self, field, field_mapping, original): + super(AdminBoundField, self).__init__(field, field_mapping, original) + + 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.ManyToOne) or isinstance(field.rel, models.ManyToMany) 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 = ' class="%s" ' % ' '.join(classes) + self._repr_filled = False + + if field.rel: + self.related_url = url_for_model(field.rel.to) + + def _fetch_existing_display(self, func_name): + class_dict = self.original.__class__.__dict__ + func = class_dict.get(func_name) + return func(self.original) + + def _fill_existing_display(self): + if getattr(self, '_display_filled', False): + return + # HACK + if isinstance(self.field.rel, models.ManyToOne): + func_name = 'get_%s' % self.field.name + self._display = self._fetch_existing_display(func_name) + elif isinstance(self.field.rel, models.ManyToMany): + func_name = 'get_%s_list' % self.field.rel.singular + self._display = ", ".join([str(obj) for obj in self._fetch_existing_display(func_name)]) + self._display_filled = True + + def existing_display(self): + self._fill_existing_display() + return self._display + + def __repr__(self): + return repr(self.__dict__) + + def html_error_list(self): + return " ".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(BoundFieldLine): + def __init__(self, field_line, field_mapping, original): + super(AdminBoundFieldLine, self).__init__(field_line, field_mapping, original, AdminBoundField) + for bound_field in self: + bound_field.first = True + break + +class AdminBoundFieldSet(BoundFieldSet): + def __init__(self, field_set, field_mapping, original): + super(AdminBoundFieldSet, self).__init__(field_set, field_mapping, original, AdminBoundFieldLine) + +class BoundManipulator(object): + def __init__(self, model, manipulator, field_mapping): + self.model = model + self.opts = model._meta + self.inline_related_objects = self.opts.get_followed_related_objects(manipulator.follow) + self.original = hasattr(manipulator, 'original_object') and manipulator.original_object or None + self.bound_field_sets = [field_set.bind(field_mapping, self.original, AdminBoundFieldSet) + for field_set in self.opts.admin.get_field_sets(self.opts)] + self.ordered_objects = self.opts.get_ordered_objects()[:] + +class AdminBoundManipulator(BoundManipulator): + def __init__(self, model, manipulator, field_mapping): + super(AdminBoundManipulator, self).__init__(model, manipulator, field_mapping) + field_sets = self.opts.admin.get_field_sets(self.opts) + + self.auto_populated_fields = [f for f in self.opts.fields if f.prepopulate_from] + self.javascript_imports = get_javascript_imports(self.opts, self.auto_populated_fields, self.ordered_objects, field_sets); + + self.coltype = self.ordered_objects and 'colMS' or 'colM' + self.has_absolute_url = hasattr(model, 'get_absolute_url') + self.form_enc_attrib = self.opts.has_field_type(models.FileField) and \ + 'enctype="multipart/form-data" ' or '' + + self.first_form_field_id = self.bound_field_sets[0].bound_field_lines[0].bound_fields[0].form_fields[0].get_id(); + self.ordered_object_pk_names = [o.pk.name for o in self.ordered_objects] + + opts = self.opts + self.save_on_top = opts.admin.save_on_top + self.save_as = opts.admin.save_as + + self.content_type_id = opts.get_content_type_id() + self.verbose_name_plural = opts.verbose_name_plural + self.verbose_name = opts.verbose_name + self.object_name = opts.object_name + + def get_ordered_object_pk(self, ordered_obj): + for name in self.ordered_object_pk_names: + if hasattr(ordered_obj, name): + return str(getattr(ordered_obj, name)) + return "" + +def render_change_form(model, manipulator, app_label, context, add=False, change=False, show_delete=False, form_url=''): + opts = model._meta + extra_context = { + 'add': add, + 'change': change, + 'bound_manipulator': AdminBoundManipulator(model, manipulator, context['form']), + 'has_delete_permission': context['perms'][app_label][opts.get_delete_permission()], + 'form_url': form_url, + 'app_label': app_label, + } + context.update(extra_context) + return render_to_response(["admin/%s/%s/change_form" % (app_label, opts.object_name.lower() ), + "admin/%s/change_form" % app_label , + "admin/change_form"], context_instance=context) diff --git a/django/db/models/manager.py b/django/db/models/manager.py index 8aaef95a5a..78c4f8a30f 100644 --- a/django/db/models/manager.py +++ b/django/db/models/manager.py @@ -17,6 +17,7 @@ def ensure_default_manager(sender): if hasattr(cls, 'objects'): raise ValueError, "Model %s must specify a custom Manager, because it has a field named 'objects'" % name cls.add_to_class('objects', Manager()) + cls.objects._prepare() dispatcher.connect( ensure_default_manager, @@ -33,8 +34,7 @@ class Manager(object): Manager.creation_counter += 1 self.klass = None - def _prepare(self, klass): - self.klass = klass + def _prepare(self): if self.klass._meta.get_latest_by: self.get_latest = self.__get_latest for f in self.klass._meta.fields: @@ -43,7 +43,8 @@ class Manager(object): def contribute_to_class(self, klass, name): # TODO: Use weakref because of possible memory leak / circular reference. - self._prepare(klass) + self.klass = klass + dispatcher.connect(self._prepare, signal=signals.class_prepared, sender=klass) setattr(klass,name, ManagerDescriptor(self)) if not hasattr(klass, '_default_manager') or \ self.creation_counter < klass._default_manager.creation_counter: diff --git a/django/db/models/options.py b/django/db/models/options.py index 0cec6c54f3..f7367b1141 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -36,6 +36,7 @@ class Options: self.module_constants = {} self.admin = None self.meta = meta + def merge_meta(self): meta_attrs = self.meta.__dict__ del meta_attrs['__module__']