mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-02-03 21:24:33 +03:00
commit
6305ae8916
|
@ -85,9 +85,9 @@ A value that should be used for pre-populating the value of HTML form fields.
|
|||
|
||||
### `style`
|
||||
|
||||
A dictionary of key-value pairs that can be used to control how renderers should render the field. The API for this should still be considered experimental, and will be formalized with the 3.1 release.
|
||||
A dictionary of key-value pairs that can be used to control how renderers should render the field.
|
||||
|
||||
Two options are currently used in HTML form generation, `'input_type'` and `'base_template'`.
|
||||
Two examples here are `'input_type'` and `'base_template'`:
|
||||
|
||||
# Use <input type="password"> for the input.
|
||||
password = serializers.CharField(
|
||||
|
@ -100,7 +100,7 @@ Two options are currently used in HTML form generation, `'input_type'` and `'bas
|
|||
style = {'base_template': 'radio.html'}
|
||||
}
|
||||
|
||||
**Note**: The `style` argument replaces the old-style version 2.x `widget` keyword argument. Because REST framework 3 now uses templated HTML form generation, the `widget` option that was used to support Django built-in widgets can no longer be supported. Version 3.3 is planned to include public API support for customizing HTML form generation.
|
||||
For more details see the [HTML & Forms][html-and-forms] documentation.
|
||||
|
||||
---
|
||||
|
||||
|
@ -658,6 +658,7 @@ The [django-rest-framework-gis][django-rest-framework-gis] package provides geog
|
|||
The [django-rest-framework-hstore][django-rest-framework-hstore] package provides an `HStoreField` to support [django-hstore][django-hstore] `DictionaryField` model field.
|
||||
|
||||
[cite]: https://docs.djangoproject.com/en/dev/ref/forms/api/#django.forms.Form.cleaned_data
|
||||
[html-and-forms]: ../topics/html-and-forms.md
|
||||
[FILE_UPLOAD_HANDLERS]: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-FILE_UPLOAD_HANDLERS
|
||||
[ecma262]: http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.15
|
||||
[strftime]: http://docs.python.org/2/library/datetime.html#strftime-and-strptime-behavior
|
||||
|
|
|
@ -197,9 +197,19 @@ Note that views that have nested or list serializers for their input won't work
|
|||
|
||||
## HTMLFormRenderer
|
||||
|
||||
Renders data returned by a serializer into an HTML form. The output of this renderer does not include the enclosing `<form>` tags or an submit actions, as you'll probably need those to include the desired method and URL. Also note that the `HTMLFormRenderer` does not yet support including field error messages.
|
||||
Renders data returned by a serializer into an HTML form. The output of this renderer does not include the enclosing `<form>` tags, a hidden CSRF input or any submit buttons.
|
||||
|
||||
**Note**: The `HTMLFormRenderer` class is intended for internal use with the browsable API and admin interface. It should not be considered a fully documented or stable API. The template used by the `HTMLFormRenderer` class, and the context submitted to it **may be subject to change**. If you need to use this renderer class it is advised that you either make a local copy of the class and templates, or follow the release note on REST framework upgrades closely.
|
||||
This renderer is not intended to be used directly, but can instead be used in templates by passing a serializer instance to the `render_form` template tag.
|
||||
|
||||
{% load rest_framework %}
|
||||
|
||||
<form action="/submit-report/" method="post">
|
||||
{% csrf_token %}
|
||||
{% render_form serializer %}
|
||||
<input type="submit" value="Save" />
|
||||
</form>
|
||||
|
||||
For more information see the [HTML & Forms][html-and-forms] documentation.
|
||||
|
||||
**.media_type**: `text/html`
|
||||
|
||||
|
@ -207,7 +217,7 @@ Renders data returned by a serializer into an HTML form. The output of this ren
|
|||
|
||||
**.charset**: `utf-8`
|
||||
|
||||
**.template**: `'rest_framework/form.html'`
|
||||
**.template**: `'rest_framework/horizontal/form.html'`
|
||||
|
||||
## MultiPartRenderer
|
||||
|
||||
|
@ -455,6 +465,7 @@ Comma-separated values are a plain-text tabular data format, that can be easily
|
|||
|
||||
[cite]: https://docs.djangoproject.com/en/dev/ref/template-response/#the-rendering-process
|
||||
[conneg]: content-negotiation.md
|
||||
[html-and-forms]: ../topics/html-and-forms.md
|
||||
[browser-accept-headers]: http://www.gethifi.com/blog/browser-rest-http-accept-headers
|
||||
[testing]: testing.md
|
||||
[HATEOAS]: http://timelessrepo.com/haters-gonna-hateoas
|
||||
|
|
BIN
docs/img/horizontal.png
Normal file
BIN
docs/img/horizontal.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
docs/img/inline.png
Normal file
BIN
docs/img/inline.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.1 KiB |
BIN
docs/img/vertical.png
Normal file
BIN
docs/img/vertical.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
|
@ -191,7 +191,9 @@ The API guide is your complete reference manual to all the functionality provide
|
|||
General guides to using REST framework.
|
||||
|
||||
* [Documenting your API][documenting-your-api]
|
||||
* [Internationalization][internationalization]
|
||||
* [AJAX, CSRF & CORS][ajax-csrf-cors]
|
||||
* [HTML & Forms][html-and-forms]
|
||||
* [Browser enhancements][browser-enhancements]
|
||||
* [The Browsable API][browsableapi]
|
||||
* [REST, Hypermedia & HATEOAS][rest-hypermedia-hateoas]
|
||||
|
@ -303,8 +305,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
[settings]: api-guide/settings.md
|
||||
|
||||
[documenting-your-api]: topics/documenting-your-api.md
|
||||
[internationalization]: topics/documenting-your-api.md
|
||||
[internationalization]: topics/internationalization.md
|
||||
[ajax-csrf-cors]: topics/ajax-csrf-cors.md
|
||||
[html-and-forms]: topics/html-and-forms.md
|
||||
[browser-enhancements]: topics/browser-enhancements.md
|
||||
[browsableapi]: topics/browsable-api.md
|
||||
[rest-hypermedia-hateoas]: topics/rest-hypermedia-hateoas.md
|
||||
|
|
214
docs/topics/html-and-forms.md
Normal file
214
docs/topics/html-and-forms.md
Normal file
|
@ -0,0 +1,214 @@
|
|||
# HTML & Forms
|
||||
|
||||
REST framework is suitable for returning both API style responses, and regular HTML pages. Additionally, serializers can used as HTML forms and rendered in templates.
|
||||
|
||||
## Rendering HTML
|
||||
|
||||
In order to return HTML responses you'll need to either `TemplateHTMLRenderer`, or `StaticHTMLRenderer`.
|
||||
|
||||
The `TemplateHTMLRenderer` class expects the response to contain a dictionary of context data, and renders an HTML page based on a template that must be specified either in the view or on the response.
|
||||
|
||||
The `StaticHTMLRender` class expects the response to contain a string of the pre-rendered HTML content.
|
||||
|
||||
Because static HTML pages typically have different behavior from API responses you'll probably need to write any HTML views explicitly, rather than relying on the built-in generic views.
|
||||
|
||||
Here's an example of a view that returns a list of "Profile" instances, rendered in an HTML template:
|
||||
|
||||
**views.py**:
|
||||
|
||||
from my_project.example.models import Profile
|
||||
from rest_framework.renderers import TemplateHTMLRenderer
|
||||
from rest_framework.views import APIView
|
||||
|
||||
|
||||
class ProfileList(APIView):
|
||||
renderer_classes = [TemplateHTMLRenderer]
|
||||
template_name = 'profile_list.html'
|
||||
|
||||
def get(self, request):
|
||||
queryset = Profile.objects.all()
|
||||
return Response({'profiles': queryset})
|
||||
|
||||
**profile_list.html**:
|
||||
|
||||
<html><body>
|
||||
<h1>Profiles</h1>
|
||||
<ul>
|
||||
{% for profile in profiles %}
|
||||
<li>{{ profile.name }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</body></html>
|
||||
|
||||
## Rendering Forms
|
||||
|
||||
Serializers may be rendered as forms by using the `render_form` template tag, and including the serializer instance as context to the template.
|
||||
|
||||
The following view demonstrates an example of using a serializer in a template for viewing and updating a model instance:
|
||||
|
||||
**views.py**:
|
||||
|
||||
from django.shortcuts import get_object_or_404
|
||||
from my_project.example.models import Profile
|
||||
from rest_framework.renderers import TemplateHTMLRenderer
|
||||
from rest_framework.views import APIView
|
||||
|
||||
|
||||
class ProfileDetail(APIView):
|
||||
renderer_classes = [TemplateHTMLRenderer]
|
||||
template_name = 'profile_detail.html'
|
||||
|
||||
def get(self, request, pk):
|
||||
profile = get_object_or_404(Profile, pk=pk)
|
||||
serializer = ProfileSerializer(profile)
|
||||
return Response({'serializer': serializer, 'profile': profile})
|
||||
|
||||
def post(self, request, pk):
|
||||
profile = get_object_or_404(Profile, pk=pk)
|
||||
serializer = ProfileSerializer(profile)
|
||||
if not serializer.is_valid():
|
||||
return Response({'serializer': serializer, 'profile': profile})
return redirect('profile-list')
|
||||
|
||||
**profile_detail.html**:
|
||||
|
||||
{% load rest_framework %}
|
||||
|
||||
<html><body>
|
||||
|
||||
<h1>Profile - {{ profile.name }}</h1>
|
||||
|
||||
<form action="{% url 'profile-detail' pk=profile.pk '%}" method="POST">
|
||||
{% csrf_token %}
|
||||
{% render_form serializer %}
|
||||
<input type="submit" value="Save">
|
||||
</form>
|
||||
|
||||
</body></html>
|
||||
|
||||
### Using template packs
|
||||
|
||||
The `render_form` tag takes an optional `template_pack` argument, that specifies which template directory should be used for rendering the form and form fields.
|
||||
|
||||
REST framework includes three built-in template packs, all based on Bootstrap 3. The built-in styles are `horizontal`, `vertical`, and `inline`. The default style is `horizontal`. To use any of these template packs you'll want to also include the Bootstrap 3 CSS.
|
||||
|
||||
The following HTML will link to a CDN hosted version of the Bootstrap 3 CSS:
|
||||
|
||||
<head>
|
||||
…
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
|
||||
</head>
|
||||
|
||||
Third party packages may include alternate template packs, by bundling a template directory containing the necessary form and field templates.
|
||||
|
||||
Let's take a look at how to render each of the three available template packs. For these examples we'll use a single serializer class to present a "Login" form.
|
||||
|
||||
class LoginSerializer(serializers.Serializer):
|
||||
email = serializers.EmailField(
|
||||
max_length=100,
|
||||
style={'placeholder': 'Email'}
|
||||
)
|
||||
password = serializers.CharField(
|
||||
max_length=100,
|
||||
style={'input_type': 'password', 'placeholder': 'Password'}
|
||||
)
|
||||
remember_me = serializers.BooleanField()
---
|
||||
|
||||
#### `rest_framework/vertical`
|
||||
|
||||
Presents form labels above their corresponding control inputs, using the standard Bootstrap layout.
|
||||
|
||||
*This is the default template pack.*
|
||||
|
||||
{% load rest_framework %}
|
||||
|
||||
...
|
||||
|
||||
<form action="{% url 'login' %}" method="post" novalidate>
|
||||
{% csrf_token %}
|
||||
{% render_form serializer template_pack='rest_framework/vertical' %}
|
||||
<button type="submit" class="btn btn-default">Sign in</button>
|
||||
</form>
|
||||
|
||||
![Vertical form example](../img/vertical.png)
|
||||
|
||||
---
|
||||
#### `rest_framework/horizontal`
|
||||
|
||||
Presents labels and controls alongside each other, using a 2/10 column split.
|
||||
|
||||
*This is the form style used in the browsable API and admin renderers.*
|
||||
|
||||
{% load rest_framework %}
|
||||
|
||||
...
|
||||
|
||||
<form class="form-horizontal" action="{% url 'login' %}" method="post" novalidate>
|
||||
{% csrf_token %}
|
||||
{% render_form serializer %}
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
<button type="submit" class="btn btn-default">Sign in</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
![Horizontal form example](../img/horizontal.png)
|
||||
|
||||
---
|
||||
|
||||
#### `rest_framework/inline`
|
||||
|
||||
A compact form style that presents all the controls inline.
|
||||
|
||||
{% load rest_framework %}
|
||||
|
||||
...
|
||||
|
||||
<form class="form-inline" action="{% url 'login' %}" method="post" novalidate>
|
||||
{% csrf_token %}
|
||||
{% render_form serializer template_pack='rest_framework/inline' %}
|
||||
<button type="submit" class="btn btn-default">Sign in</button>
|
||||
</form>
|
||||
|
||||
![Inline form example](../img/inline.png)
|
||||
|
||||
## Field styles
|
||||
|
||||
Serializer fields can have their rendering style customized by using the `style` keyword argument. This argument is a dictionary of options that control the template and layout used.
|
||||
|
||||
The most common way to customize the field style is to use the `base_template` style keyword argument to select which template in the template pack should be use.
|
||||
|
||||
For example, to render a `CharField` as an HTML textarea rather than the default HTML input, you would use something like this:
|
||||
|
||||
details = serializers.CharField(
|
||||
max_length=1000,
|
||||
style={'base_template': 'textarea.html'}
|
||||
)
|
||||
|
||||
If you instead want a field to be rendered using a custom template that is *not part of an included template pack*, you can instead use the `template` style option, to fully specify a template name:
|
||||
|
||||
details = serializers.CharField(
|
||||
max_length=1000,
|
||||
style={'template': 'my-field-templates/custom-input.html'}
|
||||
)
|
||||
|
||||
Field templates can also use additional style properties, depending on their type. For example, the `textarea.html` template also accepts a `rows` property that can be used to affect the sizing of the control.
|
||||
|
||||
details = serializers.CharField(
|
||||
max_length=1000,
|
||||
style={'base_template': 'textarea.html', 'rows': 10}
|
||||
)
|
||||
|
||||
The complete list of `base_template` options and their associated style options is listed below.
|
||||
|
||||
base_template | Valid field types | Additional style options
|
||||
----|----|----
|
||||
input.html | Any string, numeric or date/time field | input_type, placeholder, hide_label
|
||||
textarea.html | `CharField` | rows, placeholder, hide_label
|
||||
select.html | `ChoiceField` or relational field types | hide_label
|
||||
radio.html | `ChoiceField` or relational field types | inline, hide_label
|
||||
select_multiple.html | `MultipleChoiceField` or relational fields with `many=True` | hide_label
|
||||
checkbox_multiple.html | `MultipleChoiceField` or relational fields with `many=True` | inline, hide_label
|
||||
checkbox.html | `BooleanField` | hide_label
|
||||
fieldset.html | Nested serializer | hide_label
|
||||
list_fieldset.html | `ListField` or nested serializer with `many=True` | hide_label
|
|
@ -47,6 +47,7 @@ pages:
|
|||
- 'Documenting your API': 'topics/documenting-your-api.md'
|
||||
- 'Internationalization': 'topics/internationalization.md'
|
||||
- 'AJAX, CSRF & CORS': 'topics/ajax-csrf-cors.md'
|
||||
- 'HTML & Forms': 'topics/html-and-forms.md'
|
||||
- 'Browser Enhancements': 'topics/browser-enhancements.md'
|
||||
- 'The Browsable API': 'topics/browsable-api.md'
|
||||
- 'REST, Hypermedia & HATEOAS': 'topics/rest-hypermedia-hateoas.md'
|
||||
|
|
|
@ -249,7 +249,7 @@ class HTMLFormRenderer(BaseRenderer):
|
|||
media_type = 'text/html'
|
||||
format = 'form'
|
||||
charset = 'utf-8'
|
||||
template_pack = 'rest_framework/horizontal/'
|
||||
template_pack = 'rest_framework/vertical/'
|
||||
base_template = 'form.html'
|
||||
|
||||
default_style = ClassLookupDict({
|
||||
|
@ -341,26 +341,16 @@ class HTMLFormRenderer(BaseRenderer):
|
|||
Render serializer data and return an HTML form, as a string.
|
||||
"""
|
||||
form = data.serializer
|
||||
meta = getattr(form, 'Meta', None)
|
||||
style = getattr(meta, 'style', {})
|
||||
|
||||
style = renderer_context.get('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
|
||||
|
||||
# This API needs to be finessed and finalized for 3.1
|
||||
if 'template' in renderer_context:
|
||||
template_name = renderer_context['template']
|
||||
elif '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_pack = style['template_pack'].strip('/')
|
||||
template_name = template_pack + '/' + self.base_template
|
||||
template = loader.get_template(template_name)
|
||||
context = RequestContext(request, {
|
||||
context = Context({
|
||||
'form': form,
|
||||
'style': style
|
||||
})
|
||||
|
@ -505,10 +495,7 @@ class BrowsableAPIRenderer(BaseRenderer):
|
|||
return form_renderer.render(
|
||||
serializer.data,
|
||||
self.accepted_media_type,
|
||||
dict(
|
||||
list(self.renderer_context.items()) +
|
||||
[('template', 'rest_framework/api_form.html')]
|
||||
)
|
||||
{'style': {'template_pack': 'rest_framework/horizontal'}}
|
||||
)
|
||||
|
||||
def get_raw_data_form(self, data, view, method, request):
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
{% load rest_framework %}
|
||||
{% csrf_token %}
|
||||
{% for field in form %}
|
||||
{% if not field.read_only %}
|
||||
{% render_field field style=style %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<!-- form.non_field_errors -->
|
|
@ -154,6 +154,7 @@
|
|||
{% with form=post_form %}
|
||||
<form action="{{ request.get_full_path }}" method="POST" enctype="multipart/form-data" class="form-horizontal" novalidate>
|
||||
<fieldset>
|
||||
{% csrf_token %}
|
||||
{{ post_form }}
|
||||
<div class="form-actions">
|
||||
<button class="btn btn-primary" title="Make a POST request on the {{ name }} resource">POST</button>
|
||||
|
|
|
@ -1,16 +1,6 @@
|
|||
{% load rest_framework %}
|
||||
|
||||
<form class="form-horizontal" role="form" action="." method="POST" novalidate>
|
||||
{% csrf_token %}
|
||||
{% for field in form %}
|
||||
{% if not field.read_only %}
|
||||
{% render_field field style=style %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
<button type="submit" class="btn btn-default">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% for field in form %}
|
||||
{% if not field.read_only %}
|
||||
{% render_field field style=style %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
|
|
@ -1,13 +1,6 @@
|
|||
{% load rest_framework %}
|
||||
|
||||
<form class="form-inline" role="form" action="." method="POST" novalidate>
|
||||
{% csrf_token %}
|
||||
|
||||
{% for field in form %}
|
||||
{% if not field.read_only %}
|
||||
{% render_field field style=style %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<button type="submit" class="btn btn-default">Submit</button>
|
||||
</form>
|
||||
{% for field in form %}
|
||||
{% if not field.read_only %}
|
||||
{% render_field field style=style %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
{% load rest_framework %}
|
||||
|
||||
<form role="form" action="." method="POST" novalidate>
|
||||
{% csrf_token %}
|
||||
{% for field in form %}
|
||||
{% if not field.read_only %}
|
||||
{% render_field field style=style %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<button type="submit" class="btn btn-default">Submit</button>
|
||||
</form>
|
||||
{% for field in form %}
|
||||
{% if not field.read_only %}
|
||||
{% render_field field style=style %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
|
|
@ -25,8 +25,14 @@ def get_pagination_html(pager):
|
|||
|
||||
|
||||
@register.simple_tag
|
||||
def render_field(field, style=None):
|
||||
style = style or {}
|
||||
def render_form(serializer, template_pack=None):
|
||||
style = {'template_pack': template_pack} if template_pack else {}
|
||||
renderer = HTMLFormRenderer()
|
||||
return renderer.render(serializer.data, None, {'style': style})
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def render_field(field, style):
|
||||
renderer = style.get('renderer', HTMLFormRenderer())
|
||||
return renderer.render_field(field, style)
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user