From b4f3379c7002f0c80a26605fdd9c69d7cef2f16f Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 15 Oct 2014 15:13:28 +0100 Subject: [PATCH] Support fields that reference a simple callable --- rest_framework/fields.py | 4 +- rest_framework/renderers.py | 105 +++++++++++------- rest_framework/serializers.py | 4 +- .../rest_framework/css/bootstrap-tweaks.css | 4 - .../static/rest_framework/css/default.css | 4 + rest_framework/templatetags/rest_framework.py | 7 +- 6 files changed, 78 insertions(+), 50 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index b881ad13b..24dfaaf54 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -66,6 +66,8 @@ def get_attribute(instance, attrs): return instance[attr] except (KeyError, TypeError, AttributeError): raise exc + if is_simple_callable(instance): + return instance() return instance @@ -1025,8 +1027,6 @@ class ReadOnlyField(Field): super(ReadOnlyField, self).__init__(**kwargs) def to_representation(self, value): - if is_simple_callable(value): - return value() return value diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 5fae75f2e..d5defa318 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -340,76 +340,101 @@ class HTMLFormRenderer(BaseRenderer): media_type = 'text/html' format = 'form' charset = 'utf-8' + template_pack = 'rest_framework/horizontal/' + base_template = 'form.html' - field_templates = ClassLookupDict({ + default_style = ClassLookupDict({ 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: { - 'default': 'checkbox.html' - }, - serializers.CharField: { - 'default': 'input.html', - 'textarea': 'textarea.html' + 'base_template': 'checkbox.html' }, serializers.ChoiceField: { - 'default': 'select.html', - 'radio': 'select_radio.html' + 'base_template': 'select.html', # Also valid: 'radio.html' }, serializers.MultipleChoiceField: { - 'default': 'select_multiple.html', - 'checkbox': 'select_checkbox.html' + 'base_template': 'select_multiple.html', # Also valid: 'checkbox_multiple.html' }, serializers.ManyRelation: { - 'default': 'select_multiple.html', - 'checkbox': 'select_checkbox.html' + 'base_template': 'select_multiple.html', # Also valid: 'checkbox_multiple.html' }, serializers.Serializer: { - 'default': 'fieldset.html' + 'base_template': 'fieldset.html' }, serializers.ListSerializer: { - 'default': 'list_fieldset.html' + 'base_template': 'list_fieldset.html' } }) - input_type = ClassLookupDict({ - serializers.Field: 'text', - serializers.EmailField: 'email', - serializers.URLField: 'url', - serializers.IntegerField: 'number', - serializers.DateTimeField: 'datetime-local', - serializers.DateField: 'date', - serializers.TimeField: 'time', - }) + def render_field(self, field, parent_style): + style = dict(self.default_style[field]) + style.update(field.style) + if 'template_pack' not in style: + style['template_pack'] = parent_style['template_pack'] + style['renderer'] = self - def render_field(self, field, template_pack=None): - 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): + if style.get('input_type') == 'datetime-local' and isinstance(field.value, six.text_type): field.value = field.value.rstrip('Z') - base = self.field_templates[field][style_type] - template_name = template_pack + '/fields/' + base + if 'template' in style: + template_name = style['template'] + else: + template_name = style['template_pack'].strip('/') + '/' + style['base_template'] + template = loader.get_template(template_name) - context = Context({ - 'field': field, - 'input_type': input_type, - 'renderer': self, - }) + context = Context({'field': field, 'style': style}) return template.render(context) def render(self, data, accepted_media_type=None, renderer_context=None): """ 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 {} request = renderer_context['request'] - template = loader.get_template('rest_framework/horizontal/form.html') + template = loader.get_template(template_name) context = RequestContext(request, { - 'form': data.serializer, - 'template_pack': 'rest_framework/horizontal', - 'renderer': self + 'form': form, + 'style': style }) return template.render(context) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index c844605fc..534be6f90 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -115,7 +115,7 @@ class BaseSerializer(Field): @property def data(self): 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) else: self._data = self.get_initial() @@ -339,7 +339,7 @@ class Serializer(BaseSerializer): Dict of native values <- Dict of primitive datatypes. """ ret = {} - errors = {} + errors = ReturnDict(serializer=self) fields = [field for field in self.fields.values() if not field.read_only] for field in fields: diff --git a/rest_framework/static/rest_framework/css/bootstrap-tweaks.css b/rest_framework/static/rest_framework/css/bootstrap-tweaks.css index 6a37cae23..36c7be481 100644 --- a/rest_framework/static/rest_framework/css/bootstrap-tweaks.css +++ b/rest_framework/static/rest_framework/css/bootstrap-tweaks.css @@ -115,10 +115,6 @@ html, body { margin-bottom: 0; } -.well form .help-block { - color: #999999; -} - .nav-tabs { border: 0; } diff --git a/rest_framework/static/rest_framework/css/default.css b/rest_framework/static/rest_framework/css/default.css index 82c6033b6..4f52cc566 100644 --- a/rest_framework/static/rest_framework/css/default.css +++ b/rest_framework/static/rest_framework/css/default.css @@ -36,6 +36,10 @@ ul.breadcrumb { margin: 70px 0 0 0; } +.breadcrumb li.active a { + color: #777; +} + form select, form input, form textarea { width: 90%; } diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py index c092d39f6..d9424f022 100644 --- a/rest_framework/templatetags/rest_framework.py +++ b/rest_framework/templatetags/rest_framework.py @@ -8,6 +8,7 @@ from django.utils.html import escape from django.utils.safestring import SafeData, mark_safe from django.utils.html import smart_urlquote from rest_framework.compat import urlparse, force_text +from rest_framework.renderers import HTMLFormRenderer import re register = template.Library() @@ -32,8 +33,10 @@ class_re = re.compile(r'(?<=class=["\'])(.*)(?=["\'])') # And the template tags themselves... @register.simple_tag -def render_field(field, template_pack=None, renderer=None): - return renderer.render_field(field, template_pack) +def render_field(field, style=None): + style = style or {} + renderer = style.get('renderer', HTMLFormRenderer()) + return renderer.render_field(field, style) @register.simple_tag