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__']