4

I want to create boiler-plate forms for my django models using the rest framework.

Documentation shows it using the APIView: http://www.django-rest-framework.org/topics/html-and-forms/#rendering-forms.

But I want to use the ModelViewSet in order to avoid defining custom action methods.

Is this possible? Can you share an example?

Here is what I tried. My model:

class Person(AbstractUser):
    pass

My serializer:

class PersonSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Person
        fields = ('first_name', 'last_name', 'email', 'groups')

My view:

class PersonViewSet(viewsets.ModelViewSet):
    """
    API endpoint that allows persons to be viewed or edited.
    """
    queryset = Person.objects.all().order_by('-date_joined')
    serializer_class = PersonSerializer
    renderer_classes = [TemplateHTMLRenderer]
    template_name = 'common/rest_create.html'

And here is my url:

url(r'person_create_api/$', PersonViewSet.as_view({'get': 'create'}), name='person-create-api'),

And the error I get:

IntegrityError at /school/person_create_api/
duplicate key value violates unique constraint "school_person_username_key"
DETAIL:  Key (username)=() already exists.

When I add the username field to the serializer fields, I get:

HTTP/1.0 400 Bad Request
Date: Tue, 20 Sep 2016 17:00:22 GMT
Server: WSGIServer/0.2 CPython/3.5.1+
X-Frame-Options: SAMEORIGIN
Vary: Cookie
Allow: GET, HEAD, OPTIONS
Content-Type: text/html; charset=utf-8

I am using django 1.9 and latest DRF 3.4.6.

mehmet
  • 7,720
  • 5
  • 42
  • 48
  • once this works, I will take the content of that view and use it in other views. – mehmet Sep 19 '16 at 03:35
  • ModelViewSet's base classes are as follows: ModelViewSet > GenericViewSet > GenericAPIView > APIView. So it does inherit from APIView. – mehmet Sep 19 '16 at 22:26
  • What version of DRF and Django are you using? You're defining your get as an `as_view` instead of overriding/overloading it on the modelviewset you created, which might not perform the way you expect. You're also not using a router, which is the traditional way to route API endpoints for DRF. They even show how to create a custom one in the documentation (http://www.django-rest-framework.org/api-guide/routers/#custom-routers). I also have no idea what your serializer or model for `Person` looks like. Could you possibly include that? – aredzko Sep 20 '16 at 16:29
  • tredzko I added the model and the serializer. I need to look into the custom routers. – mehmet Sep 20 '16 at 16:38
  • Try adding 'username' to your list of fields on the 'PersonSerializer' to see if that might help (you would have to specify it on create). The integrity error looks like it comes from trying to assign a blank username (since it's not exposed on the serializer) to more than one id, when username is constrained to be unique. Keep in mind that you're telling 'get' (default behavior is to view, not post) to try and 'create' a Person, so it might not be getting any values and it may be erroring because of that. – aredzko Sep 20 '16 at 16:48
  • tredzko I posted the error message I get when adding the `username` to the serializer fields. I don't know how to specify it on create, and I am just trying to get a blank form. I don't understand why would it be needed. Thanks for help. – mehmet Sep 20 '16 at 17:05
  • tredzko if you could make a simple example work along the same line and post it here as an answer, that would be very helpful. – mehmet Sep 20 '16 at 17:06
  • The example you linked to would likely be where I'd point for an example of what you're going for. It sounds like you want to have a form or custom render view on 'get'. You're overriding 'get' to act like 'post', which is the problem at the moment. Try registering this view like you normally would with your existing api router and see if it properly gives you the custom template you've specified. – aredzko Sep 20 '16 at 17:09

1 Answers1

3

First thing first, let the DRF create the url's for you (this prevents misconfiguring the urls):

from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'snippets', PersonViewSet)

urlpatterns = [
    ...
    url(r'^', include(router.urls)),
]

Here are the urls generated:

^persons/$ [name='person-list']
^persons\.(?P<format>[a-z0-9]+)/?$ [name='person-list']
^persons/blank_form/$ [name='person-blank-form']
^persons/blank_form\.(?P<format>[a-z0-9]+)/?$ [name='person-blank-form']
^persons/(?P<pk>[^/.]+)/$ [name='person-detail']
^persons/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$ [name='person-detail']

Model is same as above, and here is the view:

class PersonViewSet(viewsets.ModelViewSet):
    """
    API endpoint that allows persons to be viewed or edited.
    """
    queryset = Person.objects.all().order_by('-date_joined')
    serializer_class = PersonSerializer
    template_name = 'common/rest_create.html'

    @list_route(renderer_classes=[renderers.TemplateHTMLRenderer])
    def blank_form(self, request, *args, **kwargs):
        serializer = PersonSerializer()
        return Response({'serializer': serializer})

Note, the TemplateHtmlRenderer is set at the method level instead of globally in the class so to let it use other appropriate renderers for other methods/views. Cheating off the tutorial at DRF site, serializer = PersonSerializer() this is used for generating an unbound form.

And here is the template:

{% load rest_framework %}

<html><body>    
<h1>New Person</h1>

<form action="{% url 'school:person-create' %}" method="POST">
    {% csrf_token %}
    {% render_form serializer %}
    <input type="submit" value="Save">
</form>

</body></html>

Other views that you expect from a ModelViewSet work as usual.

mehmet
  • 7,720
  • 5
  • 42
  • 48
  • So this approach involves adding a method to the class and writing a template. I am still curious how would it be done using the out-of-the-box functionality. – mehmet Sep 22 '16 at 02:43
  • FYI `@list_route` has been replaced by `@action` – foobarbecue Nov 01 '22 at 18:27