Serializers can now be rendered directly to HTML

This commit is contained in:
Tom Christie 2013-10-02 13:45:35 +01:00
parent 1fd83adb9c
commit a14f1e8864
4 changed files with 49 additions and 65 deletions

View File

@ -123,6 +123,7 @@ class Field(object):
use_files = False use_files = False
form_field_class = forms.CharField form_field_class = forms.CharField
type_label = 'field' type_label = 'field'
widget = None
def __init__(self, source=None, label=None, help_text=None): def __init__(self, source=None, label=None, help_text=None):
self.parent = None self.parent = None
@ -134,9 +135,29 @@ class Field(object):
if label is not None: if label is not None:
self.label = smart_text(label) self.label = smart_text(label)
else:
self.label = None
if help_text is not None: if help_text is not None:
self.help_text = strip_multiple_choice_msg(smart_text(help_text)) self.help_text = strip_multiple_choice_msg(smart_text(help_text))
else:
self.help_text = None
self._errors = []
self._value = None
self._name = None
@property
def errors(self):
return self._errors
def widget_html(self):
if not self.widget:
return ''
return self.widget.render(self._name, self._value)
def label_tag(self):
return '<label for="%s">%s:</label>' % (self._name, self.label)
def initialize(self, parent, field_name): def initialize(self, parent, field_name):
""" """

View File

@ -336,71 +336,15 @@ class HTMLFormRenderer(BaseRenderer):
template = 'rest_framework/form.html' template = 'rest_framework/form.html'
charset = 'utf-8' charset = 'utf-8'
def data_to_form_fields(self, data):
fields = {}
for key, val in data.fields.items():
if getattr(val, 'read_only', True):
# Don't include read-only fields.
continue
if getattr(val, 'fields', None):
# Nested data not supported by HTML forms.
continue
kwargs = {}
kwargs['required'] = val.required
#if getattr(v, 'queryset', None):
# kwargs['queryset'] = v.queryset
if getattr(val, 'choices', None) is not None:
kwargs['choices'] = val.choices
if getattr(val, 'regex', None) is not None:
kwargs['regex'] = val.regex
if getattr(val, 'widget', None):
widget = copy.deepcopy(val.widget)
kwargs['widget'] = widget
if getattr(val, 'default', None) is not None:
kwargs['initial'] = val.default
if getattr(val, 'label', None) is not None:
kwargs['label'] = val.label
if getattr(val, 'help_text', None) is not None:
kwargs['help_text'] = val.help_text
fields[key] = val.form_field_class(**kwargs)
return fields
def render(self, data, accepted_media_type=None, renderer_context=None): def render(self, data, accepted_media_type=None, renderer_context=None):
""" """
Render serializer data and return an HTML form, as a string. Render serializer data and return an HTML form, as a string.
""" """
# The HTMLFormRenderer currently uses something of a hack to render renderer_context = renderer_context or {}
# the content, by translating each of the serializer fields into request = renderer_context['request']
# an html form field, creating a dynamic form using those fields,
# and then rendering that form.
# This isn't strictly neccessary, as we could render the serilizer
# fields to HTML directly. The implementation is historical and will
# likely change at some point.
self.renderer_context = renderer_context or {}
request = self.renderer_context['request']
# Creating an on the fly form see:
# http://stackoverflow.com/questions/3915024/dynamically-creating-classes-python
fields = self.data_to_form_fields(data)
DynamicForm = type(str('DynamicForm'), (forms.Form,), fields)
data = None if data.empty else data
template = loader.get_template(self.template) template = loader.get_template(self.template)
context = RequestContext(request, {'form': DynamicForm(data)}) context = RequestContext(request, {'form': data})
return template.render(context) return template.render(context)

View File

@ -32,6 +32,13 @@ from rest_framework.relations import *
from rest_framework.fields import * from rest_framework.fields import *
def pretty_name(name):
"""Converts 'first_name' to 'First name'"""
if not name:
return ''
return name.replace('_', ' ').capitalize()
class RelationsList(list): class RelationsList(list):
_deleted = [] _deleted = []
@ -306,7 +313,17 @@ class BaseSerializer(WritableField):
for field_name, field in self.fields.items(): for field_name, field in self.fields.items():
field.initialize(parent=self, field_name=field_name) field.initialize(parent=self, field_name=field_name)
key = self.get_field_key(field_name) key = self.get_field_key(field_name)
value = field.field_to_native(obj, field_name) if self._errors:
value = self.init_data.get(field_name)
else:
value = field.field_to_native(obj, field_name)
field._errors = self._errors.get(key) if self._errors else None
field._name = field_name
field._value = value
if not field.label:
field.label = pretty_name(key)
ret[key] = value ret[key] = value
ret.fields[key] = field ret.fields[key] = field
return ret return ret

View File

@ -1,13 +1,15 @@
{% load rest_framework %} {% load rest_framework %}
{% csrf_token %} {% csrf_token %}
{{ form.non_field_errors }} {{ form.non_field_errors }}
{% for field in form %} {% for field in form.fields.values %}
<div class="control-group"> <!--{% if field.errors %}error{% endif %}--> {% if not field.read_only %}
<div class="control-group {% if field.errors %}error{% endif %}">
{{ field.label_tag|add_class:"control-label" }} {{ field.label_tag|add_class:"control-label" }}
<div class="controls"> <div class="controls">
{{ field }} {{ field.widget_html }}
<span class="help-block">{{ field.help_text }}</span> {% if field.help_text %}<span class="help-block">{{ field.help_text }}</span>{% endif %}
<!--{{ field.errors|add_class:"help-block" }}--> {% for error in field.errors %}<span class="help-block">{{ error }}</span>{% endfor %}
</div> </div>
</div> </div>
{% endif %}
{% endfor %} {% endfor %}