Login

DisplayModelForm

Author:
koenb
Posted:
May 15, 2008
Language:
Python
Version:
.96
Score:
4 (after 4 ratings)

Subclass of the ModelForm which allows to make fields 'display_only'. This means no formfield will be displayed, but a suitable representation. You can make all fields display_only or just a few (or you can use the form as a normal modelform).

There are also some extra's to easily set attrs on fields or set help_texts on widgets when using this form.

Why ?

I made my own set of generic crud views based on newforms, but added a 'display' view to simply display objects, in the same table layout as the editing is done, but without the fields. I wanted to avoid having to redefine my forms twice and I wanted to reuse some generic templates as much as possible.

Obviously this is not good for performance, but I use it for an intranet app with a lot of objects, but not that big a load.

Their is definitely still a lot of room for improvement, and maybe all this could have been done much easier.

How to use? See the docstring.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
"""
replacement for ModelForm to make certain (or all) fields display-only
"""

import django.newforms as forms
from django.db.models.fields import FieldDoesNotExist
from django.utils.encoding import force_unicode
from django.utils.html import escape, conditional_escape
from django.utils.safestring import mark_safe
from django.utils.html import linebreaks
from django.conf import settings
import datetime, time
from itertools import chain
import markdown

class DisplayModelForm(forms.ModelForm):
    """
    Subclass of ModelForm
    Can be used as normal ModelForm
    but has the following extra's:
        - making form display_only:
        - determine the widget to use when displaying as display_only
            (to add links to foreign keys and M2M fields: use the links attribute)
        and
        - adding attrs to widgets
        - modifying help_texts in widgets
        - set links on FK and M2M fields when display-only (requires the target
            to have a get_absolute_url attribute)
        - reorder fields
        - set input and output formats on datefields uses settings.DATE_INPUT_FORMATS
            and settings.DATE_OUTPUT_FORMAT

    How to use?

        Render a form display_only: this renders all the fields not with form fields
        but with normal html.

        example:
            class MyForm(DisplayModelForm):
                ...
            use form for editing:
                form = MyForm(instance=myobject)
            use form for displaying info (all fields display_only):
                form = EventForm(instance=myobject, display_only = True)
            or make only a few field display_only:
                form = EventForm(instance=myobject, display_only = True,
                    display_fields=['title','category'])


        to determine the widgets used when rendering as display_only add a
        'displaywidgets' dict to the Meta class of the ModelForm

        example:
            class MyForm(DisplayModelForm):
                class Meta:
                    displaywidgets = {
                            'location': DisplayTextURL(urlbase='/documents/')
                        }
                # PS: do this if location is a charfield, not a foreign key
                # for foreign keys, use the links attribute (see further)

        Extra's:
            - add an 'attrs' dict to the Meta class, for each field a dict of attrs
            - add a 'help_texts' dict to Meta
            - add a 'links' list of fields to Meta
            - field ordering uses the fields list (as in ModelForm)

        example:
            class MyForm(DisplayModelForm):
                class Meta:
                    model = Event
                    attrs = {
                        'title': {'size': 90},
                        }
                    help_texts = {
                        'title': 'Give me a title!'
                        }
                    links = ['category',]

    """
    def __init__(self, display_only = False, display_fields=[], *args, **kwargs):
        super(DisplayModelForm, self).__init__(*args, **kwargs)
        if display_only:
            self._make_form_display_only(display_fields)
        else:
            self._set_defaults()
            
        # add attrs to widgets
        attrs = getattr(self.Meta, 'attrs', {})
        for field in attrs:
            try:
                self.fields[field].widget.attrs.update(attrs[field])
            except:
                pass
            
        # modify help_texts
        help_texts = getattr(self.Meta, 'help_texts', {})
        for field in help_texts:
            try:
                self.fields[field].help_text = help_texts[field]
            except:
                pass
            
        # Reorder fields
        fields = list(getattr(self.Meta, 'fields', []))
        if fields:
            for f in self.fields:
                if not f in fields:
                    fields.append(f)
            self.fields.keyOrder = fields

    def _make_form_display_only(self, display_fields=[]):
        links = getattr(self.Meta, 'links', [])
        displaywidgets = getattr(self.Meta, 'displaywidgets', [])

        dfields = display_fields or self.fields
        for field in dfields:
            if not field in self.fields:
                raise FieldDoesNotExist, 'Item %s in display_fields is no field on the form' % field
            else:
                self.fields[field].help_text = u''
                # replace widgets
                if field in displaywidgets:
                    self.fields[field].widget = displaywidgets[field]
                else:
                    if isinstance(self.fields[field].widget, forms.Select):
                        if field in links:
                            newfield = DisplayLinkedModelChoiceField(self.fields[field].queryset,
                                widget = DisplaySelect,
                                label = self.fields[field].label,
                                help_text = self.fields[field].help_text,
                                )
                            self.fields[field] = newfield
                        else:
                            self.fields[field].widget = DisplaySelect(choices=self.fields[field].widget.choices)
                    elif isinstance(self.fields[field].widget, forms.SelectMultiple):
                        if field in links:
                            newfield = DisplayLinkedModelMultipleChoiceField(self.fields[field].queryset,
                                widget = DisplaySelectMultiple,
                                label = self.fields[field].label,
                                help_text = self.fields[field].help_text,
                                )
                            self.fields[field] = newfield
                        else:
                            self.fields[field].widget = DisplaySelectMultiple(choices=self.fields[field].widget.choices)
                    elif isinstance(self.fields[field].widget, forms.Textarea):
                        self.fields[field].widget = DisplayTextarea()
                    elif isinstance(self.fields[field].widget, forms.TextInput):
                        self.fields[field].widget = DisplayTextInput()
                    elif isinstance(self.fields[field].widget, forms.CheckboxInput):
                        self.fields[field].widget = DisplayCheckboxInput()
                    elif isinstance(self.fields[field].widget, forms.FileInput):
                        self.fields[field].widget = DisplayTextInput()
                    # special cases
                    if isinstance(self.fields[field], forms.DateField):
                        self.fields[field].widget = DisplayTextInputDate()
                    if isinstance(self.fields[field], forms.URLField):
                        self.fields[field].widget = DisplayTextURL()

    def _set_defaults(self):
        for field in self.fields:
            if isinstance(self.fields[field], forms.DateField):
                self.fields[field].input_formats = settings.DATE_INPUT_FORMATS
                self.fields[field].widget = forms.DateTimeInput(format=settings.DATE_OUTPUT_FORMAT)
                

# widgets

class DisplayInput(forms.Widget):
    """ basic class display data only"""
    def format_value(self, value):
        return u'%s' % conditional_escape(force_unicode(value))

    def render(self, name, value, attrs=None):
        if value is None: value = ''
        final_attrs = u''
        if attrs.get('id'):
            final_attrs = u' id="%s"' % attrs['id']
        return mark_safe(u'<div%s>%s</div>' % (final_attrs,
                self.format_value(value)))

class DisplayTextInput(DisplayInput):
    """Custom class display data only for TextInput"""
    pass

class DisplayTextarea(DisplayInput):
    """Custom class display data only for Textarea"""
    def format_value(self, value):
        if value:
            return u'%s' % linebreaks(conditional_escape(force_unicode(value)))
        return u''

class DisplayMarkdown(DisplayInput):
    """Custom class display data only for Markdown"""
    def format_value(self, value):
        if value:
            return u'%s' % markdown(force_unicode(value))
        return u''

class DisplayTextInputDate(DisplayInput):
    """Custom class display data only for Dates"""
    def format_value(self, value):
        if value:
            return u'%s' % datetime.date.strftime(value, settings.DATE_OUTPUT_FORMAT)
        return ''

class DisplayTextURL(DisplayInput):
    """Custom class display data only for URLs"""
    def __init__(self, urlbase='', *args, **kwargs):
        super(DisplayTextURL, self).__init__(*args, **kwargs)
        self.urlbase=urlbase

    def format_value(self, value):
        url = value
        if url[:4] != 'http':
            if self.urlbase:
                url = '%s%s' % (self.urlbase, url)
            else:
                url = "http://%s" % url
        return u'<a href="%s">%s</a>' % (conditional_escape(force_unicode(url)),conditional_escape(force_unicode(value)))

class DisplaySelect(forms.Select):
    def render(self, name, value, attrs=None, choices=()):
        if value is None: value = ''
        final_attrs = u''
        if attrs.get('id'):
            final_attrs = u' id="%s"' % attrs['id']
        str_value = force_unicode(value) # Normalize to string.
        output = u''
        for option_value, option_label in chain(self.choices, choices):
            option_value = force_unicode(option_value)
            if option_value == str_value:
                output = u'%s' % conditional_escape(force_unicode(option_label))
        return mark_safe(u'<div%s>%s</div>' % (final_attrs, output))

class DisplaySelectMultiple(forms.SelectMultiple):
    def render(self, name, value, attrs=None, choices=()):
        if value is None: value = []
        final_attrs = u''
        if attrs.get('id'):
            final_attrs = u' id="%s"' % attrs['id']
        output = []
        str_values = set([force_unicode(v) for v in value]) # Normalize to strings.
        for option_value, option_label in chain(self.choices, choices):
            option_value = force_unicode(option_value)
            if option_value in str_values:
                output.append(u'%s' % conditional_escape(force_unicode(option_label)))
        if output:
            o = u'<br />'.join(output)
        else:
            o = u'---'
        return mark_safe(u'<div%s>%s</div>' % (final_attrs, o))

class DisplayCheckboxInput(DisplayInput, forms.CheckboxInput):
    def format_value(self, value):
        try:
            result = self.check_test(value)
        except: # Silently catch exceptions
            result = False
        if result:
            return u'X'
        return u'-'

# Fields

class DisplayLinkedModelChoiceField(forms.ModelChoiceField):
    """ ModelChoiceField displaying with a link
    """
    def label_from_instance(self, obj):
        value = super(DisplayLinkedModelChoiceField, self).label_from_instance(obj)
        if hasattr(obj, 'get_absolute_url'):
            url = obj.get_absolute_url()
            return mark_safe(u'<a href="%s">%s</a>' % (url, value))
        return value

class DisplayLinkedModelMultipleChoiceField(forms.ModelMultipleChoiceField):
    """ ModelMultipleChoiceField displaying with links
    """
    def label_from_instance(self, obj):
        value = super(DisplayLinkedModelMultipleChoiceField, self).label_from_instance(obj)
        if hasattr(obj, 'get_absolute_url'):
            url = obj.get_absolute_url()
            return mark_safe(u'<a href="%s">%s</a>' % (url, value))
        return value

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 11 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 11 months, 2 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 6 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 7 months ago
  5. Help text hyperlinks by sa2812 1 year, 7 months ago

Comments

Please login first before commenting.