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
- Template tag - list punctuation for a list of items by shapiromatron 11 months, 2 weeks ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 11 months, 2 weeks ago
- Serializer factory with Django Rest Framework by julio 1 year, 6 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 7 months ago
- Help text hyperlinks by sa2812 1 year, 7 months ago
Comments
Please login first before commenting.