5

I am new to Django REST Framework. What I try to do is to render a Generic APIView (RetrieveUpdateDestroyAPIView) in HTML similarly to the way the ViewSet is automatically rendered in the Browsable API.

Following the official documentation, I have in my myApp/views.py:

class AnnounceViewSet(viewsets.ModelViewSet):
    """
    API endpoint that allows announces to be viewed or edited.
    """
    queryset = Announce.objects.all()
    serializer_class = AnnounceSerializer
    permission_classes = (permissions.IsAuthenticatedOrReadOnly,
                          IsOwnerOrReadOnly,)


    def perform_create(self, serializer): # without this, the POST request of the announce doesnt work
        serializer.save(owner=self.request.user)


class AnnounceList(APIView):
    renderer_classes = [TemplateHTMLRenderer]
    template_name = 'myApp/announces_list.html'
    permission_classes = (permissions.IsAuthenticatedOrReadOnly,)

    def get(self, request):
        queryset = Announce.objects.all()
        return Response({'announces': queryset})

class AnnounceDetail(generics.RetrieveUpdateDestroyAPIView):
   queryset = Announce.objects.all()
   serializer_class = AnnounceSerializer
   renderer_classes = [TemplateHTMLRenderer]
   template_name = 'myApp/announce_detail.html'
   permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly,)

In my urls.py:

from django.conf.urls import url, include
from rest_framework import routers
from myApp import views
from django.contrib import admin


router = routers.DefaultRouter()
router.register(r'api/users', views.UserViewSet)
router.register(r'api/groups', views.GroupViewSet)
router.register(r'api/announces', views.AnnounceViewSet)

# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'', include('myApp.urls')),
    url(r'^', include(router.urls)),
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
    url(r'^accounts/', include('allauth.urls')),
    url(r'^announces/$', views.AnnounceList.as_view(), name='announces-list'),
    url(r'^announces/(?P<pk>[0-9]+)/$', views.AnnounceDetail.as_view(), name='announce-detail'),

When I go the Browsable API, through the link /api/announces/3/, I can see properly the announce object, with the right permissions depending on the authenticated user.

But when I go to the /announces/3/, I have this error:

NoReverseMatch at /announces/3/

Reverse for 'announce-detail' with keyword arguments '{'pk': ''}' not found. 3 pattern(s) tried: ['announces/(?P<pk>[0-9]+)/$', 'api/announces/(?P<pk>[^/.]+)\\.(?P<format>[a-z0-9]+)/?$', 'api/announces/(?P<pk>[^/.]+)/$']

Here is my announce_detail.html template:

{% load rest_framework %}

{% block content %}

<form action="{% url 'announce-detail' pk=announce.pk %}" method="POST">
    {% csrf_token %}
    {% render_form serializer %}
    <input type="submit" value="Save">
</form>

{% endblock content %}

If I understand well, the Django REST View (be it ViewSet or ViewAPI) is for retrieving/putting data in JSON format, and the Django normal view is for HTML usual rendering. However, the Django REST View can also be used for normal Django HTML rendering. By having an exposed API endpoint, data could be retrieved and be used by other applications (the same web app or other web app/mobile app etc...). Correct me if I am wrong.

I don't understand why I got the error... Thank you for your help!

UPDATE After creating a normal APIView, I manage to get the form rendered, and the permissions is well respected (status 403 Forbidden when a user tries to click on the Save button, when the he/she is not the owner. But still, the fields appeared as 'modifyable', ie, we can input text inside the area, but the Save button doesn't save the data if it's not the owner that does the modification.

myApp/views.py

class AnnounceDetail(APIView):  
    renderer_classes = [TemplateHTMLRenderer]
    template_name = 'myApp/announce_detail.html'
    permission_classes = (permissions.IsAuthenticatedOrReadOnly,
                          IsOwnerOrReadOnly,)



    def get(self, request, pk):
        announce = get_object_or_404(Announce, pk=pk)
        self.check_object_permissions(self.request, announce) # required for IsOwnerOrReadOnly to work fine see https://stackoverflow.com/questions/25554415/django-rest-framework-ignoring-my-isownerorreadonly-permissions
        serializer_context = {
            'request': Request(request),
        }
        serializer = AnnounceSerializer(announce, context=serializer_context)
        return Response({'serializer': serializer, 'announce': announce})

    def post(self, request, pk):
        announce = get_object_or_404(Announce, pk=pk)
        self.check_object_permissions(self.request, announce) # required for IsOwnerOrReadOnly to work fine see https://stackoverflow.com/questions/25554415/django-rest-framework-ignoring-my-isownerorreadonly-permissions
        serializer_context = {
            'request': Request(request),
        }
        serializer = AnnounceSerializer(announce, context=serializer_context, data=request.data)
        if not serializer.is_valid():
            return Response({'serializer': serializer, 'announce': announce})
        serializer.save()
        return HttpResponseRedirect(reverse('announces-list')) # redirect to URL that is associated with the name announces-list
0

1 Answer 1

2

views.py

from django.shortcuts import get_object_or_404
from rest_framework.response import Response

class AnnounceDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Announce.objects.all()
    serializer_class = AnnounceSerializer
    renderer_classes = [TemplateHTMLRenderer]
    template_name = 'myApp/announce_detail.html'
    permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly,)

    def retrieve(self, request, pk, *args, **kwargs):
        announce = get_object_or_404(Announce, pk=pk)
        serializer = self.get_serializer(announce)  # typo fixed
        return Response({'serializer': serializer, 'announce': announce})

announce_detail.html

{% load rest_framework %}
{% load staticfiles %}

{% block content %}

<form action="{% url 'announce-detail' pk=announce.pk %}" data-method="PUT">
    {% csrf_token %}
    {% render_form serializer %}
    <input type="submit" value="Save">
</form>

{% endblock content %}

<script>
    window.drf = {
        csrfHeaderName: "X-CSRFTOKEN",
        csrfCookieName: "csrftoken"
    };
</script>
<script src="{% static 'rest_framework/js/jquery-1.12.4.min.js' %}"></script>
<script src="{% static 'rest_framework/js/ajax-form.js' %}"></script>
<script src="{% static 'rest_framework/js/csrf.js' %}"></script>
<script src="{% static 'rest_framework/js/bootstrap.min.js' %}"></script>
<script src="{% static 'rest_framework/js/prettify-min.js' %}"></script>
<script src="{% static 'rest_framework/js/default.js' %}"></script>
<script>
    $(document).ready(function() {
        $('form').ajaxForm();
    });
</script>
Sign up to request clarification or add additional context in comments.

6 Comments

Thank you for your answer ! However, this would render the form, but once the button 'Save' is clicked, it directs me to '405 Method Not Allowed'. Moreover, all users can see a 'modifyable' form (even if the POST doesn't work), even when it's not their Announce. What I would like, is kind of exactly the same version as the Browsable API (ie, the fields are modifyable by only the owner of the Announce), but rendered in a HTML template.
I updated my answer, I added template(announce_detail.html). Can you check again ?
Thanks for the answer ! However, it still doesn't work, any click on the Save button does actually nothing, just refresh the input area to the previous value before modification, so the modification is not even saved. I updated my question, please check.
For a HTML rendering, should I better use the Django ModelForm instead ? However, when data are inputted, can it be converted in JSON format like the way DRF does ? I plan to use Ajax and other stuff later, so I would like to have data well organized in JSON format to easily retrieve and manipulate them.
It's working RetrieveUpdateDestroyAPIView in my project. I have no idea for your problem. You don't need to using APIView.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.