diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 225f9fe8f..6483a47c0 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -13,17 +13,18 @@ import django from django import forms from django.core.exceptions import ImproperlyConfigured from django.http.multipartparser import parse_header -from django.template import RequestContext, loader, Template +from django.template import Context, RequestContext, loader, Template from django.test.client import encode_multipart from django.utils import six from django.utils.xmlutils import SimplerXMLGenerator +from rest_framework import exceptions, serializers, status, VERSION from rest_framework.compat import StringIO, smart_text, yaml from rest_framework.exceptions import ParseError from rest_framework.settings import api_settings from rest_framework.request import is_form_media_type, override_method from rest_framework.utils import encoders from rest_framework.utils.breadcrumbs import get_breadcrumbs -from rest_framework import exceptions, status, VERSION +from rest_framework.utils.field_mapping import ClassLookupDict def zero_as_none(value): @@ -341,6 +342,42 @@ class HTMLFormRenderer(BaseRenderer): template = 'rest_framework/form.html' charset = 'utf-8' + field_templates = ClassLookupDict({ + serializers.Field: { + 'default': 'input.html' + }, + serializers.BooleanField: { + 'default': 'checkbox.html' + }, + serializers.CharField: { + 'default': 'input.html', + 'textarea': 'textarea.html' + }, + serializers.ChoiceField: { + 'default': 'select.html', + 'radio': 'select_radio.html' + }, + serializers.MultipleChoiceField: { + 'default': 'select_multiple.html', + 'checkbox': 'select_checkbox.html' + } + }) + + def render_field(self, field, value, errors, layout=None): + layout = layout or 'vertical' + style_type = field.style.get('type', 'default') + if style_type == 'textarea' and layout == 'inline': + style_type = 'default' + base = self.field_templates[field][style_type] + template_name = 'rest_framework/fields/' + layout + '/' + base + template = loader.get_template(template_name) + context = Context({ + 'field': field, + 'value': value, + 'errors': errors + }) + 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. @@ -349,7 +386,11 @@ class HTMLFormRenderer(BaseRenderer): request = renderer_context['request'] template = loader.get_template(self.template) - context = RequestContext(request, {'form': data}) + context = RequestContext(request, { + 'form': data, + 'layout': getattr(getattr(data, 'Meta', None), 'layout', 'vertical'), + 'renderer': self + }) return template.render(context) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 0faa56718..5da812477 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -302,6 +302,8 @@ class Serializer(BaseSerializer): def __iter__(self): errors = self.errors if hasattr(self, '_errors') else {} for field in self.fields.values(): + if field.read_only: + continue value = self.data.get(field.field_name) if self.data else None error = errors.get(field.field_name) yield FieldResult(field, value, error) diff --git a/rest_framework/templates/rest_framework/fields/attrs.html b/rest_framework/templates/rest_framework/fields/attrs.html new file mode 100644 index 000000000..b5a4dbcf1 --- /dev/null +++ b/rest_framework/templates/rest_framework/fields/attrs.html @@ -0,0 +1 @@ +name="{{ field.field_name }}" {% if field.style.placeholder %}placeholder="{{ field.style.placeholder }}"{% endif %} {% if field.style.rows %}rows="{{ field.style.rows }}"{% endif %} diff --git a/rest_framework/templates/rest_framework/fields/horizontal/checkbox.html b/rest_framework/templates/rest_framework/fields/horizontal/checkbox.html new file mode 100644 index 000000000..dce4a5cf9 --- /dev/null +++ b/rest_framework/templates/rest_framework/fields/horizontal/checkbox.html @@ -0,0 +1,10 @@ +
+
+
+ +
+
+
diff --git a/rest_framework/templates/rest_framework/fields/horizontal/fieldset.html b/rest_framework/templates/rest_framework/fields/horizontal/fieldset.html new file mode 100644 index 000000000..86417633f --- /dev/null +++ b/rest_framework/templates/rest_framework/fields/horizontal/fieldset.html @@ -0,0 +1,10 @@ +
+ {% if field.label %} +
+ {{ field.label }} +
+ {% endif %} + {% for field_item in value.field_items.values() %} + {{ renderer.render_field(field_item, layout=layout) }} + {% endfor %} +
diff --git a/rest_framework/templates/rest_framework/fields/horizontal/input.html b/rest_framework/templates/rest_framework/fields/horizontal/input.html new file mode 100644 index 000000000..310154bb4 --- /dev/null +++ b/rest_framework/templates/rest_framework/fields/horizontal/input.html @@ -0,0 +1,7 @@ +
+ {% include "rest_framework/fields/horizontal/label.html" %} +
+ + {% if field.help_text %}

{{ field.help_text }}

{% endif %} +
+
diff --git a/rest_framework/templates/rest_framework/fields/horizontal/label.html b/rest_framework/templates/rest_framework/fields/horizontal/label.html new file mode 100644 index 000000000..bf21f78cc --- /dev/null +++ b/rest_framework/templates/rest_framework/fields/horizontal/label.html @@ -0,0 +1 @@ +{% if field.label %}{% endif %} diff --git a/rest_framework/templates/rest_framework/fields/horizontal/select.html b/rest_framework/templates/rest_framework/fields/horizontal/select.html new file mode 100644 index 000000000..3f8cab0a3 --- /dev/null +++ b/rest_framework/templates/rest_framework/fields/horizontal/select.html @@ -0,0 +1,10 @@ +
+ {% include "rest_framework/fields/horizontal/label.html" %} +
+ +
+
diff --git a/rest_framework/templates/rest_framework/fields/horizontal/select_checkbox.html b/rest_framework/templates/rest_framework/fields/horizontal/select_checkbox.html new file mode 100644 index 000000000..659eede84 --- /dev/null +++ b/rest_framework/templates/rest_framework/fields/horizontal/select_checkbox.html @@ -0,0 +1,22 @@ +
+ {% include "rest_framework/fields/horizontal/label.html" %} +
+ {% if field.style.inline %} + {% for key, text in field.choices.items %} + + {% endfor %} + {% else %} + {% for key, text in field.choices.items %} +
+ +
+ {% endfor %} + {% endif %} +
+
diff --git a/rest_framework/templates/rest_framework/fields/horizontal/select_multiple.html b/rest_framework/templates/rest_framework/fields/horizontal/select_multiple.html new file mode 100644 index 000000000..da25eb2bd --- /dev/null +++ b/rest_framework/templates/rest_framework/fields/horizontal/select_multiple.html @@ -0,0 +1,10 @@ +
+ {% include "rest_framework/fields/horizontal/label.html" %} +
+ +
+
diff --git a/rest_framework/templates/rest_framework/fields/horizontal/select_radio.html b/rest_framework/templates/rest_framework/fields/horizontal/select_radio.html new file mode 100644 index 000000000..188f05e27 --- /dev/null +++ b/rest_framework/templates/rest_framework/fields/horizontal/select_radio.html @@ -0,0 +1,22 @@ +
+ {% include "rest_framework/fields/horizontal/label.html" %} +
+ {% if field.style.inline %} + {% for key, text in field.choices.items %} + + {% endfor %} + {% else %} + {% for key, text in field.choices.items %} +
+ +
+ {% endfor %} + {% endif %} +
+
diff --git a/rest_framework/templates/rest_framework/fields/horizontal/textarea.html b/rest_framework/templates/rest_framework/fields/horizontal/textarea.html new file mode 100644 index 000000000..e99266f37 --- /dev/null +++ b/rest_framework/templates/rest_framework/fields/horizontal/textarea.html @@ -0,0 +1,7 @@ +
+ {% include "rest_framework/fields/horizontal/label.html" %} +
+ + {% if field.help_text %}

{{ field.help_text }}

{% endif %} +
+
diff --git a/rest_framework/templates/rest_framework/fields/inline/checkbox.html b/rest_framework/templates/rest_framework/fields/inline/checkbox.html new file mode 100644 index 000000000..01d30aaef --- /dev/null +++ b/rest_framework/templates/rest_framework/fields/inline/checkbox.html @@ -0,0 +1,6 @@ +
+ +
diff --git a/rest_framework/templates/rest_framework/fields/inline/fieldset.html b/rest_framework/templates/rest_framework/fields/inline/fieldset.html new file mode 100644 index 000000000..d22982fdd --- /dev/null +++ b/rest_framework/templates/rest_framework/fields/inline/fieldset.html @@ -0,0 +1,3 @@ +{% for field_item in value.field_items.values() %} + {{ renderer.render_field(field_item, layout=layout) }} +{% endfor %} diff --git a/rest_framework/templates/rest_framework/fields/inline/input.html b/rest_framework/templates/rest_framework/fields/inline/input.html new file mode 100644 index 000000000..aefd16723 --- /dev/null +++ b/rest_framework/templates/rest_framework/fields/inline/input.html @@ -0,0 +1,4 @@ +
+ {% include "rest_framework/fields/inline/label.html" %} + +
diff --git a/rest_framework/templates/rest_framework/fields/inline/label.html b/rest_framework/templates/rest_framework/fields/inline/label.html new file mode 100644 index 000000000..7d546a571 --- /dev/null +++ b/rest_framework/templates/rest_framework/fields/inline/label.html @@ -0,0 +1 @@ +{% if field.label %}{% endif %} diff --git a/rest_framework/templates/rest_framework/fields/inline/select.html b/rest_framework/templates/rest_framework/fields/inline/select.html new file mode 100644 index 000000000..cb9a70137 --- /dev/null +++ b/rest_framework/templates/rest_framework/fields/inline/select.html @@ -0,0 +1,8 @@ +
+ {% include "rest_framework/fields/inline/label.html" %} + +
diff --git a/rest_framework/templates/rest_framework/fields/inline/select_checkbox.html b/rest_framework/templates/rest_framework/fields/inline/select_checkbox.html new file mode 100644 index 000000000..424df93e6 --- /dev/null +++ b/rest_framework/templates/rest_framework/fields/inline/select_checkbox.html @@ -0,0 +1,11 @@ +
+ {% include "rest_framework/fields/inline/label.html" %} + {% for key, text in field.choices.items %} +
+ +
+ {% endfor %} +
diff --git a/rest_framework/templates/rest_framework/fields/inline/select_multiple.html b/rest_framework/templates/rest_framework/fields/inline/select_multiple.html new file mode 100644 index 000000000..6fdfd672f --- /dev/null +++ b/rest_framework/templates/rest_framework/fields/inline/select_multiple.html @@ -0,0 +1,8 @@ +
+ {% include "rest_framework/fields/inline/label.html" %} + +
diff --git a/rest_framework/templates/rest_framework/fields/inline/select_radio.html b/rest_framework/templates/rest_framework/fields/inline/select_radio.html new file mode 100644 index 000000000..ddabc9e96 --- /dev/null +++ b/rest_framework/templates/rest_framework/fields/inline/select_radio.html @@ -0,0 +1,11 @@ +
+ {% include "rest_framework/fields/inline/label.html" %} + {% for key, text in field.choices.items %} +
+ +
+ {% endfor %} +
diff --git a/rest_framework/templates/rest_framework/fields/inline/textarea.html b/rest_framework/templates/rest_framework/fields/inline/textarea.html new file mode 100644 index 000000000..313668098 --- /dev/null +++ b/rest_framework/templates/rest_framework/fields/inline/textarea.html @@ -0,0 +1,4 @@ +
+ {% include "rest_framework/fields/inline/label.html" %} + +
diff --git a/rest_framework/templates/rest_framework/fields/vertical/checkbox.html b/rest_framework/templates/rest_framework/fields/vertical/checkbox.html new file mode 100644 index 000000000..01d30aaef --- /dev/null +++ b/rest_framework/templates/rest_framework/fields/vertical/checkbox.html @@ -0,0 +1,6 @@ +
+ +
diff --git a/rest_framework/templates/rest_framework/fields/vertical/fieldset.html b/rest_framework/templates/rest_framework/fields/vertical/fieldset.html new file mode 100644 index 000000000..cad32df99 --- /dev/null +++ b/rest_framework/templates/rest_framework/fields/vertical/fieldset.html @@ -0,0 +1,6 @@ +
+ {% if field.label %}{{ field.label }}{% endif %} + {% for field_item in value.field_items.values() %} + {{ renderer.render_field(field_item, layout=layout) }} + {% endfor %} +
diff --git a/rest_framework/templates/rest_framework/fields/vertical/input.html b/rest_framework/templates/rest_framework/fields/vertical/input.html new file mode 100644 index 000000000..c25407d16 --- /dev/null +++ b/rest_framework/templates/rest_framework/fields/vertical/input.html @@ -0,0 +1,5 @@ +
+ {% include "rest_framework/fields/vertical/label.html" %} + + {% if field.help_text %}

{{ field.help_text }}

{% endif %} +
diff --git a/rest_framework/templates/rest_framework/fields/vertical/label.html b/rest_framework/templates/rest_framework/fields/vertical/label.html new file mode 100644 index 000000000..651939b26 --- /dev/null +++ b/rest_framework/templates/rest_framework/fields/vertical/label.html @@ -0,0 +1 @@ +{% if field.label %}{% endif %} diff --git a/rest_framework/templates/rest_framework/fields/vertical/select.html b/rest_framework/templates/rest_framework/fields/vertical/select.html new file mode 100644 index 000000000..44679d8a4 --- /dev/null +++ b/rest_framework/templates/rest_framework/fields/vertical/select.html @@ -0,0 +1,8 @@ +
+ {% include "rest_framework/fields/vertical/label.html" %} + +
diff --git a/rest_framework/templates/rest_framework/fields/vertical/select_checkbox.html b/rest_framework/templates/rest_framework/fields/vertical/select_checkbox.html new file mode 100644 index 000000000..e60574c07 --- /dev/null +++ b/rest_framework/templates/rest_framework/fields/vertical/select_checkbox.html @@ -0,0 +1,22 @@ +
+ {% include "rest_framework/fields/vertical/label.html" %} + {% if field.style.inline %} +
+ {% for key, text in field.choices.items %} + + {% endfor %} +
+ {% else %} + {% for key, text in field.choices.items %} +
+ +
+ {% endfor %} + {% endif %} +
diff --git a/rest_framework/templates/rest_framework/fields/vertical/select_multiple.html b/rest_framework/templates/rest_framework/fields/vertical/select_multiple.html new file mode 100644 index 000000000..f0fa418b5 --- /dev/null +++ b/rest_framework/templates/rest_framework/fields/vertical/select_multiple.html @@ -0,0 +1,8 @@ +
+ {% include "rest_framework/fields/vertical/label.html" %} + +
diff --git a/rest_framework/templates/rest_framework/fields/vertical/select_radio.html b/rest_framework/templates/rest_framework/fields/vertical/select_radio.html new file mode 100644 index 000000000..4ffe38ea9 --- /dev/null +++ b/rest_framework/templates/rest_framework/fields/vertical/select_radio.html @@ -0,0 +1,22 @@ +
+ {% include "rest_framework/fields/vertical/label.html" %} + {% if field.style.inline %} +
+ {% for key, text in field.choices.items %} + + {% endfor %} +
+ {% else %} + {% for key, text in field.choices.items %} +
+ +
+ {% endfor %} + {% endif %} +
diff --git a/rest_framework/templates/rest_framework/fields/vertical/textarea.html b/rest_framework/templates/rest_framework/fields/vertical/textarea.html new file mode 100644 index 000000000..33cb27c75 --- /dev/null +++ b/rest_framework/templates/rest_framework/fields/vertical/textarea.html @@ -0,0 +1,5 @@ +
+ {% include "rest_framework/fields/vertical/label.html" %} + + {% if field.help_text %}

{{ field.help_text }}

{% endif %} +
diff --git a/rest_framework/templates/rest_framework/form.html b/rest_framework/templates/rest_framework/form.html index b1e148df7..64b1b0bc5 100644 --- a/rest_framework/templates/rest_framework/form.html +++ b/rest_framework/templates/rest_framework/form.html @@ -1,15 +1,31 @@ + + + + + +
+ +

User update

+
+ {% load rest_framework %} -{% csrf_token %} -{{ form.non_field_errors }} -{% for field in form.fields.values %} - {% if not field.read_only %} -
- {{ field.label_tag|add_class:"control-label" }} -
- {{ field.widget_html }} - {% if field.help_text %}{{ field.help_text }}{% endif %} - {% for error in field.errors %}{{ error }}{% endfor %} +
+ {% csrf_token %} + {% for field, value, errors in form %} + {% render_field field value errors layout=layout renderer=renderer %} + {% endfor %} + + {% if layout == "horizontal" %} +
+
+ +
-
+ {% else %} + {% endif %} -{% endfor %} + + +
+
+ diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py index 864d64dd0..88ff9d4ed 100644 --- a/rest_framework/templatetags/rest_framework.py +++ b/rest_framework/templatetags/rest_framework.py @@ -31,6 +31,14 @@ class_re = re.compile(r'(?<=class=["\'])(.*)(?=["\'])') # And the template tags themselves... +# @register.simple_tag +# def render_field(field, value, errors, renderer): +# return renderer.render_field(field, value, errors) +@register.simple_tag +def render_field(field, value, errors, layout=None, renderer=None): + return renderer.render_field(field, value, errors, layout) + + @register.simple_tag def optional_login(request): """ diff --git a/rest_framework/utils/field_mapping.py b/rest_framework/utils/field_mapping.py index cf9d910aa..b4d33e395 100644 --- a/rest_framework/utils/field_mapping.py +++ b/rest_framework/utils/field_mapping.py @@ -79,6 +79,9 @@ def get_field_kwargs(field_name, model_field): kwargs['choices'] = model_field.flatchoices return kwargs + if isinstance(model_field, models.TextField): + kwargs['style'] = {'type': 'textarea'} + if model_field.null and not isinstance(model_field, models.NullBooleanField): kwargs['allow_null'] = True diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py index f74750247..2edf0be5b 100644 --- a/tests/test_model_serializer.py +++ b/tests/test_model_serializer.py @@ -95,7 +95,7 @@ class TestRegularFieldMappings(TestCase): positive_small_integer_field = IntegerField() slug_field = SlugField(max_length=100) small_integer_field = IntegerField() - text_field = CharField() + text_field = CharField(style={'type': 'textarea'}) time_field = TimeField() url_field = URLField(max_length=100) custom_field = ModelField(model_field=)