First pass on incorperating the form rendering into the browsable API

This commit is contained in:
Tom Christie 2014-10-02 16:24:24 +01:00
parent ffc6aa3abc
commit df7b6fcf58
32 changed files with 225 additions and 152 deletions

View File

@ -689,10 +689,10 @@ class DateTimeField(Field):
return value return value
if self.format.lower() == ISO_8601: if self.format.lower() == ISO_8601:
ret = value.isoformat() value = value.isoformat()
if ret.endswith('+00:00'): if value.endswith('+00:00'):
ret = ret[:-6] + 'Z' value = value[:-6] + 'Z'
return ret return value
return value.strftime(self.format) return value.strftime(self.format)

View File

@ -127,7 +127,7 @@ class HyperlinkedRelatedField(RelatedField):
attributes are not configured to correctly match the URL conf. attributes are not configured to correctly match the URL conf.
""" """
# Unsaved objects will not yet have a valid URL. # Unsaved objects will not yet have a valid URL.
if obj.pk is None: if obj.pk:
return None return None
lookup_value = getattr(obj, self.lookup_field) lookup_value = getattr(obj, self.lookup_field)
@ -248,11 +248,13 @@ class ManyRelation(Field):
You shouldn't need to be using this class directly yourself. You shouldn't need to be using this class directly yourself.
""" """
initial = []
def __init__(self, child_relation=None, *args, **kwargs): def __init__(self, child_relation=None, *args, **kwargs):
self.child_relation = child_relation self.child_relation = child_relation
assert child_relation is not None, '`child_relation` is a required argument.' assert child_relation is not None, '`child_relation` is a required argument.'
super(ManyRelation, self).__init__(*args, **kwargs) super(ManyRelation, self).__init__(*args, **kwargs)
self.child_relation.bind(field_name='', parent=self)
def to_internal_value(self, data): def to_internal_value(self, data):
return [ return [

View File

@ -377,23 +377,21 @@ class HTMLFormRenderer(BaseRenderer):
serializers.TimeField: 'time', serializers.TimeField: 'time',
}) })
def render_field(self, field, value, errors, layout=None): def render_field(self, field, layout=None):
layout = layout or 'vertical' layout = layout or 'vertical'
style_type = field.style.get('type', 'default') style_type = field.style.get('type', 'default')
if style_type == 'textarea' and layout == 'inline': if style_type == 'textarea' and layout == 'inline':
style_type = 'default' style_type = 'default'
input_type = self.input_type[field] input_type = self.input_type[field]
if input_type == 'datetime-local': if input_type == 'datetime-local' and isinstance(field.value, six.text_type):
value = value.rstrip('Z') field.value = field.value.rstrip('Z')
base = self.field_templates[field][style_type] base = self.field_templates[field][style_type]
template_name = 'rest_framework/fields/' + layout + '/' + base template_name = 'rest_framework/fields/' + layout + '/' + base
template = loader.get_template(template_name) template = loader.get_template(template_name)
context = Context({ context = Context({
'field': field, 'field': field,
'value': value,
'errors': errors,
'input_type': input_type 'input_type': input_type
}) })
@ -408,7 +406,7 @@ class HTMLFormRenderer(BaseRenderer):
template = loader.get_template(self.template) template = loader.get_template(self.template)
context = RequestContext(request, { context = RequestContext(request, {
'form': data, 'form': data.serializer,
'layout': getattr(getattr(data, 'Meta', None), 'layout', 'horizontal'), 'layout': getattr(getattr(data, 'Meta', None), 'layout', 'horizontal'),
'renderer': self 'renderer': self
}) })
@ -479,27 +477,29 @@ class BrowsableAPIRenderer(BaseRenderer):
return False # Doesn't have permissions return False # Doesn't have permissions
return True return True
def get_rendered_html_form(self, view, method, request): def get_rendered_html_form(self, data, view, method, request):
""" """
Return a string representing a rendered HTML form, possibly bound to Return a string representing a rendered HTML form, possibly bound to
either the input or output data. either the input or output data.
In the absence of the View having an associated form then return None. In the absence of the View having an associated form then return None.
""" """
serializer = getattr(data, 'serializer', None)
if serializer and not getattr(serializer, 'many', False):
instance = getattr(serializer, 'instance', None)
else:
instance = None
if request.method == method: if request.method == method:
try: try:
data = request.data data = request.data
# files = request.FILES
except ParseError: except ParseError:
data = None data = None
# files = None
else: else:
data = None data = None
# files = None
with override_method(view, request, method) as request: with override_method(view, request, method) as request:
obj = getattr(view, 'object', None) if not self.show_form_for_method(view, method, request, instance):
if not self.show_form_for_method(view, method, request, obj):
return return
if method in ('DELETE', 'OPTIONS'): if method in ('DELETE', 'OPTIONS'):
@ -511,19 +511,24 @@ class BrowsableAPIRenderer(BaseRenderer):
): ):
return return
serializer = view.get_serializer(instance=obj, data=data) serializer = view.get_serializer(instance=instance, data=data)
serializer.is_valid() if data is not None:
data = serializer.data serializer.is_valid()
form_renderer = self.form_renderer_class() form_renderer = self.form_renderer_class()
return form_renderer.render(data, self.accepted_media_type, self.renderer_context) return form_renderer.render(serializer.data, self.accepted_media_type, self.renderer_context)
def get_raw_data_form(self, view, method, request): def get_raw_data_form(self, data, view, method, request):
""" """
Returns a form that allows for arbitrary content types to be tunneled Returns a form that allows for arbitrary content types to be tunneled
via standard HTML forms. via standard HTML forms.
(Which are typically application/x-www-form-urlencoded) (Which are typically application/x-www-form-urlencoded)
""" """
serializer = getattr(data, 'serializer', None)
if serializer and not getattr(serializer, 'many', False):
instance = getattr(serializer, 'instance', None)
else:
instance = None
with override_method(view, request, method) as request: with override_method(view, request, method) as request:
# If we're not using content overloading there's no point in # If we're not using content overloading there's no point in
# supplying a generic form, as the view won't treat the form's # supplying a generic form, as the view won't treat the form's
@ -533,8 +538,7 @@ class BrowsableAPIRenderer(BaseRenderer):
return None return None
# Check permissions # Check permissions
obj = getattr(view, 'object', None) if not self.show_form_for_method(view, method, request, instance):
if not self.show_form_for_method(view, method, request, obj):
return return
# If possible, serialize the initial content for the generic form # If possible, serialize the initial content for the generic form
@ -545,8 +549,8 @@ class BrowsableAPIRenderer(BaseRenderer):
# corresponding renderer that can be used to render the data. # corresponding renderer that can be used to render the data.
# Get a read-only version of the serializer # Get a read-only version of the serializer
serializer = view.get_serializer(instance=obj) serializer = view.get_serializer(instance=instance)
if obj is None: if instance is None:
for name, field in serializer.fields.items(): for name, field in serializer.fields.items():
if getattr(field, 'read_only', None): if getattr(field, 'read_only', None):
del serializer.fields[name] del serializer.fields[name]
@ -606,9 +610,9 @@ class BrowsableAPIRenderer(BaseRenderer):
renderer = self.get_default_renderer(view) renderer = self.get_default_renderer(view)
raw_data_post_form = self.get_raw_data_form(view, 'POST', request) raw_data_post_form = self.get_raw_data_form(data, view, 'POST', request)
raw_data_put_form = self.get_raw_data_form(view, 'PUT', request) raw_data_put_form = self.get_raw_data_form(data, view, 'PUT', request)
raw_data_patch_form = self.get_raw_data_form(view, 'PATCH', request) raw_data_patch_form = self.get_raw_data_form(data, view, 'PATCH', request)
raw_data_put_or_patch_form = raw_data_put_form or raw_data_patch_form raw_data_put_or_patch_form = raw_data_put_form or raw_data_patch_form
response_headers = dict(response.items()) response_headers = dict(response.items())
@ -632,10 +636,10 @@ class BrowsableAPIRenderer(BaseRenderer):
'available_formats': [renderer_cls.format for renderer_cls in view.renderer_classes], 'available_formats': [renderer_cls.format for renderer_cls in view.renderer_classes],
'response_headers': response_headers, 'response_headers': response_headers,
# 'put_form': self.get_rendered_html_form(view, 'PUT', request), 'put_form': self.get_rendered_html_form(data, view, 'PUT', request),
# 'post_form': self.get_rendered_html_form(view, 'POST', request), 'post_form': self.get_rendered_html_form(data, view, 'POST', request),
# 'delete_form': self.get_rendered_html_form(view, 'DELETE', request), 'delete_form': self.get_rendered_html_form(data, view, 'DELETE', request),
# 'options_form': self.get_rendered_html_form(view, 'OPTIONS', request), 'options_form': self.get_rendered_html_form(data, view, 'OPTIONS', request),
'raw_data_put_form': raw_data_put_form, 'raw_data_put_form': raw_data_put_form,
'raw_data_post_form': raw_data_post_form, 'raw_data_post_form': raw_data_post_form,

View File

@ -14,7 +14,6 @@ from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.db import models from django.db import models
from django.utils import six from django.utils import six
from django.utils.datastructures import SortedDict from django.utils.datastructures import SortedDict
from collections import namedtuple
from rest_framework.fields import empty, set_value, Field, SkipField from rest_framework.fields import empty, set_value, Field, SkipField
from rest_framework.settings import api_settings from rest_framework.settings import api_settings
from rest_framework.utils import html, model_meta, representation from rest_framework.utils import html, model_meta, representation
@ -38,8 +37,8 @@ from rest_framework.relations import * # NOQA
from rest_framework.fields import * # NOQA from rest_framework.fields import * # NOQA
FieldResult = namedtuple('FieldResult', ['field', 'value', 'error']) # BaseSerializer
# --------------
class BaseSerializer(Field): class BaseSerializer(Field):
""" """
@ -113,11 +112,6 @@ class BaseSerializer(Field):
if not hasattr(self, '_data'): if not hasattr(self, '_data'):
if self.instance is not None: if self.instance is not None:
self._data = self.to_representation(self.instance) self._data = self.to_representation(self.instance)
elif self._initial_data is not None:
self._data = dict([
(field_name, field.get_value(self._initial_data))
for field_name, field in self.fields.items()
])
else: else:
self._data = self.get_initial() self._data = self.get_initial()
return self._data return self._data
@ -137,6 +131,79 @@ class BaseSerializer(Field):
return self._validated_data return self._validated_data
# Serializer & ListSerializer classes
# -----------------------------------
class ReturnDict(SortedDict):
"""
Return object from `serialier.data` for the `Serializer` class.
Includes a backlink to the serializer instance for renderers
to use if they need richer field information.
"""
def __init__(self, *args, **kwargs):
self.serializer = kwargs.pop('serializer')
super(ReturnDict, self).__init__(*args, **kwargs)
class ReturnList(list):
"""
Return object from `serialier.data` for the `SerializerList` class.
Includes a backlink to the serializer instance for renderers
to use if they need richer field information.
"""
def __init__(self, *args, **kwargs):
self.serializer = kwargs.pop('serializer')
super(ReturnList, self).__init__(*args, **kwargs)
class BoundField(object):
"""
A field object that also includes `.value` and `.error` properties.
Returned when iterating over a serializer instance,
providing an API similar to Django forms and form fields.
"""
def __init__(self, field, value, errors):
self._field = field
self.value = value
self.errors = errors
def __getattr__(self, attr_name):
return getattr(self._field, attr_name)
@property
def _proxy_class(self):
return self._field.__class__
class BindingDict(object):
"""
This dict-like object is used to store fields on a serializer.
This ensures that whenever fields are added to the serializer we call
`field.bind()` so that the `field_name` and `parent` attributes
can be set correctly.
"""
def __init__(self, serializer):
self.serializer = serializer
self.fields = SortedDict()
def __setitem__(self, key, field):
self.fields[key] = field
field.bind(field_name=key, parent=self.serializer)
def __getitem__(self, key):
return self.fields[key]
def __delitem__(self, key):
del self.fields[key]
def items(self):
return self.fields.items()
def values(self):
return self.fields.values()
class SerializerMetaclass(type): class SerializerMetaclass(type):
""" """
This metaclass sets a dictionary named `base_fields` on the class. This metaclass sets a dictionary named `base_fields` on the class.
@ -167,35 +234,6 @@ class SerializerMetaclass(type):
return super(SerializerMetaclass, cls).__new__(cls, name, bases, attrs) return super(SerializerMetaclass, cls).__new__(cls, name, bases, attrs)
class BindingDict(object):
"""
This dict-like object is used to store fields on a serializer.
This ensures that whenever fields are added to the serializer we call
`field.bind()` so that the `field_name` and `parent` attributes
can be set correctly.
"""
def __init__(self, serializer):
self.serializer = serializer
self.fields = SortedDict()
def __setitem__(self, key, field):
self.fields[key] = field
field.bind(field_name=key, parent=self.serializer)
def __getitem__(self, key):
return self.fields[key]
def __delitem__(self, key):
del self.fields[key]
def items(self):
return self.fields.items()
def values(self):
return self.fields.values()
@six.add_metaclass(SerializerMetaclass) @six.add_metaclass(SerializerMetaclass)
class Serializer(BaseSerializer): class Serializer(BaseSerializer):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -212,10 +250,18 @@ class Serializer(BaseSerializer):
return copy.deepcopy(self._declared_fields) return copy.deepcopy(self._declared_fields)
def get_initial(self): def get_initial(self):
return dict([ if self._initial_data is not None:
return ReturnDict([
(field_name, field.get_value(self._initial_data))
for field_name, field in self.fields.items()
], serializer=self)
#return self.to_representation(self._initial_data)
return ReturnDict([
(field.field_name, field.get_initial()) (field.field_name, field.get_initial())
for field in self.fields.values() for field in self.fields.values()
]) if not field.write_only
], serializer=self)
def get_value(self, dictionary): def get_value(self, dictionary):
# We override the default field access in order to support # We override the default field access in order to support
@ -288,7 +334,7 @@ class Serializer(BaseSerializer):
""" """
Object instance -> Dict of primitive datatypes. Object instance -> Dict of primitive datatypes.
""" """
ret = SortedDict() ret = ReturnDict(serializer=self)
fields = [field for field in self.fields.values() if not field.write_only] fields = [field for field in self.fields.values() if not field.write_only]
for field in fields: for field in fields:
@ -302,11 +348,9 @@ class Serializer(BaseSerializer):
def __iter__(self): def __iter__(self):
errors = self.errors if hasattr(self, '_errors') else {} errors = self.errors if hasattr(self, '_errors') else {}
for field in self.fields.values(): for field in self.fields.values():
if field.read_only:
continue
value = self.data.get(field.field_name) if self.data else None value = self.data.get(field.field_name) if self.data else None
error = errors.get(field.field_name) error = errors.get(field.field_name)
yield FieldResult(field, value, error) yield BoundField(field, value, error)
def __repr__(self): def __repr__(self):
return representation.serializer_repr(self, indent=1) return representation.serializer_repr(self, indent=1)
@ -317,7 +361,7 @@ class Serializer(BaseSerializer):
class ListSerializer(BaseSerializer): class ListSerializer(BaseSerializer):
child = None child = None
initial = [] many = True
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.child = kwargs.pop('child', copy.deepcopy(self.child)) self.child = kwargs.pop('child', copy.deepcopy(self.child))
@ -326,6 +370,11 @@ class ListSerializer(BaseSerializer):
super(ListSerializer, self).__init__(*args, **kwargs) super(ListSerializer, self).__init__(*args, **kwargs)
self.child.bind(field_name='', parent=self) self.child.bind(field_name='', parent=self)
def get_initial(self):
if self._initial_data is not None:
return self.to_representation(self._initial_data)
return ReturnList(serializer=self)
def get_value(self, dictionary): def get_value(self, dictionary):
# We override the default field access in order to support # We override the default field access in order to support
# lists in HTML forms. # lists in HTML forms.
@ -345,7 +394,10 @@ class ListSerializer(BaseSerializer):
""" """
List of object instances -> List of dicts of primitive datatypes. List of object instances -> List of dicts of primitive datatypes.
""" """
return [self.child.to_representation(item) for item in data] return ReturnList(
[self.child.to_representation(item) for item in data],
serializer=self
)
def create(self, attrs_list): def create(self, attrs_list):
return [self.child.create(attrs) for attrs in attrs_list] return [self.child.create(attrs) for attrs in attrs_list]
@ -354,6 +406,9 @@ class ListSerializer(BaseSerializer):
return representation.list_repr(self, indent=1) return representation.list_repr(self, indent=1)
# ModelSerializer & HyperlinkedModelSerializer
# --------------------------------------------
class ModelSerializer(Serializer): class ModelSerializer(Serializer):
_field_mapping = ClassLookupDict({ _field_mapping = ClassLookupDict({
models.AutoField: IntegerField, models.AutoField: IntegerField,

View File

@ -10,6 +10,12 @@ a single block in the template.
background: transparent; background: transparent;
border-top-color: transparent; border-top-color: transparent;
padding-top: 0; padding-top: 0;
text-align: right;
}
#generic-content-form textarea {
font-family:Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace;
font-size: 80%;
} }
.navbar-inverse .brand a { .navbar-inverse .brand a {
@ -29,7 +35,7 @@ a single block in the template.
z-index: 3; z-index: 3;
} }
.navbar .navbar-inner { .navbar {
background: #2C2C2C; background: #2C2C2C;
color: white; color: white;
border: none; border: none;
@ -37,7 +43,7 @@ a single block in the template.
border-radius: 0px; border-radius: 0px;
} }
.navbar .navbar-inner .nav li, .navbar .navbar-inner .nav li a, .navbar .navbar-inner .brand:hover { .navbar .nav li, .navbar .nav li a, .navbar .brand:hover {
color: white; color: white;
} }
@ -45,11 +51,11 @@ a single block in the template.
background: #2C2C2C; background: #2C2C2C;
} }
.navbar .navbar-inner .dropdown-menu li a, .navbar .navbar-inner .dropdown-menu li { .navbar .dropdown-menu li a, .navbar .dropdown-menu li {
color: #A30000; color: #A30000;
} }
.navbar .navbar-inner .dropdown-menu li a:hover { .navbar .dropdown-menu li a:hover {
background: #EEEEEE; background: #EEEEEE;
color: #C20000; color: #C20000;
} }
@ -61,10 +67,10 @@ html {
background: none; background: none;
} }
body, .navbar .navbar-inner .container-fluid { /*body, .navbar .container-fluid {
max-width: 1150px; max-width: 1150px;
margin: 0 auto; margin: 0 auto;
} }*/
body { body {
background: url("../img/grid.png") repeat-x; background: url("../img/grid.png") repeat-x;

View File

@ -15,7 +15,8 @@
{% block style %} {% block style %}
{% block bootstrap_theme %} {% block bootstrap_theme %}
<link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/bootstrap.min.css" %}"/> <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">
<!--<link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/bootstrap.min.css" %}"/>-->
<link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/bootstrap-tweaks.css" %}"/> <link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/bootstrap-tweaks.css" %}"/>
{% endblock %} {% endblock %}
<link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/prettify.css" %}"/> <link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/prettify.css" %}"/>
@ -26,44 +27,42 @@
</head> </head>
{% block body %} {% block body %}
<body class="{% block bodyclass %}{% endblock %} container"> <body class="{% block bodyclass %}{% endblock %}">
<div class="wrapper"> <div class="wrapper">
{% block navbar %} {% block navbar %}
<div class="navbar {% block bootstrap_navbar_variant %}navbar-inverse{% endblock %}"> <div class="navbar navbar-static-top {% block bootstrap_navbar_variant %}navbar-inverse{% endblock %}">
<div class="navbar-inner"> <div class="container">
<div class="container-fluid"> <span>
<span> {% block branding %}
{% block branding %} <a class='navbar-brand' rel="nofollow" href='http://www.django-rest-framework.org'>
<a class='brand' rel="nofollow" href='http://www.django-rest-framework.org'> Django REST framework <span class="version">{{ version }}</span>
Django REST framework <span class="version">{{ version }}</span> </a>
</a> {% endblock %}
{% endblock %} </span>
</span> <ul class="nav navbar-nav pull-right">
<ul class="nav pull-right"> {% block userlinks %}
{% block userlinks %} {% if user.is_authenticated %}
{% if user.is_authenticated %} {% optional_logout request user %}
{% optional_logout request user %} {% else %}
{% else %} {% optional_login request %}
{% optional_login request %} {% endif %}
{% endif %} {% endblock %}
{% endblock %} </ul>
</ul>
</div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
<div class="container">
{% block breadcrumbs %} {% block breadcrumbs %}
<ul class="breadcrumb"> <ul class="breadcrumb">
{% for breadcrumb_name, breadcrumb_url in breadcrumblist %} {% for breadcrumb_name, breadcrumb_url in breadcrumblist %}
<li> {% if forloop.last %}
<a href="{{ breadcrumb_url }}" {% if forloop.last %}class="active"{% endif %}> <li class="active">{{ breadcrumb_name }}</li>
{{ breadcrumb_name }} {% else %}
</a> <li><a href="{{ breadcrumb_url }}">{{ breadcrumb_name }}</a></li>
{% if not forloop.last %}<span class="divider">&rsaquo;</span>{% endif %} {% endif %}
</li>
{% endfor %} {% endfor %}
</ul> </ul>
{% endblock %} {% endblock %}
@ -238,6 +237,7 @@
{% endif %} {% endif %}
</div> </div>
<!-- END Content --> <!-- END Content -->
</div><!-- /.container -->
<footer> <footer>
{% block footer %} {% block footer %}

View File

@ -2,7 +2,7 @@
<div class="col-sm-offset-2 col-sm-10"> <div class="col-sm-offset-2 col-sm-10">
<div class="checkbox"> <div class="checkbox">
<label> <label>
<input type="checkbox" name="{{ field.field_name }}" value="true" {% if value %}checked{% endif %}> <input type="checkbox" name="{{ field.field_name }}" value="true" {% if field.value %}checked{% endif %}>
{% if field.label %}{{ field.label }}{% endif %} {% if field.label %}{{ field.label }}{% endif %}
</label> </label>
</div> </div>

View File

@ -4,7 +4,7 @@
<legend class="control-label col-sm-2 {% if field.style.hide_label %}sr-only{% endif %}" style="border-bottom: 0">{{ field.label }}</legend> <legend class="control-label col-sm-2 {% if field.style.hide_label %}sr-only{% endif %}" style="border-bottom: 0">{{ field.label }}</legend>
</div> </div>
{% endif %} {% endif %}
{% for field_item in value.field_items.values() %} {% for field_item in field.value.field_items.values() %}
{{ renderer.render_field(field_item, layout=layout) }} {{ renderer.render_field(field_item, layout=layout) }}
{% endfor %} {% endfor %}
</fieldset> </fieldset>

View File

@ -1,7 +1,7 @@
<div class="form-group"> <div class="form-group">
{% include "rest_framework/fields/horizontal/label.html" %} {% include "rest_framework/fields/horizontal/label.html" %}
<div class="col-sm-10"> <div class="col-sm-10">
<input type="{{ input_type }}" class="form-control" {% include "rest_framework/fields/attrs.html" %} {% if value %}value="{{ value }}"{% endif %}> <input type="{{ input_type }}" class="form-control" {% include "rest_framework/fields/attrs.html" %} {% if field.value %}value="{{ field.value }}"{% endif %}>
{% if field.help_text %}<p class="help-block">{{ field.help_text }}</p>{% endif %} {% if field.help_text %}<p class="help-block">{{ field.help_text }}</p>{% endif %}
</div> </div>
</div> </div>

View File

@ -3,7 +3,7 @@
<div class="col-sm-10"> <div class="col-sm-10">
<select class="form-control" name="{{ field.field_name }}"> <select class="form-control" name="{{ field.field_name }}">
{% for key, text in field.choices.items %} {% for key, text in field.choices.items %}
<option value="{{ key }}" {% if key == value %}selected{% endif %}>{{ text }}</option> <option value="{{ key }}" {% if key == field.value %}selected{% endif %}>{{ text }}</option>
{% endfor %} {% endfor %}
</select> </select>
</div> </div>

View File

@ -4,7 +4,7 @@
{% if field.style.inline %} {% if field.style.inline %}
{% for key, text in field.choices.items %} {% for key, text in field.choices.items %}
<label class="checkbox-inline"> <label class="checkbox-inline">
<input type="checkbox" name="{{ field.field_name }}" value="{{ key }}" {% if key in value %}checked{% endif %}> <input type="checkbox" name="{{ field.field_name }}" value="{{ key }}" {% if key in field.value %}checked{% endif %}>
{{ text }} {{ text }}
</label> </label>
{% endfor %} {% endfor %}
@ -12,7 +12,7 @@
{% for key, text in field.choices.items %} {% for key, text in field.choices.items %}
<div class="checkbox"> <div class="checkbox">
<label> <label>
<input type="checkbox" name="{{ field.field_name }}" value="{{ key }}" {% if key in value %}checked{% endif %}> <input type="checkbox" name="{{ field.field_name }}" value="{{ key }}" {% if key in field.value %}checked{% endif %}>
{{ text }} {{ text }}
</label> </label>
</div> </div>

View File

@ -3,7 +3,7 @@
<div class="col-sm-10"> <div class="col-sm-10">
<select multiple class="form-control" name="{{ field.field_name }}"> <select multiple class="form-control" name="{{ field.field_name }}">
{% for key, text in field.choices.items %} {% for key, text in field.choices.items %}
<option value="{{ key }}" {% if key in value %}selected{% endif %}>{{ text }}</option> <option value="{{ key }}" {% if key in field.value %}selected{% endif %}>{{ text }}</option>
{% endfor %} {% endfor %}
</select> </select>
</div> </div>

View File

@ -4,7 +4,7 @@
{% if field.style.inline %} {% if field.style.inline %}
{% for key, text in field.choices.items %} {% for key, text in field.choices.items %}
<label class="radio-inline"> <label class="radio-inline">
<input type="radio" name="{{ field.field_name }}" value="{{ key }}" {% if key == value %}checked{% endif %}> <input type="radio" name="{{ field.field_name }}" value="{{ key }}" {% if key == field.value %}checked{% endif %}>
{{ text }} {{ text }}
</label> </label>
{% endfor %} {% endfor %}
@ -12,7 +12,7 @@
{% for key, text in field.choices.items %} {% for key, text in field.choices.items %}
<div class="radio"> <div class="radio">
<label> <label>
<input type="radio" name="{{ field.field_name }}" value="{{ key }}" {% if key == value %}checked{% endif %}> <input type="radio" name="{{ field.field_name }}" value="{{ key }}" {% if key == field.value %}checked{% endif %}>
{{ text }} {{ text }}
</label> </label>
</div> </div>

View File

@ -1,7 +1,7 @@
<div class="form-group"> <div class="form-group">
{% include "rest_framework/fields/horizontal/label.html" %} {% include "rest_framework/fields/horizontal/label.html" %}
<div class="col-sm-10"> <div class="col-sm-10">
<textarea class="form-control" {% include "rest_framework/fields/attrs.html" %}>{% if value %}{{ value }}{% endif %}</textarea> <textarea class="form-control" {% include "rest_framework/fields/attrs.html" %}>{% if field.value %}{{ field.value }}{% endif %}</textarea>
{% if field.help_text %}<p class="help-block">{{ field.help_text }}</p>{% endif %} {% if field.help_text %}<p class="help-block">{{ field.help_text }}</p>{% endif %}
</div> </div>
</div> </div>

View File

@ -1,6 +1,6 @@
<div class="checkbox"> <div class="checkbox">
<label> <label>
<input type="checkbox" name="{{ field.field_name }}" value="true" {% if value %}checked{% endif %}> <input type="checkbox" name="{{ field.field_name }}" value="true" {% if field.value %}checked{% endif %}>
{% if field.label %}{{ field.label }}{% endif %} {% if field.label %}{{ field.label }}{% endif %}
</label> </label>
</div> </div>

View File

@ -1,3 +1,3 @@
{% for field_item in value.field_items.values() %} {% for field_item in field.value.field_items.values() %}
{{ renderer.render_field(field_item, layout=layout) }} {{ renderer.render_field(field_item, layout=layout) }}
{% endfor %} {% endfor %}

View File

@ -1,4 +1,4 @@
<div class="form-group"> <div class="form-group">
{% include "rest_framework/fields/inline/label.html" %} {% include "rest_framework/fields/inline/label.html" %}
<input type="{{ input_type }}" class="form-control" {% include "rest_framework/fields/attrs.html" %} {% if value %}value="{{ value }}"{% endif %}> <input type="{{ input_type }}" class="form-control" {% include "rest_framework/fields/attrs.html" %} {% if field.value %}value="{{ field.value }}"{% endif %}>
</div> </div>

View File

@ -2,7 +2,7 @@
{% include "rest_framework/fields/inline/label.html" %} {% include "rest_framework/fields/inline/label.html" %}
<select class="form-control" name="{{ field.field_name }}"> <select class="form-control" name="{{ field.field_name }}">
{% for key, text in field.choices.items %} {% for key, text in field.choices.items %}
<option value="{{ key }}" {% if key == value %}selected{% endif %}>{{ text }}</option> <option value="{{ key }}" {% if key == field.value %}selected{% endif %}>{{ text }}</option>
{% endfor %} {% endfor %}
</select> </select>
</div> </div>

View File

@ -3,7 +3,7 @@
{% for key, text in field.choices.items %} {% for key, text in field.choices.items %}
<div class="checkbox"> <div class="checkbox">
<label> <label>
<input type="checkbox" name="{{ rest_framework/field.field_name }}" value="{{ key }}" {% if key in value %}checked{% endif %}> <input type="checkbox" name="{{ rest_framework/field.field_name }}" value="{{ key }}" {% if key in field.value %}checked{% endif %}>
{{ text }} {{ text }}
</label> </label>
</div> </div>

View File

@ -2,7 +2,7 @@
{% include "rest_framework/fields/inline/label.html" %} {% include "rest_framework/fields/inline/label.html" %}
<select multiple class="form-control" name="{{ field.field_name }}"> <select multiple class="form-control" name="{{ field.field_name }}">
{% for key, text in field.choices.items %} {% for key, text in field.choices.items %}
<option value="{{ key }}" {% if key in value %}selected{% endif %}>{{ text }}</option> <option value="{{ key }}" {% if key in field.value %}selected{% endif %}>{{ text }}</option>
{% endfor %} {% endfor %}
</select> </select>
</div> </div>

View File

@ -3,7 +3,7 @@
{% for key, text in field.choices.items %} {% for key, text in field.choices.items %}
<div class="radio"> <div class="radio">
<label> <label>
<input type="radio" name="{{ field.field_name }}" value="{{ key }}" {% if key == value %}checked{% endif %}> <input type="radio" name="{{ field.field_name }}" value="{{ key }}" {% if key == field.value %}checked{% endif %}>
{{ text }} {{ text }}
</label> </label>
</div> </div>

View File

@ -1,4 +1,4 @@
<div class="form-group"> <div class="form-group">
{% include "rest_framework/fields/inline/label.html" %} {% include "rest_framework/fields/inline/label.html" %}
<textarea class="form-control" {% include "rest_framework/fields/attrs.html" %}>{% if value %}{{ value }}{% endif %}</textarea> <textarea class="form-control" {% include "rest_framework/fields/attrs.html" %}>{% if field.value %}{{ field.value }}{% endif %}</textarea>
</div> </div>

View File

@ -1,6 +1,6 @@
<fieldset> <fieldset>
{% if field.label %}<legend {% if field.style.hide_label %}class="sr-only"{% endif %}>{{ field.label }}</legend>{% endif %} {% if field.label %}<legend {% if field.style.hide_label %}class="sr-only"{% endif %}>{{ field.label }}</legend>{% endif %}
{% for field_item in value.field_items.values() %} {% for field_item in field.value.field_items.values() %}
{{ renderer.render_field(field_item, layout=layout) }} {{ renderer.render_field(field_item, layout=layout) }}
{% endfor %} {% endfor %}
</fieldset> </fieldset>

View File

@ -1,5 +1,5 @@
<div class="form-group"> <div class="form-group">
{% include "rest_framework/fields/vertical/label.html" %} {% include "rest_framework/fields/vertical/label.html" %}
<input type="{{ input_type }}" class="form-control" {% include "rest_framework/fields/attrs.html" %} {% if value %}value="{{ value }}"{% endif %}> <input type="{{ input_type }}" class="form-control" {% include "rest_framework/fields/attrs.html" %} {% if field.value %}value="{{ field.value }}"{% endif %}>
{% if field.help_text %}<p class="help-block">{{ field.help_text }}</p>{% endif %} {% if field.help_text %}<p class="help-block">{{ field.help_text }}</p>{% endif %}
</div> </div>

View File

@ -2,7 +2,7 @@
{% include "rest_framework/fields/vertical/label.html" %} {% include "rest_framework/fields/vertical/label.html" %}
<select class="form-control" name="{{ field.field_name }}"> <select class="form-control" name="{{ field.field_name }}">
{% for key, text in field.choices.items %} {% for key, text in field.choices.items %}
<option value="{{ key }}" {% if key == value %}selected{% endif %}>{{ text }}</option> <option value="{{ key }}" {% if key == field.value %}selected{% endif %}>{{ text }}</option>
{% endfor %} {% endfor %}
</select> </select>
</div> </div>

View File

@ -4,7 +4,7 @@
<div> <div>
{% for key, text in field.choices.items %} {% for key, text in field.choices.items %}
<label class="checkbox-inline"> <label class="checkbox-inline">
<input type="checkbox" name="{{ field.field_name }}" value="{{ key }}" {% if key in value %}checked{% endif %}> <input type="checkbox" name="{{ field.field_name }}" value="{{ key }}" {% if key in field.value %}checked{% endif %}>
{{ text }} {{ text }}
</label> </label>
{% endfor %} {% endfor %}
@ -13,7 +13,7 @@
{% for key, text in field.choices.items %} {% for key, text in field.choices.items %}
<div class="checkbox"> <div class="checkbox">
<label> <label>
<input type="checkbox" name="{{ field.field_name }}" value="{{ key }}" {% if key in value %}checked{% endif %}> <input type="checkbox" name="{{ field.field_name }}" value="{{ key }}" {% if key in field.value %}checked{% endif %}>
{{ text }} {{ text }}
</label> </label>
</div> </div>

View File

@ -2,7 +2,7 @@
{% include "rest_framework/fields/vertical/label.html" %} {% include "rest_framework/fields/vertical/label.html" %}
<select multiple class="form-control" name="{{ field.field_name }}"> <select multiple class="form-control" name="{{ field.field_name }}">
{% for key, text in field.choices.items %} {% for key, text in field.choices.items %}
<option value="{{ key }}" {% if key in value %}selected{% endif %}>{{ text }}</option> <option value="{{ key }}" {% if key in field.value %}selected{% endif %}>{{ text }}</option>
{% endfor %} {% endfor %}
</select> </select>
</div> </div>

View File

@ -4,7 +4,7 @@
<div> <div>
{% for key, text in field.choices.items %} {% for key, text in field.choices.items %}
<label class="radio-inline"> <label class="radio-inline">
<input type="radio" name="{{ field.field_name }}" value="{{ key }}" {% if key|string==value|string %}checked{% endif %}> <input type="radio" name="{{ field.field_name }}" value="{{ key }}" {% if key == field.value %}checked{% endif %}>
{{ text }} {{ text }}
</label> </label>
{% endfor %} {% endfor %}
@ -13,7 +13,7 @@
{% for key, text in field.choices.items %} {% for key, text in field.choices.items %}
<div class="radio"> <div class="radio">
<label> <label>
<input type="radio" name="{{ field.field_name }}" value="{{ key }}" {% if key|string==value|string %}checked{% endif %}> <input type="radio" name="{{ field.field_name }}" value="{{ key }}" {% if key == field.value %}checked{% endif %}>
{{ text }} {{ text }}
</label> </label>
</div> </div>

View File

@ -1,5 +1,5 @@
<div class="form-group"> <div class="form-group">
{% include "rest_framework/fields/vertical/label.html" %} {% include "rest_framework/fields/vertical/label.html" %}
<textarea class="form-control" {% include "rest_framework/fields/attrs.html" %}>{% if value %}{{ value }}{% endif %}</textarea> <textarea class="form-control" {% include "rest_framework/fields/attrs.html" %}>{% if field.value %}{{ field.value }}{% endif %}</textarea>
{% if field.help_text %}<p class="help-block">{{ field.help_text }}</p>{% endif %} {% if field.help_text %}<p class="help-block">{{ field.help_text }}</p>{% endif %}
</div> </div>

View File

@ -1,4 +1,4 @@
<html> <!-- <html>
<head> <head>
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet"> <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">
</head> </head>
@ -6,13 +6,15 @@
<div class="container"> <div class="container">
<h1>User update</h1> <h1>User update</h1>
<div class="well"> <div class="well"> -->
{% load rest_framework %} {% load rest_framework %}
<form {% if layout == "inline" %}class="form-inline"{% elif layout == "horizontal" %}class="form-horizontal"{% endif %} role="form" action="." method="POST"> <form {% if layout == "inline" %}class="form-inline"{% elif layout == "horizontal" %}class="form-horizontal"{% endif %} role="form" action="." method="POST">
{% csrf_token %} {% csrf_token %}
{% for field, value, errors in form %} {% for field in form %}
{% render_field field value errors layout=layout renderer=renderer %} {% if not field.read_only %}
{% render_field field layout=layout renderer=renderer %}
{% endif %}
{% endfor %} {% endfor %}
<!-- form.non_field_errors --> <!-- form.non_field_errors -->
{% if layout == "horizontal" %} {% if layout == "horizontal" %}
@ -25,7 +27,7 @@
<button type="submit" class="btn btn-default">Submit</button> <button type="submit" class="btn btn-default">Submit</button>
{% endif %} {% endif %}
</form> </form>
<!--
</div> </div>
</div></body> </div></body>
</html> </html> -->

View File

@ -31,12 +31,9 @@ class_re = re.compile(r'(?<=class=["\'])(.*)(?=["\'])')
# And the template tags themselves... # 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 @register.simple_tag
def render_field(field, value, errors, layout=None, renderer=None): def render_field(field, layout=None, renderer=None):
return renderer.render_field(field, value, errors, layout) return renderer.render_field(field, layout)
@register.simple_tag @register.simple_tag

View File

@ -21,7 +21,14 @@ class ClassLookupDict(object):
self.mapping = mapping self.mapping = mapping
def __getitem__(self, key): def __getitem__(self, key):
for cls in inspect.getmro(key.__class__): if hasattr(key, '_proxy_class'):
# Deal with proxy classes. Ie. BoundField behaves as if it
# is a Field instance when using ClassLookupDict.
base_class = key._proxy_class
else:
base_class = key.__class__
for cls in inspect.getmro(base_class):
if cls in self.mapping: if cls in self.mapping:
return self.mapping[cls] return self.mapping[cls]
raise KeyError('Class %s not found in lookup.', cls.__name__) raise KeyError('Class %s not found in lookup.', cls.__name__)