Ticket #342: readonly-admin.2.diff

File readonly-admin.2.diff, 16.2 KB (added by Alex Gaynor, 14 years ago)

Added documentation.

  • django/contrib/admin/helpers.py

    diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py
    index 40437c0..d8726ea 100644
    a b  
    1 
    21from django import forms
    32from django.conf import settings
    4 from django.utils.html import escape
    5 from django.utils.safestring import mark_safe
    6 from django.utils.encoding import force_unicode
    73from django.contrib.admin.util import flatten_fieldsets
    84from django.contrib.contenttypes.models import ContentType
     5from django.forms.util import flatatt
     6from django.utils.encoding import force_unicode
     7from django.utils.html import escape
     8from django.utils.safestring import mark_safe
     9from django.utils.text import capfirst
    910from django.utils.translation import ugettext_lazy as _
    1011
     12
    1113ACTION_CHECKBOX_NAME = '_selected_action'
    1214
    1315class ActionForm(forms.Form):
    class ActionForm(forms.Form):  
    1618checkbox = forms.CheckboxInput({'class': 'action-select'}, lambda value: False)
    1719
    1820class AdminForm(object):
    19     def __init__(self, form, fieldsets, prepopulated_fields):
     21    def __init__(self, form, fieldsets, prepopulated_fields, readonly_fields):
    2022        self.form, self.fieldsets = form, normalize_fieldsets(fieldsets)
    2123        self.prepopulated_fields = [{
    2224            'field': form[field_name],
    2325            'dependencies': [form[f] for f in dependencies]
    2426        } for field_name, dependencies in prepopulated_fields.items()]
     27        self.readonly_fields = readonly_fields
    2528
    2629    def __iter__(self):
    2730        for name, options in self.fieldsets:
    28             yield Fieldset(self.form, name, **options)
     31            yield Fieldset(self.form, name,
     32                readonly_fields=self.readonly_fields, **options)
    2933
    3034    def first_field(self):
    3135        try:
    class AdminForm(object):  
    4953    media = property(_media)
    5054
    5155class Fieldset(object):
    52     def __init__(self, form, name=None, fields=(), classes=(), description=None):
     56    def __init__(self, form, name=None, readonly_fields=(), fields=(), classes=(),
     57        description=None):
    5358        self.form = form
    5459        self.name, self.fields = name, fields
    5560        self.classes = u' '.join(classes)
    5661        self.description = description
     62        self.readonly_fields = readonly_fields
    5763
    5864    def _media(self):
    5965        if 'collapse' in self.classes:
    class Fieldset(object):  
    6369
    6470    def __iter__(self):
    6571        for field in self.fields:
    66             yield Fieldline(self.form, field)
     72            yield Fieldline(self.form, field, self.readonly_fields)
    6773
    6874class Fieldline(object):
    69     def __init__(self, form, field):
     75    def __init__(self, form, field, readonly_fields):
    7076        self.form = form # A django.forms.Form instance
    7177        if isinstance(field, basestring):
    7278            self.fields = [field]
    7379        else:
    7480            self.fields = field
     81        self.readonly_fields = readonly_fields
    7582
    7683    def __iter__(self):
    7784        for i, field in enumerate(self.fields):
    78             yield AdminField(self.form, field, is_first=(i == 0))
     85            if field in self.readonly_fields:
     86                yield AdminReadonlyField(self.form, field, is_first=(i == 0))
     87            else:
     88                yield AdminField(self.form, field, is_first=(i == 0))
    7989
    8090    def errors(self):
    81         return mark_safe(u'\n'.join([self.form[f].errors.as_ul() for f in self.fields]).strip('\n'))
     91        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'))
    8292
    8393class AdminField(object):
    8494    def __init__(self, form, field, is_first):
    class AdminField(object):  
    100110        attrs = classes and {'class': u' '.join(classes)} or {}
    101111        return self.field.label_tag(contents=contents, attrs=attrs)
    102112
     113class AdminReadonlyField(object):
     114    def __init__(self, form, field, is_first):
     115        self.field = field
     116        self.form = form
     117        self.is_first = is_first
     118        self.is_checkbox = False
     119        self.is_readonly = True
     120   
     121    def label_tag(self):
     122        attrs = {}
     123        if not self.is_first:
     124            attrs["class"] = "inline"
     125        contents = force_unicode(escape(capfirst(self.field))) + u":"
     126        return mark_safe('<label %(attrs)s>%(contents)s</label>' % {
     127            "attrs": flatatt(attrs),
     128            "contents": contents,
     129        })
     130   
     131    def contents(self):
     132        return getattr(self.form.instance, self.field)
     133
    103134class InlineAdminFormSet(object):
    104135    """
    105136    A wrapper around an inline formset for use in the admin system.
    106137    """
    107     def __init__(self, inline, formset, fieldsets):
     138    def __init__(self, inline, formset, fieldsets, readonly_fields):
    108139        self.opts = inline
    109140        self.formset = formset
    110141        self.fieldsets = fieldsets
     142        self.readonly_fields = readonly_fields
    111143
    112144    def __iter__(self):
    113145        for form, original in zip(self.formset.initial_forms, self.formset.get_queryset()):
    114             yield InlineAdminForm(self.formset, form, self.fieldsets, self.opts.prepopulated_fields, original)
     146            yield InlineAdminForm(self.formset, form, self.fieldsets,
     147                self.opts.prepopulated_fields, original, self.readonly_fields)
    115148        for form in self.formset.extra_forms:
    116             yield InlineAdminForm(self.formset, form, self.fieldsets, self.opts.prepopulated_fields, None)
     149            yield InlineAdminForm(self.formset, form, self.fieldsets,
     150                self.opts.prepopulated_fields, None, self.readonly_fields)
    117151
    118152    def fields(self):
    119153        fk = getattr(self.formset, "fk", None)
    class InlineAdminForm(AdminForm):  
    133167    """
    134168    A wrapper around an inline form for use in the admin system.
    135169    """
    136     def __init__(self, formset, form, fieldsets, prepopulated_fields, original):
     170    def __init__(self, formset, form, fieldsets, prepopulated_fields, original,
     171        readonly_fields):
    137172        self.formset = formset
    138173        self.original = original
    139174        if original is not None:
    140175            self.original_content_type_id = ContentType.objects.get_for_model(original).pk
    141176        self.show_url = original and hasattr(original, 'get_absolute_url')
    142         super(InlineAdminForm, self).__init__(form, fieldsets, prepopulated_fields)
     177        super(InlineAdminForm, self).__init__(form, fieldsets, prepopulated_fields,
     178            readonly_fields)
    143179
    144180    def __iter__(self):
    145181        for name, options in self.fieldsets:
    146             yield InlineFieldset(self.formset, self.form, name, **options)
     182            yield InlineFieldset(self.formset, self.form, name, self.readonly_fields, **options)
    147183
    148184    def has_auto_field(self):
    149185        if self.form._meta.model._meta.has_auto_field:
    class InlineFieldset(Fieldset):  
    194230        for field in self.fields:
    195231            if fk and fk.name == field:
    196232                continue
    197             yield Fieldline(self.form, field)
     233            yield Fieldline(self.form, field, self.readonly_fields)
    198234
    199235class AdminErrorList(forms.util.ErrorList):
    200236    """
  • django/contrib/admin/options.py

    diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
    index 3144a22..57000b8 100644
    a b class BaseModelAdmin(object):  
    6464    radio_fields = {}
    6565    prepopulated_fields = {}
    6666    formfield_overrides = {}
     67    readonly_fields = ()
    6768
    6869    def __init__(self):
    6970        self.formfield_overrides = dict(FORMFIELD_FOR_DBFIELD_DEFAULTS, **self.formfield_overrides)
    class BaseModelAdmin(object):  
    171172            return [(None, {'fields': self.fields})]
    172173        return None
    173174    declared_fieldsets = property(_declared_fieldsets)
     175   
     176    def get_readonly_fields(self, request, obj=None):
     177        return self.readonly_fields
    174178
    175179class ModelAdmin(BaseModelAdmin):
    176180    "Encapsulates all admin options and functionality for a given model."
    class ModelAdmin(BaseModelAdmin):  
    321325        if self.declared_fieldsets:
    322326            return self.declared_fieldsets
    323327        form = self.get_form(request, obj)
    324         return [(None, {'fields': form.base_fields.keys()})]
     328        fields = form.base_fields.keys() + list(self.get_readonly_fields(request, obj))
     329        return [(None, {'fields': fields})]
    325330
    326331    def get_form(self, request, obj=None, **kwargs):
    327332        """
    class ModelAdmin(BaseModelAdmin):  
    336341            exclude = []
    337342        else:
    338343            exclude = list(self.exclude)
     344        exclude.extend(kwargs.get("exclude", []))
     345        exclude.extend(self.get_readonly_fields(request, obj))
    339346        # if exclude is an empty list we pass None to be consistant with the
    340347        # default on modelform_factory
     348        exclude = exclude or None
    341349        defaults = {
    342350            "form": self.form,
    343351            "fields": fields,
    344             "exclude": (exclude + kwargs.get("exclude", [])) or None,
     352            "exclude": exclude,
    345353            "formfield_callback": curry(self.formfield_for_dbfield, request=request),
    346354        }
    347355        defaults.update(kwargs)
    class ModelAdmin(BaseModelAdmin):  
    759767                formset = FormSet(instance=self.model(), prefix=prefix)
    760768                formsets.append(formset)
    761769
    762         adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)), self.prepopulated_fields)
     770        adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)),
     771            self.prepopulated_fields, self.get_readonly_fields(request)
     772        )
    763773        media = self.media + adminForm.media
    764774
    765775        inline_admin_formsets = []
    766776        for inline, formset in zip(self.inline_instances, formsets):
    767777            fieldsets = list(inline.get_fieldsets(request))
    768             inline_admin_formset = helpers.InlineAdminFormSet(inline, formset, fieldsets)
     778            readonly = list(inline.get_readonly_fields(request))
     779            inline_admin_formset = helpers.InlineAdminFormSet(inline, formset,
     780                fieldsets, readonly)
    769781            inline_admin_formsets.append(inline_admin_formset)
    770782            media = media + inline_admin_formset.media
    771783
    class ModelAdmin(BaseModelAdmin):  
    847859                formset = FormSet(instance=obj, prefix=prefix)
    848860                formsets.append(formset)
    849861
    850         adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj), self.prepopulated_fields)
     862        adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj),
     863            self.prepopulated_fields, self.get_readonly_fields(request, obj))
    851864        media = self.media + adminForm.media
    852865
    853866        inline_admin_formsets = []
    854867        for inline, formset in zip(self.inline_instances, formsets):
    855868            fieldsets = list(inline.get_fieldsets(request, obj))
    856             inline_admin_formset = helpers.InlineAdminFormSet(inline, formset, fieldsets)
     869            readonly = list(inline.get_readonly_fields(request, obj))
     870            inline_admin_formset = helpers.InlineAdminFormSet(inline, formset,
     871                fieldsets, readonly)
    857872            inline_admin_formsets.append(inline_admin_formset)
    858873            media = media + inline_admin_formset.media
    859874
    class InlineModelAdmin(BaseModelAdmin):  
    11441159            exclude = []
    11451160        else:
    11461161            exclude = list(self.exclude)
     1162        exclude.extend(kwargs.get("exclude", []))
     1163        exclude.extend(self.get_readonly_fields(request, obj))
    11471164        # if exclude is an empty list we use None, since that's the actual
    11481165        # default
     1166        exclude = exclude or None
    11491167        defaults = {
    11501168            "form": self.form,
    11511169            "formset": self.formset,
    11521170            "fk_name": self.fk_name,
    11531171            "fields": fields,
    1154             "exclude": (exclude + kwargs.get("exclude", [])) or None,
     1172            "exclude": exclude,
    11551173            "formfield_callback": curry(self.formfield_for_dbfield, request=request),
    11561174            "extra": self.extra,
    11571175            "max_num": self.max_num,
    class InlineModelAdmin(BaseModelAdmin):  
    11631181        if self.declared_fieldsets:
    11641182            return self.declared_fieldsets
    11651183        form = self.get_formset(request).form
    1166         return [(None, {'fields': form.base_fields.keys()})]
     1184        fields = form.base_fields.keys() + list(self.get_readonly_fields(request, obj))
     1185        return [(None, {'fields': fields})]
    11671186
    11681187class StackedInline(InlineModelAdmin):
    11691188    template = 'admin/edit_inline/stacked.html'
  • django/contrib/admin/templates/admin/includes/fieldset.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 b  
    11<fieldset class="module aligned {{ fieldset.classes }}">
    2   {% if fieldset.name %}<h2>{{ fieldset.name }}</h2>{% endif %}
    3   {% if fieldset.description %}<div class="description">{{ fieldset.description|safe }}</div>{% endif %}
    4   {% for line in fieldset %}
    5       <div class="form-row{% if line.errors %} errors{% endif %} {% for field in line %}{{ field.field.name }} {% endfor %} ">
    6       {{ line.errors }}
    7       {% for field in line %}
    8       <div{% if not line.fields|length_is:"1" %} class="field-box"{% endif %}>
    9           {% if field.is_checkbox %}
    10               {{ field.field }}{{ field.label_tag }}
    11           {% else %}
    12               {{ field.label_tag }}{{ field.field }}
    13           {% endif %}
    14           {% if field.field.field.help_text %}<p class="help">{{ field.field.field.help_text|safe }}</p>{% endif %}
    15       </div>
    16       {% endfor %}
    17       </div>
    18   {% endfor %}
     2    {% if fieldset.name %}<h2>{{ fieldset.name }}</h2>{% endif %}
     3    {% if fieldset.description %}
     4        <div class="description">{{ fieldset.description|safe }}</div>
     5    {% endif %}
     6    {% for line in fieldset %}
     7        <div class="form-row{% if line.errors %} errors{% endif %} {% for field in line %}{{ field.field.name }} {% endfor %} ">
     8            {{ line.errors }}
     9            {% for field in line %}
     10                <div{% if not line.fields|length_is:"1" %} class="field-box"{% endif %}>
     11                    {% if field.is_checkbox %}
     12                        {{ field.field }}{{ field.label_tag }}
     13                    {% else %}
     14                        {{ field.label_tag }}
     15                        {% if field.is_readonly %}
     16                            {{ field.contents }}
     17                        {% else %}
     18                            {{ field.field }}
     19                        {% endif %}
     20                    {% endif %}
     21                    {% if field.field.field.help_text %}
     22                        <p class="help">{{ field.field.field.help_text|safe }}</p>
     23                    {% endif %}
     24                </div>
     25            {% endfor %}
     26        </div>
     27    {% endfor %}
    1928</fieldset>
  • docs/ref/contrib/admin/index.txt

    diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt
    index c1e05ed..34d283b 100644
    a b into a ``Input`` widget for either a ``ForeignKey`` or ``ManyToManyField``::  
    540540    class ArticleAdmin(admin.ModelAdmin):
    541541        raw_id_fields = ("newspaper",)
    542542
     543.. attribute:: ModelAdmin.readonly_fields
     544
     545By default any field Django's admin displays is shown as editable.  Any fields
     546in this option (which should be a ``list`` or ``tuple``) will be displayed as
     547just showing the data they contain, without the ability to be edited.  Note
     548that fields in this option shouldn't be in the ``fields`` or ``exclude``
     549options, they can however be in the ``fieldsets`` option to control where they
     550appear.
     551
    543552.. attribute:: ModelAdmin.save_as
    544553
    545554Set ``save_as`` to enable a "save as" feature on admin change forms.
    model instance::  
    744753                instance.save()
    745754            formset.save_m2m()
    746755
     756.. method:: ModelAdmin.get_readonly_fields(self, request, obj=None)
     757
     758The ``get_readonly_fields`` method is given the ``HttpRequest`` and the
     759``obj`` being edited (or ``None`` on a add form) and is expected to return a
     760``list`` or ``tuple`` of field names that will be displayed as read-only, as
     761described above in the ``ModelAdmin.readonly_fields`` section.  Note that this
     762method will be called several times during a given request, therefore if it
     763performs and very expensive calculations it may be wise to cache them.
     764
    747765.. method:: ModelAdmin.get_urls(self)
    748766
    749767.. versionadded:: 1.1
Back to Top