using pretty much the same template as for a non-form view. This solution is less hackish than the previous one which used the form.as_table() rendering of a form, and then split the resulting html in chunks. Instead, '''all''' the html has been moved out to a formfield template, and a custom form class is generated which will render the form elements using an indicated template. * Add form factory in utils.py (an old version which still has html in the code is there for history, but will be removed * Changes in ipr/views.py:new(), now subclassing one form with some class members being other form instances, matching the members used for the show() view * A fix in ipr/models, making an email field use models.EmailField * Reverting a number of changes in ipr/details.html which aren't needed any more, as well as in ipr/new.html; and adding ipr/formfield.html - Legacy-Id: 104
267 lines
8.9 KiB
Python
267 lines
8.9 KiB
Python
import operator
|
|
# look at snippets 59, 148, 99 for newforms helpers
|
|
|
|
# http://www.djangosnippets.org/snippets/59/
|
|
def form_decorator(fields = {}, attrs = {}, widgets = {},
|
|
labels = {}, choices = {}):
|
|
|
|
"""
|
|
This function helps to add overrides when creating forms from models/instances.
|
|
Pass in dictionary of fields to override certain fields altogether, otherwise
|
|
add widgets or labels as desired.
|
|
|
|
For example:
|
|
|
|
class Project(models.Model):
|
|
|
|
name = models.CharField(maxlength = 100)
|
|
description = models.TextField()
|
|
owner = models.ForeignKey(User)
|
|
|
|
project_fields = dict(
|
|
owner = None
|
|
)
|
|
|
|
project_widgets = dict(
|
|
name = forms.TextInput({"size":40}),
|
|
description = forms.Textarea({"rows":5, "cols":40}))
|
|
|
|
project_labels = dict(
|
|
name = "Enter your project name here"
|
|
)
|
|
|
|
callback = form_decorator(project_fields, project_widgets, project_labels)
|
|
project_form = forms.form_for_model(Project, formfield_callback = callback)
|
|
|
|
This saves having to redefine whole fields for example just to change a widget
|
|
setting or label.
|
|
"""
|
|
|
|
def formfields_callback(f, **kw):
|
|
|
|
if f.name in fields:
|
|
|
|
# replace field altogether
|
|
field = fields[f.name]
|
|
f.initial = kw.pop("initial", None)
|
|
return field
|
|
|
|
if f.name in widgets:
|
|
|
|
kw["widget"] = widgets[f.name]
|
|
|
|
if f.name in attrs:
|
|
|
|
widget = kw.pop("widget", f.formfield().widget)
|
|
if widget :
|
|
widget.attrs.update(attrs[f.name])
|
|
kw["widget"] = widget
|
|
|
|
if f.name in labels:
|
|
|
|
kw["label"] = labels[f.name]
|
|
|
|
if f.name in choices:
|
|
|
|
choice_set = choices[f.name]
|
|
if callable(choice_set) : choice_set = choice_set()
|
|
kw["choices"] = choice_set
|
|
|
|
|
|
return f.formfield(**kw)
|
|
|
|
return formfields_callback
|
|
|
|
|
|
# Caching accessor for the reverse of a ForeignKey relatinoship
|
|
# Started by axiak on #django
|
|
class FKAsOneToOne(object):
|
|
def __init__(self, field, reverse = False, query = None):
|
|
self.field = field
|
|
self.reverse = reverse
|
|
self.query = query
|
|
|
|
def __get_attr(self, instance):
|
|
if self.reverse:
|
|
field_name = '%s_set' % self.field
|
|
else:
|
|
field_name = self.field
|
|
return getattr(instance, field_name)
|
|
|
|
def __get__(self, instance, Model):
|
|
if not hasattr(instance, '_field_values'):
|
|
instance._field_values = {}
|
|
try:
|
|
return instance._field_values[self.field]
|
|
except KeyError:
|
|
pass
|
|
|
|
if self.reverse:
|
|
value_set = self.__get_attr(instance).all()
|
|
if self.query:
|
|
value_set = value_set.filter(self.query)
|
|
try:
|
|
instance._field_values[self.field] = value_set[0]
|
|
except IndexError:
|
|
instance._field_values[self.field] = None
|
|
else:
|
|
instance._field_values[self.field] = self.__get_attr(instance)
|
|
|
|
return instance._field_values[self.field]
|
|
|
|
def __set__(self, instance, value):
|
|
if self.reverse:
|
|
# this is dangerous
|
|
#other_instance = self.__get_attr(instance).all()[0]
|
|
#setattr(other_instance, self.field, value)
|
|
#other_instance.save()
|
|
raise NotImplemented
|
|
else:
|
|
setattr(instance, self.field, value)
|
|
|
|
|
|
def orl(list):
|
|
""" Return the "or" of every element in a list.
|
|
Used to generate "or" queries with a list of Q objects. """
|
|
return reduce(operator.__or__, list)
|
|
|
|
def flattenl(list):
|
|
""" Flatten a list one level, e.g., turn
|
|
[ ['a'], ['b'], ['c', 'd'] ] into
|
|
[ 'a', 'b', 'c', 'd' ]
|
|
"""
|
|
return reduce(operator.__concat__, list)
|
|
|
|
|
|
def split_form(html, blocks):
|
|
"""Split the rendering of a form into a dictionary of named blocks.
|
|
|
|
Takes the html of the rendered form as the first argument.
|
|
|
|
Expects a dictionary as the second argument, with desired block
|
|
name and a field specification as key:value pairs.
|
|
|
|
The field specification can be either a list of field names, or
|
|
a string with the field names separated by whitespace.
|
|
|
|
The return value is a new dictionary, with the same keys as the
|
|
block specification dictionary, and the form rendering matching
|
|
the specified keys as the value.
|
|
|
|
Any line in the rendered form which doesn't match any block's
|
|
field list will cause an exception to be raised.
|
|
"""
|
|
import re
|
|
output = dict([(block,[]) for block in blocks])
|
|
# handle field lists in string form
|
|
for block in blocks:
|
|
if type(blocks[block]) == type(""):
|
|
blocks[block] = blocks[block].split()
|
|
|
|
# collapse radio button html to one line
|
|
html = re.sub('\n(.*type="radio".*\n)', "\g<1>", html)
|
|
html = re.sub('(?m)^(.*type="radio".*)\n', "\g<1>", html)
|
|
|
|
for line in html.split('\n'):
|
|
found = False
|
|
for block in blocks:
|
|
for field in blocks[block]:
|
|
if ('name="%s"' % field) in line:
|
|
output[block].append(line)
|
|
found = True
|
|
if not found:
|
|
raise LookupError("Could not place line in any section: '%s'" % line)
|
|
|
|
for block in output:
|
|
output[block] = "\n".join(output[block])
|
|
|
|
return output
|
|
|
|
def mk_formatting_form(format="<span>%(label)s</span><span><ul>%(errors)s</ul>%(field)s%(help_text)s</span>",
|
|
labelfmt="%s:", fieldfmt="%s", errfmt="<li>%s</li>", error_wrap="<ul>%s</ul>", helpfmt="%s"):
|
|
"""Create a form class which formats its fields using the provided format string(s).
|
|
|
|
The format string may use these format specifications:
|
|
%(label)s
|
|
%(errors)s
|
|
%(field)s
|
|
%(help_text)s
|
|
|
|
The individual sub-formats must contain "%s" if defined.
|
|
"""
|
|
class FormattingForm(forms.BaseForm):
|
|
_format = format
|
|
_labelfmt = labelfmt
|
|
_fieldfmt = fieldfmt
|
|
_errfmt = errfmt
|
|
_errwrap = error_wrap
|
|
_helpfmt = helpfmt
|
|
def __getitem__(self, name):
|
|
"Returns a BoundField with the given name."
|
|
# syslog.syslog("FormattingForm.__getitem__(%s)" % (name))
|
|
try:
|
|
field = self.fields[name]
|
|
except KeyError:
|
|
# syslog.syslog("Exception: FormattingForm.__getitem__: Key %r not found" % (name))
|
|
raise KeyError('Key %r not found in Form' % name)
|
|
|
|
if not isinstance(field, forms.fields.Field):
|
|
return field
|
|
|
|
try:
|
|
bf = forms.forms.BoundField(self, field, name)
|
|
except Exception, e:
|
|
# syslog.syslog("Exception: FormattingForm.__getitem__: %s" % (e))
|
|
raise Exception(e)
|
|
|
|
try:
|
|
error_txt = "".join([self._errfmt % escape(error) for error in bf.errors])
|
|
error_txt = error_txt and self._errwrap % error_txt
|
|
label_txt = bf.label and self._labelfmt % bf.label_tag(escape(bf.label)) or ''
|
|
field_txt = self._fieldfmt % unicode(bf)
|
|
help_txt = field.help_text and self._helpfmt % field.help_text or u''
|
|
|
|
except Exception, e:
|
|
# syslog.syslog("Exception: FormattingForm.__getitem__: %s" % (e))
|
|
raise Exception(e)
|
|
|
|
return self._format % {"label":label_txt, "errors":error_txt, "field":field_txt, "help_text":help_txt}
|
|
|
|
def add_prefix(self, field_name):
|
|
return self.prefix and ('%s_%s' % (self.prefix, field_name)) or field_name
|
|
|
|
|
|
# syslog.syslog("Created new FormattingForm class: %s" % FormattingForm)
|
|
|
|
return FormattingForm
|
|
|
|
|
|
def makeFormattingForm(template=None):
|
|
"""Create a form class which formats its fields using the provided template
|
|
|
|
The template is provided with a dictionary containing the following keys, value
|
|
pairs:
|
|
|
|
"label": field label, if any,
|
|
"errors": list of errors, if any,
|
|
"field": widget rendering for an unbound form / field value for a bound form,
|
|
"help_text": field help text, if any
|
|
|
|
"""
|
|
from django.template import loader
|
|
import django.newforms as forms
|
|
|
|
class FormattingForm(forms.BaseForm):
|
|
_template = template
|
|
def __getitem__(self, name):
|
|
"Returns a BoundField with the given name."
|
|
try:
|
|
field = self.fields[name]
|
|
except KeyError:
|
|
raise KeyError('Key %r not found in Form' % name)
|
|
if not isinstance(field, forms.fields.Field):
|
|
return field
|
|
bf = forms.forms.BoundField(self, field, name)
|
|
return loader.render_to_string(self._template, { "errors": bf.errors, "label": bf.label, "field": unicode(bf), "help_text": field.help_text })
|
|
return FormattingForm
|