""" 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'%s' % (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'%s' % (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'%s' % (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'
'.join(output) else: o = u'---' return mark_safe(u'%s' % (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'%s' % (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'%s' % (url, value)) return value