Code

Ticket #342: readonly-admin.diff

File readonly-admin.diff, 14.6 KB (added by Alex, 5 years ago)

Initial patch.

Line 
1diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py
2index 40437c0..d8726ea 100644
3--- a/django/contrib/admin/helpers.py
4+++ b/django/contrib/admin/helpers.py
5@@ -1,13 +1,15 @@
6-
7 from django import forms
8 from django.conf import settings
9-from django.utils.html import escape
10-from django.utils.safestring import mark_safe
11-from django.utils.encoding import force_unicode
12 from django.contrib.admin.util import flatten_fieldsets
13 from django.contrib.contenttypes.models import ContentType
14+from django.forms.util import flatatt
15+from django.utils.encoding import force_unicode
16+from django.utils.html import escape
17+from django.utils.safestring import mark_safe
18+from django.utils.text import capfirst
19 from django.utils.translation import ugettext_lazy as _
20 
21+
22 ACTION_CHECKBOX_NAME = '_selected_action'
23 
24 class ActionForm(forms.Form):
25@@ -16,16 +18,18 @@ class ActionForm(forms.Form):
26 checkbox = forms.CheckboxInput({'class': 'action-select'}, lambda value: False)
27 
28 class AdminForm(object):
29-    def __init__(self, form, fieldsets, prepopulated_fields):
30+    def __init__(self, form, fieldsets, prepopulated_fields, readonly_fields):
31         self.form, self.fieldsets = form, normalize_fieldsets(fieldsets)
32         self.prepopulated_fields = [{
33             'field': form[field_name],
34             'dependencies': [form[f] for f in dependencies]
35         } for field_name, dependencies in prepopulated_fields.items()]
36+        self.readonly_fields = readonly_fields
37 
38     def __iter__(self):
39         for name, options in self.fieldsets:
40-            yield Fieldset(self.form, name, **options)
41+            yield Fieldset(self.form, name,
42+                readonly_fields=self.readonly_fields, **options)
43 
44     def first_field(self):
45         try:
46@@ -49,11 +53,13 @@ class AdminForm(object):
47     media = property(_media)
48 
49 class Fieldset(object):
50-    def __init__(self, form, name=None, fields=(), classes=(), description=None):
51+    def __init__(self, form, name=None, readonly_fields=(), fields=(), classes=(),
52+        description=None):
53         self.form = form
54         self.name, self.fields = name, fields
55         self.classes = u' '.join(classes)
56         self.description = description
57+        self.readonly_fields = readonly_fields
58 
59     def _media(self):
60         if 'collapse' in self.classes:
61@@ -63,22 +69,26 @@ class Fieldset(object):
62 
63     def __iter__(self):
64         for field in self.fields:
65-            yield Fieldline(self.form, field)
66+            yield Fieldline(self.form, field, self.readonly_fields)
67 
68 class Fieldline(object):
69-    def __init__(self, form, field):
70+    def __init__(self, form, field, readonly_fields):
71         self.form = form # A django.forms.Form instance
72         if isinstance(field, basestring):
73             self.fields = [field]
74         else:
75             self.fields = field
76+        self.readonly_fields = readonly_fields
77 
78     def __iter__(self):
79         for i, field in enumerate(self.fields):
80-            yield AdminField(self.form, field, is_first=(i == 0))
81+            if field in self.readonly_fields:
82+                yield AdminReadonlyField(self.form, field, is_first=(i == 0))
83+            else:
84+                yield AdminField(self.form, field, is_first=(i == 0))
85 
86     def errors(self):
87-        return mark_safe(u'\n'.join([self.form[f].errors.as_ul() for f in self.fields]).strip('\n'))
88+        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'))
89 
90 class AdminField(object):
91     def __init__(self, form, field, is_first):
92@@ -100,20 +110,44 @@ class AdminField(object):
93         attrs = classes and {'class': u' '.join(classes)} or {}
94         return self.field.label_tag(contents=contents, attrs=attrs)
95 
96+class AdminReadonlyField(object):
97+    def __init__(self, form, field, is_first):
98+        self.field = field
99+        self.form = form
100+        self.is_first = is_first
101+        self.is_checkbox = False
102+        self.is_readonly = True
103+   
104+    def label_tag(self):
105+        attrs = {}
106+        if not self.is_first:
107+            attrs["class"] = "inline"
108+        contents = force_unicode(escape(capfirst(self.field))) + u":"
109+        return mark_safe('<label %(attrs)s>%(contents)s</label>' % {
110+            "attrs": flatatt(attrs),
111+            "contents": contents,
112+        })
113+   
114+    def contents(self):
115+        return getattr(self.form.instance, self.field)
116+
117 class InlineAdminFormSet(object):
118     """
119     A wrapper around an inline formset for use in the admin system.
120     """
121-    def __init__(self, inline, formset, fieldsets):
122+    def __init__(self, inline, formset, fieldsets, readonly_fields):
123         self.opts = inline
124         self.formset = formset
125         self.fieldsets = fieldsets
126+        self.readonly_fields = readonly_fields
127 
128     def __iter__(self):
129         for form, original in zip(self.formset.initial_forms, self.formset.get_queryset()):
130-            yield InlineAdminForm(self.formset, form, self.fieldsets, self.opts.prepopulated_fields, original)
131+            yield InlineAdminForm(self.formset, form, self.fieldsets,
132+                self.opts.prepopulated_fields, original, self.readonly_fields)
133         for form in self.formset.extra_forms:
134-            yield InlineAdminForm(self.formset, form, self.fieldsets, self.opts.prepopulated_fields, None)
135+            yield InlineAdminForm(self.formset, form, self.fieldsets,
136+                self.opts.prepopulated_fields, None, self.readonly_fields)
137 
138     def fields(self):
139         fk = getattr(self.formset, "fk", None)
140@@ -133,17 +167,19 @@ class InlineAdminForm(AdminForm):
141     """
142     A wrapper around an inline form for use in the admin system.
143     """
144-    def __init__(self, formset, form, fieldsets, prepopulated_fields, original):
145+    def __init__(self, formset, form, fieldsets, prepopulated_fields, original,
146+        readonly_fields):
147         self.formset = formset
148         self.original = original
149         if original is not None:
150             self.original_content_type_id = ContentType.objects.get_for_model(original).pk
151         self.show_url = original and hasattr(original, 'get_absolute_url')
152-        super(InlineAdminForm, self).__init__(form, fieldsets, prepopulated_fields)
153+        super(InlineAdminForm, self).__init__(form, fieldsets, prepopulated_fields,
154+            readonly_fields)
155 
156     def __iter__(self):
157         for name, options in self.fieldsets:
158-            yield InlineFieldset(self.formset, self.form, name, **options)
159+            yield InlineFieldset(self.formset, self.form, name, self.readonly_fields, **options)
160 
161     def has_auto_field(self):
162         if self.form._meta.model._meta.has_auto_field:
163@@ -194,7 +230,7 @@ class InlineFieldset(Fieldset):
164         for field in self.fields:
165             if fk and fk.name == field:
166                 continue
167-            yield Fieldline(self.form, field)
168+            yield Fieldline(self.form, field, self.readonly_fields)
169 
170 class AdminErrorList(forms.util.ErrorList):
171     """
172diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
173index 3144a22..57000b8 100644
174--- a/django/contrib/admin/options.py
175+++ b/django/contrib/admin/options.py
176@@ -64,6 +64,7 @@ class BaseModelAdmin(object):
177     radio_fields = {}
178     prepopulated_fields = {}
179     formfield_overrides = {}
180+    readonly_fields = ()
181 
182     def __init__(self):
183         self.formfield_overrides = dict(FORMFIELD_FOR_DBFIELD_DEFAULTS, **self.formfield_overrides)
184@@ -171,6 +172,9 @@ class BaseModelAdmin(object):
185             return [(None, {'fields': self.fields})]
186         return None
187     declared_fieldsets = property(_declared_fieldsets)
188+   
189+    def get_readonly_fields(self, request, obj=None):
190+        return self.readonly_fields
191 
192 class ModelAdmin(BaseModelAdmin):
193     "Encapsulates all admin options and functionality for a given model."
194@@ -321,7 +325,8 @@ class ModelAdmin(BaseModelAdmin):
195         if self.declared_fieldsets:
196             return self.declared_fieldsets
197         form = self.get_form(request, obj)
198-        return [(None, {'fields': form.base_fields.keys()})]
199+        fields = form.base_fields.keys() + list(self.get_readonly_fields(request, obj))
200+        return [(None, {'fields': fields})]
201 
202     def get_form(self, request, obj=None, **kwargs):
203         """
204@@ -336,12 +341,15 @@ class ModelAdmin(BaseModelAdmin):
205             exclude = []
206         else:
207             exclude = list(self.exclude)
208+        exclude.extend(kwargs.get("exclude", []))
209+        exclude.extend(self.get_readonly_fields(request, obj))
210         # if exclude is an empty list we pass None to be consistant with the
211         # default on modelform_factory
212+        exclude = exclude or None
213         defaults = {
214             "form": self.form,
215             "fields": fields,
216-            "exclude": (exclude + kwargs.get("exclude", [])) or None,
217+            "exclude": exclude,
218             "formfield_callback": curry(self.formfield_for_dbfield, request=request),
219         }
220         defaults.update(kwargs)
221@@ -759,13 +767,17 @@ class ModelAdmin(BaseModelAdmin):
222                 formset = FormSet(instance=self.model(), prefix=prefix)
223                 formsets.append(formset)
224 
225-        adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)), self.prepopulated_fields)
226+        adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)),
227+            self.prepopulated_fields, self.get_readonly_fields(request)
228+        )
229         media = self.media + adminForm.media
230 
231         inline_admin_formsets = []
232         for inline, formset in zip(self.inline_instances, formsets):
233             fieldsets = list(inline.get_fieldsets(request))
234-            inline_admin_formset = helpers.InlineAdminFormSet(inline, formset, fieldsets)
235+            readonly = list(inline.get_readonly_fields(request))
236+            inline_admin_formset = helpers.InlineAdminFormSet(inline, formset,
237+                fieldsets, readonly)
238             inline_admin_formsets.append(inline_admin_formset)
239             media = media + inline_admin_formset.media
240 
241@@ -847,13 +859,16 @@ class ModelAdmin(BaseModelAdmin):
242                 formset = FormSet(instance=obj, prefix=prefix)
243                 formsets.append(formset)
244 
245-        adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj), self.prepopulated_fields)
246+        adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj),
247+            self.prepopulated_fields, self.get_readonly_fields(request, obj))
248         media = self.media + adminForm.media
249 
250         inline_admin_formsets = []
251         for inline, formset in zip(self.inline_instances, formsets):
252             fieldsets = list(inline.get_fieldsets(request, obj))
253-            inline_admin_formset = helpers.InlineAdminFormSet(inline, formset, fieldsets)
254+            readonly = list(inline.get_readonly_fields(request, obj))
255+            inline_admin_formset = helpers.InlineAdminFormSet(inline, formset,
256+                fieldsets, readonly)
257             inline_admin_formsets.append(inline_admin_formset)
258             media = media + inline_admin_formset.media
259 
260@@ -1144,14 +1159,17 @@ class InlineModelAdmin(BaseModelAdmin):
261             exclude = []
262         else:
263             exclude = list(self.exclude)
264+        exclude.extend(kwargs.get("exclude", []))
265+        exclude.extend(self.get_readonly_fields(request, obj))
266         # if exclude is an empty list we use None, since that's the actual
267         # default
268+        exclude = exclude or None
269         defaults = {
270             "form": self.form,
271             "formset": self.formset,
272             "fk_name": self.fk_name,
273             "fields": fields,
274-            "exclude": (exclude + kwargs.get("exclude", [])) or None,
275+            "exclude": exclude,
276             "formfield_callback": curry(self.formfield_for_dbfield, request=request),
277             "extra": self.extra,
278             "max_num": self.max_num,
279@@ -1163,7 +1181,8 @@ class InlineModelAdmin(BaseModelAdmin):
280         if self.declared_fieldsets:
281             return self.declared_fieldsets
282         form = self.get_formset(request).form
283-        return [(None, {'fields': form.base_fields.keys()})]
284+        fields = form.base_fields.keys() + list(self.get_readonly_fields(request, obj))
285+        return [(None, {'fields': fields})]
286 
287 class StackedInline(InlineModelAdmin):
288     template = 'admin/edit_inline/stacked.html'
289diff --git a/django/contrib/admin/templates/admin/includes/fieldset.html b/django/contrib/admin/templates/admin/includes/fieldset.html
290index 8ee24b1..bcde368 100644
291--- a/django/contrib/admin/templates/admin/includes/fieldset.html
292+++ b/django/contrib/admin/templates/admin/includes/fieldset.html
293@@ -1,19 +1,28 @@
294 <fieldset class="module aligned {{ fieldset.classes }}">
295-  {% if fieldset.name %}<h2>{{ fieldset.name }}</h2>{% endif %}
296-  {% if fieldset.description %}<div class="description">{{ fieldset.description|safe }}</div>{% endif %}
297-  {% for line in fieldset %}
298-      <div class="form-row{% if line.errors %} errors{% endif %} {% for field in line %}{{ field.field.name }} {% endfor %} ">
299-      {{ line.errors }}
300-      {% for field in line %}
301-      <div{% if not line.fields|length_is:"1" %} class="field-box"{% endif %}>
302-          {% if field.is_checkbox %}
303-              {{ field.field }}{{ field.label_tag }}
304-          {% else %}
305-              {{ field.label_tag }}{{ field.field }}
306-          {% endif %}
307-          {% if field.field.field.help_text %}<p class="help">{{ field.field.field.help_text|safe }}</p>{% endif %}
308-      </div>
309-      {% endfor %}
310-      </div>
311-  {% endfor %}
312+    {% if fieldset.name %}<h2>{{ fieldset.name }}</h2>{% endif %}
313+    {% if fieldset.description %}
314+        <div class="description">{{ fieldset.description|safe }}</div>
315+    {% endif %}
316+    {% for line in fieldset %}
317+        <div class="form-row{% if line.errors %} errors{% endif %} {% for field in line %}{{ field.field.name }} {% endfor %} ">
318+            {{ line.errors }}
319+            {% for field in line %}
320+                <div{% if not line.fields|length_is:"1" %} class="field-box"{% endif %}>
321+                    {% if field.is_checkbox %}
322+                        {{ field.field }}{{ field.label_tag }}
323+                    {% else %}
324+                        {{ field.label_tag }}
325+                        {% if field.is_readonly %}
326+                            {{ field.contents }}
327+                        {% else %}
328+                            {{ field.field }}
329+                        {% endif %}
330+                    {% endif %}
331+                    {% if field.field.field.help_text %}
332+                        <p class="help">{{ field.field.field.help_text|safe }}</p>
333+                    {% endif %}
334+                </div>
335+            {% endfor %}
336+        </div>
337+    {% endfor %}
338 </fieldset>