Repository: encode/django-rest-framework

Issue:
Public forms API.
With 3.0 we now have a template based form rendering for serializers.

This has basically everything it needs to serve as a fully fledged alternative to django forms.
I'm also currently planning for it to be compatible with use in eg. Django's standard class based views.
(So for example, we'll need to provide a `files` keyword argument for cross-compatibility)

The API for all this still needs to be made public, documented, and any rough edges ironed out.
In order to not block the 3.0 release I'm milestoning this to 3.1 - there's no reason that this needs to become public API straight away, better to take an incremental approach.

In particular we should document how to create alternate template packs.



Discussion:
This might also be related: https://groups.google.com/forum/#!topic/django-rest-framework/Xd5JpZ3H_o8 (I apologize for my terrible photoshop skills).

The idea here is to support nested serializers on the form itself. In the example address is nested to `state`, etc. and `full_name` is nested to first name and last name.

Yes indeed - nested forms are already supported :) eg...

```
class ProfileSerializer(serializers.Serializer):
    role = serializers.ChoiceField(
        ['owner', 'staff', 'customer'],
        style={'base_template': 'radio.html', 'inline': True}
    )
    last_payment = serializers.DateTimeField()

class UserSerializer(serializers.Serializer):
    email = serializers.EmailField()
    username = serializers.CharField()
    profile = ProfileSerializer()
```

Renders as this...

![screen shot 2014-11-10 at 22 35 54](https://cloud.githubusercontent.com/assets/647359/4985142/324eb424-692a-11e4-8e95-9e1b66013a62.png)

It doesn't yet support multiple items or multiple inline items, and don't assume any of the API above is final, but as you can see we've got the bases pretty much covered. :)

Ah. Very cool. Is this new to 3.0 or was it there all along?

This might ALSO be related to the ticket: https://groups.google.com/forum/#!topic/django-rest-framework/e75osh1Wcyg.

It's completely new to 3.0.
I was planning on making it public API for 3.0, but I think we'll do better to let any of the more basic niggles get thrashed out during 3.0, and only make it publicly supported API in 3.1 (so think another ~2 months after 3.0, but should mostly be usable during that period, if you're willing to keep up to date with any private API changes)

I am willing to deal with a changing API.

On Mon Nov 10 2014 at 5:43:43 PM Tom Christie notifications@github.com
wrote:

> It's completely new to 3.0.
> I was planning on making it public API for 3.0, but I think we'll do
> better to let any of the more basic niggles get thrashed out during 3.0,
> and only make it publicly supported API in 3.1 (so think another ~2 months
> after 3.0, but should mostly be usable during that period, if you're
> willing to keep up to date with any private API changes)
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/tomchristie/django-rest-framework/issues/2060#issuecomment-62469932
> .

Pushing all further HTML specific stuff to 3.2.0.

@tomchristie Taking a look at the [example rendering from above](https://github.com/tomchristie/django-rest-framework/issues/2060#issuecomment-62469196), if you set the order of the fields explicitly:

``` python
class UserSerializer(serializers.Serializer):
    profile = ProfileSerializer()
    email = serializers.EmailField()
    username = serializers.CharField()
```

There is a sub heading for "Profile" but no sub-heading for "User" which make it hard to tell which fields belong to which serializer.

Compare these two:
![image](https://cloud.githubusercontent.com/assets/51059/6520952/fb848978-c39c-11e4-99e3-d3335b575dd4.png)
and
![image](https://cloud.githubusercontent.com/assets/51059/6520963/1274f848-c39d-11e4-9257-e3e1ad12ea4a.png)
Is is not clear in the first image whether color belongs to profile.

As the number of fields grows, this can become confusing. Ideally all of the nested serializers should be grouped at the bottom

So re

So reorder them in the serializer.
Quite sure that if do that automatically for the user we'd have issue raised from folks asking for the behavior to not do that. It's also unnecessary extra trickery.

So I commingled two concerns here:
1) is the intelligent reordering of fields. I think you made a fair point that this would not be transparent to the user
2) Is just adding a subheading over / some indication of the main set of serializer fields when using nested serializers. The current UI / UX makes it a bit confusing.

Yeah worth us having a look at if we can handle it any better.
I'll keep this in mind when I come to documenting and formalizing the Serializers as HTML API.

https://github.com/WiserTogether/django-remote-forms does this have any use? regarding the issue

auvipy - probably not in scope for the work as currently planned.


Resolving patch:
diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py
--- a/rest_framework/renderers.py
+++ b/rest_framework/renderers.py
@@ -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 @@ 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', {})
+
+        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 @@ def get_rendered_html_form(self, data, view, method, request):
             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):
diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py
--- a/rest_framework/templatetags/rest_framework.py
+++ b/rest_framework/templatetags/rest_framework.py
@@ -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)
 


Test patch: