diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py index 40437c0..d8726ea 100644 --- a/django/contrib/admin/helpers.py +++ b/django/contrib/admin/helpers.py @@ -1,13 +1,15 @@ - from django import forms from django.conf import settings -from django.utils.html import escape -from django.utils.safestring import mark_safe -from django.utils.encoding import force_unicode from django.contrib.admin.util import flatten_fieldsets from django.contrib.contenttypes.models import ContentType +from django.forms.util import flatatt +from django.utils.encoding import force_unicode +from django.utils.html import escape +from django.utils.safestring import mark_safe +from django.utils.text import capfirst from django.utils.translation import ugettext_lazy as _ + ACTION_CHECKBOX_NAME = '_selected_action' class ActionForm(forms.Form): @@ -16,16 +18,18 @@ class ActionForm(forms.Form): checkbox = forms.CheckboxInput({'class': 'action-select'}, lambda value: False) class AdminForm(object): - def __init__(self, form, fieldsets, prepopulated_fields): + def __init__(self, form, fieldsets, prepopulated_fields, readonly_fields): self.form, self.fieldsets = form, normalize_fieldsets(fieldsets) self.prepopulated_fields = [{ 'field': form[field_name], 'dependencies': [form[f] for f in dependencies] } for field_name, dependencies in prepopulated_fields.items()] + self.readonly_fields = readonly_fields def __iter__(self): for name, options in self.fieldsets: - yield Fieldset(self.form, name, **options) + yield Fieldset(self.form, name, + readonly_fields=self.readonly_fields, **options) def first_field(self): try: @@ -49,11 +53,13 @@ class AdminForm(object): media = property(_media) class Fieldset(object): - def __init__(self, form, name=None, fields=(), classes=(), description=None): + def __init__(self, form, name=None, readonly_fields=(), fields=(), classes=(), + description=None): self.form = form self.name, self.fields = name, fields self.classes = u' '.join(classes) self.description = description + self.readonly_fields = readonly_fields def _media(self): if 'collapse' in self.classes: @@ -63,22 +69,26 @@ class Fieldset(object): def __iter__(self): for field in self.fields: - yield Fieldline(self.form, field) + yield Fieldline(self.form, field, self.readonly_fields) class Fieldline(object): - def __init__(self, form, field): + def __init__(self, form, field, readonly_fields): self.form = form # A django.forms.Form instance if isinstance(field, basestring): self.fields = [field] else: self.fields = field + self.readonly_fields = readonly_fields def __iter__(self): for i, field in enumerate(self.fields): - yield AdminField(self.form, field, is_first=(i == 0)) + if field in self.readonly_fields: + yield AdminReadonlyField(self.form, field, is_first=(i == 0)) + else: + yield AdminField(self.form, field, is_first=(i == 0)) def errors(self): - return mark_safe(u'\n'.join([self.form[f].errors.as_ul() for f in self.fields]).strip('\n')) + return mark_safe(u'\n'.join([self.form[f].errors.as_ul() for f in self.fields if f not in self.readonly_fields]).strip('\n')) class AdminField(object): def __init__(self, form, field, is_first): @@ -100,20 +110,44 @@ class AdminField(object): attrs = classes and {'class': u' '.join(classes)} or {} return self.field.label_tag(contents=contents, attrs=attrs) +class AdminReadonlyField(object): + def __init__(self, form, field, is_first): + self.field = field + self.form = form + self.is_first = is_first + self.is_checkbox = False + self.is_readonly = True + + def label_tag(self): + attrs = {} + if not self.is_first: + attrs["class"] = "inline" + contents = force_unicode(escape(capfirst(self.field))) + u":" + return mark_safe('' % { + "attrs": flatatt(attrs), + "contents": contents, + }) + + def contents(self): + return getattr(self.form.instance, self.field) + class InlineAdminFormSet(object): """ A wrapper around an inline formset for use in the admin system. """ - def __init__(self, inline, formset, fieldsets): + def __init__(self, inline, formset, fieldsets, readonly_fields): self.opts = inline self.formset = formset self.fieldsets = fieldsets + self.readonly_fields = readonly_fields def __iter__(self): for form, original in zip(self.formset.initial_forms, self.formset.get_queryset()): - yield InlineAdminForm(self.formset, form, self.fieldsets, self.opts.prepopulated_fields, original) + yield InlineAdminForm(self.formset, form, self.fieldsets, + self.opts.prepopulated_fields, original, self.readonly_fields) for form in self.formset.extra_forms: - yield InlineAdminForm(self.formset, form, self.fieldsets, self.opts.prepopulated_fields, None) + yield InlineAdminForm(self.formset, form, self.fieldsets, + self.opts.prepopulated_fields, None, self.readonly_fields) def fields(self): fk = getattr(self.formset, "fk", None) @@ -133,17 +167,19 @@ class InlineAdminForm(AdminForm): """ A wrapper around an inline form for use in the admin system. """ - def __init__(self, formset, form, fieldsets, prepopulated_fields, original): + def __init__(self, formset, form, fieldsets, prepopulated_fields, original, + readonly_fields): self.formset = formset self.original = original if original is not None: self.original_content_type_id = ContentType.objects.get_for_model(original).pk self.show_url = original and hasattr(original, 'get_absolute_url') - super(InlineAdminForm, self).__init__(form, fieldsets, prepopulated_fields) + super(InlineAdminForm, self).__init__(form, fieldsets, prepopulated_fields, + readonly_fields) def __iter__(self): for name, options in self.fieldsets: - yield InlineFieldset(self.formset, self.form, name, **options) + yield InlineFieldset(self.formset, self.form, name, self.readonly_fields, **options) def has_auto_field(self): if self.form._meta.model._meta.has_auto_field: @@ -194,7 +230,7 @@ class InlineFieldset(Fieldset): for field in self.fields: if fk and fk.name == field: continue - yield Fieldline(self.form, field) + yield Fieldline(self.form, field, self.readonly_fields) class AdminErrorList(forms.util.ErrorList): """ diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 3144a22..57000b8 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -64,6 +64,7 @@ class BaseModelAdmin(object): radio_fields = {} prepopulated_fields = {} formfield_overrides = {} + readonly_fields = () def __init__(self): self.formfield_overrides = dict(FORMFIELD_FOR_DBFIELD_DEFAULTS, **self.formfield_overrides) @@ -171,6 +172,9 @@ class BaseModelAdmin(object): return [(None, {'fields': self.fields})] return None declared_fieldsets = property(_declared_fieldsets) + + def get_readonly_fields(self, request, obj=None): + return self.readonly_fields class ModelAdmin(BaseModelAdmin): "Encapsulates all admin options and functionality for a given model." @@ -321,7 +325,8 @@ class ModelAdmin(BaseModelAdmin): if self.declared_fieldsets: return self.declared_fieldsets form = self.get_form(request, obj) - return [(None, {'fields': form.base_fields.keys()})] + fields = form.base_fields.keys() + list(self.get_readonly_fields(request, obj)) + return [(None, {'fields': fields})] def get_form(self, request, obj=None, **kwargs): """ @@ -336,12 +341,15 @@ class ModelAdmin(BaseModelAdmin): exclude = [] else: exclude = list(self.exclude) + exclude.extend(kwargs.get("exclude", [])) + exclude.extend(self.get_readonly_fields(request, obj)) # if exclude is an empty list we pass None to be consistant with the # default on modelform_factory + exclude = exclude or None defaults = { "form": self.form, "fields": fields, - "exclude": (exclude + kwargs.get("exclude", [])) or None, + "exclude": exclude, "formfield_callback": curry(self.formfield_for_dbfield, request=request), } defaults.update(kwargs) @@ -759,13 +767,17 @@ class ModelAdmin(BaseModelAdmin): formset = FormSet(instance=self.model(), prefix=prefix) formsets.append(formset) - adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)), self.prepopulated_fields) + adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)), + self.prepopulated_fields, self.get_readonly_fields(request) + ) media = self.media + adminForm.media inline_admin_formsets = [] for inline, formset in zip(self.inline_instances, formsets): fieldsets = list(inline.get_fieldsets(request)) - inline_admin_formset = helpers.InlineAdminFormSet(inline, formset, fieldsets) + readonly = list(inline.get_readonly_fields(request)) + inline_admin_formset = helpers.InlineAdminFormSet(inline, formset, + fieldsets, readonly) inline_admin_formsets.append(inline_admin_formset) media = media + inline_admin_formset.media @@ -847,13 +859,16 @@ class ModelAdmin(BaseModelAdmin): formset = FormSet(instance=obj, prefix=prefix) formsets.append(formset) - adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj), self.prepopulated_fields) + adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj), + self.prepopulated_fields, self.get_readonly_fields(request, obj)) media = self.media + adminForm.media inline_admin_formsets = [] for inline, formset in zip(self.inline_instances, formsets): fieldsets = list(inline.get_fieldsets(request, obj)) - inline_admin_formset = helpers.InlineAdminFormSet(inline, formset, fieldsets) + readonly = list(inline.get_readonly_fields(request, obj)) + inline_admin_formset = helpers.InlineAdminFormSet(inline, formset, + fieldsets, readonly) inline_admin_formsets.append(inline_admin_formset) media = media + inline_admin_formset.media @@ -1144,14 +1159,17 @@ class InlineModelAdmin(BaseModelAdmin): exclude = [] else: exclude = list(self.exclude) + exclude.extend(kwargs.get("exclude", [])) + exclude.extend(self.get_readonly_fields(request, obj)) # if exclude is an empty list we use None, since that's the actual # default + exclude = exclude or None defaults = { "form": self.form, "formset": self.formset, "fk_name": self.fk_name, "fields": fields, - "exclude": (exclude + kwargs.get("exclude", [])) or None, + "exclude": exclude, "formfield_callback": curry(self.formfield_for_dbfield, request=request), "extra": self.extra, "max_num": self.max_num, @@ -1163,7 +1181,8 @@ class InlineModelAdmin(BaseModelAdmin): if self.declared_fieldsets: return self.declared_fieldsets form = self.get_formset(request).form - return [(None, {'fields': form.base_fields.keys()})] + fields = form.base_fields.keys() + list(self.get_readonly_fields(request, obj)) + return [(None, {'fields': fields})] class StackedInline(InlineModelAdmin): template = 'admin/edit_inline/stacked.html' diff --git a/django/contrib/admin/templates/admin/includes/fieldset.html b/django/contrib/admin/templates/admin/includes/fieldset.html index 8ee24b1..bcde368 100644 --- a/django/contrib/admin/templates/admin/includes/fieldset.html +++ b/django/contrib/admin/templates/admin/includes/fieldset.html @@ -1,19 +1,28 @@