diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index dd321c389b..aafea0d09f 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -30,6 +30,43 @@ def unquote(s): myappend('_' + item) return "".join(res) +class AdminFieldSet(object): + def __init__(self, name, classes, field_locator_func, line_specs, description): + self.name = name + self.field_lines = [AdminFieldLine(field_locator_func, line_spec) for line_spec in line_specs] + self.classes = classes + self.description = description + + def __repr__(self): + return "FieldSet: (%s, %s)" % (self.name, self.field_lines) + + def bind(self, field_mapping, original, bound_field_set_class): + return bound_field_set_class(self, field_mapping, original) + + def __iter__(self): + for field_line in self.field_lines: + yield field_line + + def __len__(self): + return len(self.field_lines) + +class AdminFieldLine(object): + def __init__(self, field_locator_func, linespec): + if isinstance(linespec, basestring): + self.fields = [field_locator_func(linespec)] + else: + self.fields = [field_locator_func(field_name) for field_name in linespec] + + def bind(self, field_mapping, original, bound_field_line_class): + return bound_field_line_class(self, field_mapping, original) + + def __iter__(self): + for field in self.fields: + yield field + + def __len__(self): + return len(self.fields) + class ModelAdmin(object): "Encapsulates all admin options and functionality for a given model." @@ -44,6 +81,7 @@ class ModelAdmin(object): save_on_top = False ordering = None js = None + fields = None def __init__(self, model): self.model = model @@ -74,6 +112,21 @@ class ModelAdmin(object): else: return self.change_view(request, unquote(url)) + def get_field_sets(self, opts): + "Returns a list of AdminFieldSet objects." + if self.fields is None: + field_struct = ((None, {'fields': [f.name for f in opts.fields + opts.many_to_many if f.editable and not isinstance(f, models.AutoField)]}),) + else: + field_struct = self.fields + new_fieldset_list = [] + for fieldset in field_struct: + fs_options = fieldset[1] + classes = fs_options.get('classes', ()) + description = fs_options.get('description', '') + new_fieldset_list.append(AdminFieldSet(fieldset[0], classes, + opts.get_field, fs_options['fields'], description)) + return new_fieldset_list + def has_add_permission(self, request): "Returns True if the given request has permission to add an object." opts = self.opts @@ -173,7 +226,7 @@ class ModelAdmin(object): if object_id_override is not None: c['object_id'] = object_id_override - return render_change_form(model, manipulator, c, add=True) + return render_change_form(self, model, manipulator, c, add=True) def change_view(self, request, object_id): "The 'change' admin view for this model." @@ -272,7 +325,7 @@ class ModelAdmin(object): 'original': manipulator.original_object, 'is_popup': request.REQUEST.has_key('_popup'), }) - return render_change_form(model, manipulator, c, change=True) + return render_change_form(self, model, manipulator, c, change=True) def change_list_view(self, request): "The 'change list' admin view for this model." diff --git a/django/contrib/admin/templatetags/admin_modify.py b/django/contrib/admin/templatetags/admin_modify.py index 5c9009ea4e..fcb5876b0c 100644 --- a/django/contrib/admin/templatetags/admin_modify.py +++ b/django/contrib/admin/templatetags/admin_modify.py @@ -41,8 +41,8 @@ def submit_row(context): and 'onclick="submitOrderForm();"' or ''), 'show_delete_link': (not is_popup and context['has_delete_permission'] and (change or context['show_delete'])), - 'show_save_as_new': not is_popup and change and opts.ModelAdmin.save_as, - 'show_save_and_add_another': not is_popup and (not opts.ModelAdmin.save_as or context['add']), + 'show_save_as_new': not is_popup and change and opts.admin.save_as, + 'show_save_and_add_another': not is_popup and (not opts.admin.save_as or context['add']), 'show_save_and_continue': not is_popup and context['has_change_permission'], 'show_save': True } diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py index d3ca7a7070..908aee74dd 100644 --- a/django/contrib/admin/views/main.py +++ b/django/contrib/admin/views/main.py @@ -57,8 +57,8 @@ def get_javascript_imports(opts, auto_populated_fields, field_sets): js.extend(['js/calendar.js', 'js/admin/DateTimeShortcuts.js']) if opts.get_ordered_objects(): js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js']) - if opts.ModelAdmin.js: - js.extend(opts.ModelAdmin.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: @@ -81,7 +81,7 @@ def model_admin_view(request, app_label, model_name, rest_of_url): raise Http404("App %r, model %r, not found" % (app_label, model_name)) if not model._meta.admin: raise Http404("This object has no admin interface.") - mav = model._meta.ModelAdmin(model) + mav = model._meta.admin(model) return mav(request, rest_of_url) model_admin_view = staff_member_required(never_cache(model_admin_view)) @@ -166,11 +166,11 @@ class AdminBoundFieldSet(object): def __len__(self): return len(self.bound_field_lines) -def render_change_form(model, manipulator, context, add=False, change=False, form_url=''): +def render_change_form(model_admin, model, manipulator, context, add=False, change=False, form_url=''): opts = model._meta app_label = opts.app_label auto_populated_fields = [f for f in opts.fields if f.prepopulate_from] - field_sets = opts.admin.get_field_sets(opts) + field_sets = model_admin.get_field_sets(opts) original = getattr(manipulator, 'original_object', None) bound_field_sets = [field_set.bind(context['form'], original, AdminBoundFieldSet) for field_set in field_sets] first_form_field_id = bound_field_sets[0].bound_field_lines[0].bound_fields[0].form_fields[0].get_id(); @@ -192,7 +192,7 @@ def render_change_form(model, manipulator, context, add=False, change=False, for 'form_url': form_url, 'opts': opts, 'content_type_id': ContentType.objects.get_for_model(model).id, - 'save_on_top': opts.ModelAdmin.save_on_top, + 'save_on_top': model_admin.save_on_top, } context.update(extra_context) return render_to_response([ @@ -305,6 +305,7 @@ class ChangeList(object): self.search_fields = search_fields self.list_select_related = list_select_related self.list_per_page = list_per_page + self.model_admin = model_admin # Get search parameters from the query string. try: @@ -399,7 +400,7 @@ class ChangeList(object): # 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.ModelAdmin.ordering or lookup_opts.ordering or ['-' + lookup_opts.pk.name] + ordering = self.model_admin.ordering or lookup_opts.ordering or ['-' + lookup_opts.pk.name] # Normalize it to new-style ordering. ordering = handle_legacy_orderlist(ordering) diff --git a/django/core/management.py b/django/core/management.py index 812f00021d..cbea3bafbc 100644 --- a/django/core/management.py +++ b/django/core/management.py @@ -979,51 +979,48 @@ def get_validation_errors(outfile, app=None): # Check admin attribute. if opts.admin is not None: - if not isinstance(opts.admin, models.AdminOptions): - e.add(opts, '"admin" attribute, if given, must be set to a models.AdminOptions() instance.') + # list_display + if not isinstance(opts.admin.list_display, (list, tuple)): + e.add(opts, '"admin.list_display", if given, must be set to a list or tuple.') else: - # list_display - if not isinstance(opts.ModelAdmin.list_display, (list, tuple)): - e.add(opts, '"admin.list_display", if given, must be set to a list or tuple.') - else: - for fn in opts.ModelAdmin.list_display: - try: - f = opts.get_field(fn) - except models.FieldDoesNotExist: - if not hasattr(cls, fn): - e.add(opts, '"admin.list_display" refers to %r, which isn\'t an attribute, method or property.' % fn) - else: - if isinstance(f, models.ManyToManyField): - e.add(opts, '"admin.list_display" doesn\'t support ManyToManyFields (%r).' % fn) - # list_display_links - if opts.ModelAdmin.list_display_links and not opts.ModelAdmin.list_display: - e.add(opts, '"admin.list_display" must be defined for "admin.list_display_links" to be used.') - if not isinstance(opts.ModelAdmin.list_display_links, (list, tuple)): - e.add(opts, '"admin.list_display_links", if given, must be set to a list or tuple.') - else: - for fn in opts.ModelAdmin.list_display_links: - try: - f = opts.get_field(fn) - except models.FieldDoesNotExist: - if not hasattr(cls, fn): - e.add(opts, '"admin.list_display_links" refers to %r, which isn\'t an attribute, method or property.' % fn) - if fn not in opts.ModelAdmin.list_display: - e.add(opts, '"admin.list_display_links" refers to %r, which is not defined in "admin.list_display".' % fn) - # list_filter - if not isinstance(opts.ModelAdmin.list_filter, (list, tuple)): - e.add(opts, '"admin.list_filter", if given, must be set to a list or tuple.') - else: - for fn in opts.ModelAdmin.list_filter: - try: - f = opts.get_field(fn) - except models.FieldDoesNotExist: - e.add(opts, '"admin.list_filter" refers to %r, which isn\'t a field.' % fn) - # date_hierarchy - if opts.ModelAdmin.date_hierarchy: + for fn in opts.admin.list_display: try: - f = opts.get_field(opts.ModelAdmin.date_hierarchy) + f = opts.get_field(fn) except models.FieldDoesNotExist: - e.add(opts, '"admin.date_hierarchy" refers to %r, which isn\'t a field.' % opts.ModelAdmin.date_hierarchy) + if not hasattr(cls, fn): + e.add(opts, '"admin.list_display" refers to %r, which isn\'t an attribute, method or property.' % fn) + else: + if isinstance(f, models.ManyToManyField): + e.add(opts, '"admin.list_display" doesn\'t support ManyToManyFields (%r).' % fn) + # list_display_links + if opts.admin.list_display_links and not opts.admin.list_display: + e.add(opts, '"admin.list_display" must be defined for "admin.list_display_links" to be used.') + if not isinstance(opts.admin.list_display_links, (list, tuple)): + e.add(opts, '"admin.list_display_links", if given, must be set to a list or tuple.') + else: + for fn in opts.admin.list_display_links: + try: + f = opts.get_field(fn) + except models.FieldDoesNotExist: + if not hasattr(cls, fn): + e.add(opts, '"admin.list_display_links" refers to %r, which isn\'t an attribute, method or property.' % fn) + if fn not in opts.admin.list_display: + e.add(opts, '"admin.list_display_links" refers to %r, which is not defined in "admin.list_display".' % fn) + # list_filter + if not isinstance(opts.admin.list_filter, (list, tuple)): + e.add(opts, '"admin.list_filter", if given, must be set to a list or tuple.') + else: + for fn in opts.admin.list_filter: + try: + f = opts.get_field(fn) + except models.FieldDoesNotExist: + e.add(opts, '"admin.list_filter" refers to %r, which isn\'t a field.' % fn) + # date_hierarchy + if opts.admin.date_hierarchy: + try: + f = opts.get_field(opts.admin.date_hierarchy) + except models.FieldDoesNotExist: + e.add(opts, '"admin.date_hierarchy" refers to %r, which isn\'t a field.' % opts.admin.date_hierarchy) # Check ordering attribute. if opts.ordering: diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py index 0308dd047a..964f84ba86 100644 --- a/django/db/models/__init__.py +++ b/django/db/models/__init__.py @@ -5,7 +5,7 @@ from django.db import backend, connection from django.db.models.loading import get_apps, get_app, get_models, get_model, register_models from django.db.models.query import Q from django.db.models.manager import Manager -from django.db.models.base import Model, AdminOptions +from django.db.models.base import Model from django.db.models.fields import * from django.db.models.fields.related import ForeignKey, OneToOneField, ManyToManyField, ManyToOneRel, ManyToManyRel, OneToOneRel, TABULAR, STACKED from django.db.models.fields.generic import GenericRelation, GenericRel, GenericForeignKey diff --git a/django/db/models/base.py b/django/db/models/base.py index 98608d8915..58a6fcb75c 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -5,7 +5,7 @@ from django.core.exceptions import ObjectDoesNotExist from django.db.models.fields import AutoField, ImageField, FieldDoesNotExist from django.db.models.fields.related import OneToOneRel, ManyToOneRel from django.db.models.query import delete_objects -from django.db.models.options import Options, AdminOptions +from django.db.models.options import Options from django.db import connection, backend, transaction from django.db.models import signals from django.db.models.loading import register_models, get_model @@ -135,10 +135,7 @@ class Model(object): # of both ModelAdmin and the 'class Admin' on this model. The # resulting class is same as if the 'class Admin' were a subclass # of ModelAdmin. - cls._meta.ModelAdmin = type('ModelAdmin', (value, ModelAdmin), {}) - # This AdminOptions stuff is legacy and will eventually be removed. - value = AdminOptions(**dict([(k, v) for k, v in value.__dict__.items() if not k.startswith('_') and k not in ('list_display', 'list_display_links', 'list_filter', 'date_hierarchy', 'save_as', 'search_fields', 'list_select_related', 'list_per_page', 'ordering', 'save_on_top', 'js', 'manager')])) - value.contribute_to_class(cls, name) + cls._meta.admin = type('ModelAdmin', (value, ModelAdmin), {}) elif hasattr(value, 'contribute_to_class'): value.contribute_to_class(cls, name) else: diff --git a/django/db/models/options.py b/django/db/models/options.py index 015e612dec..eafb6f0e03 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -196,62 +196,3 @@ class Options(object): else: self._field_types[field_type] = False return self._field_types[field_type] - -class AdminOptions(object): - def __init__(self, fields=None): - self.fields = fields - - def get_field_sets(self, opts): - "Returns a list of AdminFieldSet objects for this AdminOptions object." - if self.fields is None: - field_struct = ((None, {'fields': [f.name for f in opts.fields + opts.many_to_many if f.editable and not isinstance(f, AutoField)]}),) - else: - field_struct = self.fields - new_fieldset_list = [] - for fieldset in field_struct: - fs_options = fieldset[1] - classes = fs_options.get('classes', ()) - description = fs_options.get('description', '') - new_fieldset_list.append(AdminFieldSet(fieldset[0], classes, - opts.get_field, fs_options['fields'], description)) - return new_fieldset_list - - def contribute_to_class(self, cls, name): - cls._meta.admin = self - -class AdminFieldSet(object): - def __init__(self, name, classes, field_locator_func, line_specs, description): - self.name = name - self.field_lines = [AdminFieldLine(field_locator_func, line_spec) for line_spec in line_specs] - self.classes = classes - self.description = description - - def __repr__(self): - return "FieldSet: (%s, %s)" % (self.name, self.field_lines) - - def bind(self, field_mapping, original, bound_field_set_class): - return bound_field_set_class(self, field_mapping, original) - - def __iter__(self): - for field_line in self.field_lines: - yield field_line - - def __len__(self): - return len(self.field_lines) - -class AdminFieldLine(object): - def __init__(self, field_locator_func, linespec): - if isinstance(linespec, basestring): - self.fields = [field_locator_func(linespec)] - else: - self.fields = [field_locator_func(field_name) for field_name in linespec] - - def bind(self, field_mapping, original, bound_field_line_class): - return bound_field_line_class(self, field_mapping, original) - - def __iter__(self): - for field in self.fields: - yield field - - def __len__(self): - return len(self.fields) diff --git a/tests/regressiontests/invalid_admin_options/models.py b/tests/regressiontests/invalid_admin_options/models.py index 43bcc533ba..94e6ccdde1 100644 --- a/tests/regressiontests/invalid_admin_options/models.py +++ b/tests/regressiontests/invalid_admin_options/models.py @@ -8,18 +8,6 @@ model validation is working properly. from django.db import models model_errors = "" -# TODO: Invalid admin options should not cause a metaclass error -##This should fail gracefully but is causing a metaclass error -#class BadAdminOption(models.Model): -# "Test nonexistent admin option" -# name = models.CharField(maxlength=30) -# -# class Admin: -# nonexistent = 'option' -# -#model_errors += """invalid_admin_options.badadminoption: "admin" attribute, if given, must be set to a models.AdminOptions() instance. -#""" - class ListDisplayBadOne(models.Model): "Test list_display, list_display must be a list or tuple" first_name = models.CharField(maxlength=30) @@ -334,4 +322,4 @@ class OrderingBad(models.Model): # manager = 'nonexistent' # #model_errors += """invalid_admin_options.managerbad: "admin.manager" refers to 'nonexistent', which isn't a Manager(). -#""" \ No newline at end of file +#"""