Support fields that reference a simple callable

This commit is contained in:
Tom Christie 2014-10-15 15:13:28 +01:00
parent e8ea365c15
commit b4f3379c70
6 changed files with 78 additions and 50 deletions

View File

@ -66,6 +66,8 @@ def get_attribute(instance, attrs):
return instance[attr] return instance[attr]
except (KeyError, TypeError, AttributeError): except (KeyError, TypeError, AttributeError):
raise exc raise exc
if is_simple_callable(instance):
return instance()
return instance return instance
@ -1025,8 +1027,6 @@ class ReadOnlyField(Field):
super(ReadOnlyField, self).__init__(**kwargs) super(ReadOnlyField, self).__init__(**kwargs)
def to_representation(self, value): def to_representation(self, value):
if is_simple_callable(value):
return value()
return value return value

View File

@ -340,76 +340,101 @@ class HTMLFormRenderer(BaseRenderer):
media_type = 'text/html' media_type = 'text/html'
format = 'form' format = 'form'
charset = 'utf-8' charset = 'utf-8'
template_pack = 'rest_framework/horizontal/'
base_template = 'form.html'
field_templates = ClassLookupDict({ default_style = ClassLookupDict({
serializers.Field: { serializers.Field: {
'default': 'input.html' 'base_template': 'input.html',
'input_type': 'text'
},
serializers.EmailField: {
'base_template': 'input.html',
'input_type': 'email'
},
serializers.URLField: {
'base_template': 'input.html',
'input_type': 'url'
},
serializers.IntegerField: {
'base_template': 'input.html',
'input_type': 'number'
},
serializers.DateTimeField: {
'base_template': 'input.html',
'input_type': 'datetime-local'
},
serializers.DateField: {
'base_template': 'input.html',
'input_type': 'date'
},
serializers.TimeField: {
'base_template': 'input.html',
'input_type': 'time'
}, },
serializers.BooleanField: { serializers.BooleanField: {
'default': 'checkbox.html' 'base_template': 'checkbox.html'
},
serializers.CharField: {
'default': 'input.html',
'textarea': 'textarea.html'
}, },
serializers.ChoiceField: { serializers.ChoiceField: {
'default': 'select.html', 'base_template': 'select.html', # Also valid: 'radio.html'
'radio': 'select_radio.html'
}, },
serializers.MultipleChoiceField: { serializers.MultipleChoiceField: {
'default': 'select_multiple.html', 'base_template': 'select_multiple.html', # Also valid: 'checkbox_multiple.html'
'checkbox': 'select_checkbox.html'
}, },
serializers.ManyRelation: { serializers.ManyRelation: {
'default': 'select_multiple.html', 'base_template': 'select_multiple.html', # Also valid: 'checkbox_multiple.html'
'checkbox': 'select_checkbox.html'
}, },
serializers.Serializer: { serializers.Serializer: {
'default': 'fieldset.html' 'base_template': 'fieldset.html'
}, },
serializers.ListSerializer: { serializers.ListSerializer: {
'default': 'list_fieldset.html' 'base_template': 'list_fieldset.html'
} }
}) })
input_type = ClassLookupDict({ def render_field(self, field, parent_style):
serializers.Field: 'text', style = dict(self.default_style[field])
serializers.EmailField: 'email', style.update(field.style)
serializers.URLField: 'url', if 'template_pack' not in style:
serializers.IntegerField: 'number', style['template_pack'] = parent_style['template_pack']
serializers.DateTimeField: 'datetime-local', style['renderer'] = self
serializers.DateField: 'date',
serializers.TimeField: 'time',
})
def render_field(self, field, template_pack=None): if style.get('input_type') == 'datetime-local' and isinstance(field.value, six.text_type):
style_type = field.style.get('type', 'default')
input_type = self.input_type[field]
if input_type == 'datetime-local' and isinstance(field.value, six.text_type):
field.value = field.value.rstrip('Z') field.value = field.value.rstrip('Z')
base = self.field_templates[field][style_type] if 'template' in style:
template_name = template_pack + '/fields/' + base template_name = style['template']
else:
template_name = style['template_pack'].strip('/') + '/' + style['base_template']
template = loader.get_template(template_name) template = loader.get_template(template_name)
context = Context({ context = Context({'field': field, 'style': style})
'field': field,
'input_type': input_type,
'renderer': self,
})
return template.render(context) return template.render(context)
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.
""" """
form = data.serializer
meta = getattr(form, 'Meta', None)
style = getattr(meta, 'style', {})
if 'template_pack' not in style:
style['template_pack'] = self.template_pack
if 'base_template' not in style:
style['base_template'] = self.base_template
style['renderer'] = self
if 'template' in style:
template_name = style['template']
else:
template_name = style['template_pack'].strip('/') + '/' + style['base_template']
renderer_context = renderer_context or {} renderer_context = renderer_context or {}
request = renderer_context['request'] request = renderer_context['request']
template = loader.get_template('rest_framework/horizontal/form.html') template = loader.get_template(template_name)
context = RequestContext(request, { context = RequestContext(request, {
'form': data.serializer, 'form': form,
'template_pack': 'rest_framework/horizontal', 'style': style
'renderer': self
}) })
return template.render(context) return template.render(context)

View File

@ -115,7 +115,7 @@ class BaseSerializer(Field):
@property @property
def data(self): def data(self):
if not hasattr(self, '_data'): if not hasattr(self, '_data'):
if self.instance is not None: if self.instance is not None and not getattr(self, '_errors', None):
self._data = self.to_representation(self.instance) self._data = self.to_representation(self.instance)
else: else:
self._data = self.get_initial() self._data = self.get_initial()
@ -339,7 +339,7 @@ class Serializer(BaseSerializer):
Dict of native values <- Dict of primitive datatypes. Dict of native values <- Dict of primitive datatypes.
""" """
ret = {} ret = {}
errors = {} errors = ReturnDict(serializer=self)
fields = [field for field in self.fields.values() if not field.read_only] fields = [field for field in self.fields.values() if not field.read_only]
for field in fields: for field in fields:

View File

@ -115,10 +115,6 @@ html, body {
margin-bottom: 0; margin-bottom: 0;
} }
.well form .help-block {
color: #999999;
}
.nav-tabs { .nav-tabs {
border: 0; border: 0;
} }

View File

@ -36,6 +36,10 @@ ul.breadcrumb {
margin: 70px 0 0 0; margin: 70px 0 0 0;
} }
.breadcrumb li.active a {
color: #777;
}
form select, form input, form textarea { form select, form input, form textarea {
width: 90%; width: 90%;
} }

View File

@ -8,6 +8,7 @@ from django.utils.html import escape
from django.utils.safestring import SafeData, mark_safe from django.utils.safestring import SafeData, mark_safe
from django.utils.html import smart_urlquote from django.utils.html import smart_urlquote
from rest_framework.compat import urlparse, force_text from rest_framework.compat import urlparse, force_text
from rest_framework.renderers import HTMLFormRenderer
import re import re
register = template.Library() register = template.Library()
@ -32,8 +33,10 @@ class_re = re.compile(r'(?<=class=["\'])(.*)(?=["\'])')
# And the template tags themselves... # And the template tags themselves...
@register.simple_tag @register.simple_tag
def render_field(field, template_pack=None, renderer=None): def render_field(field, style=None):
return renderer.render_field(field, template_pack) style = style or {}
renderer = style.get('renderer', HTMLFormRenderer())
return renderer.render_field(field, style)
@register.simple_tag @register.simple_tag