2

I'm trying to convert the example app from the Rest Framework tutorial to the an app with templates. The problem is that I cannot create a delete button in a detail view of a single record. My views.py are as follows:

class SnippetViewSet(viewsets.ModelViewSet):

queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
                      IsOwnerOrReadOnly,)
renderer_classes = [renderers.TemplateHTMLRenderer]
template_name = 'list.html'


def list(self, request, **kwargs):
    print(request.method, request.user)
    queryset = Snippet.objects.all()
    serializer = SnippetSerializer(context={'request': request})
    return Response({'queryset': queryset, 'serializer': serializer}, template_name='list.html')


def retrieve(self, request, *args, **kwargs):
    print(request.method, 'retrieve')
    queryset = self.get_object()
    serializer_class = SnippetSerializer(queryset, context={'request': request})
    return Response({'queryset': queryset, 'serializer': serializer_class}, template_name='retrieve.html')


def create(self, request, *args, **kwargs):
    print(request.method, 'create')
    serializer = SnippetSerializer(instance=None, context={'request': request}, data=request.data)
    if serializer.is_valid():
        serializer.save(owner=self.request.user)
        return redirect('snippet-list')


def post(self, request, *args, **kwargs):
    print(request.method, 'post')
    queryset = self.get_object()
    serializer = SnippetSerializer(queryset, data=request.data, context={'request': request})
    if not serializer.is_valid():
        return Response({'serializer': serializer, 'queryset': queryset})
    serializer.save()
    return redirect('snippet-list')


def destroy(self, request, *args, **kwargs):
    instance = self.get_object()
    self.perform_destroy(instance)
    return Response(status=status.HTTP_204_NO_CONTENT)

def perform_destroy(self, instance):
    instance.delete()

urls.py remained unchanged:

snippet_list = SnippetViewSet.as_view({
'get': 'list',
'post': 'create',
})

snippet_detail = SnippetViewSet.as_view({
    'get': 'retrieve',
    'put': 'update',
    'patch': 'partial_update',
    'delete': 'destroy',
})
user_list = UserViewSet.as_view({ 'get': 'list'})
user_detail = UserViewSet.as_view({
    'get': 'retrieve',
    'put': 'update',
    'patch': 'partial_update',
    'delete': 'destroy'
})
urlpatterns = format_suffix_patterns([
    url(r'^', snippet_list, name='snippet-list'),
    url(r'^snippets/$', snippet_list, name='snippet-list'),
    url(r'^snippets/(?P<pk>[0-9]+)$', snippet_detail, name='snippet-detail'),
    url(r'^users/$', user_list, name='user-list'),
    url(r'^users/(?P<pk>[0-9]+)/$', user_detail, name='user-detail'),
])

The form template:

<form action="{% url 'snippet-detail' pk=queryset.pk %}" method="post">
{% csrf_token %}
{% render_form serializer %}
<input type="submit" value="Save">
<a href="{% url 'snippet-detail' pk=queryset.pk %}" methods="delete">delete</a>

All I can get after clicking the delete button is reload of the retrieve view. Everything except the delete method works fine. I know that there's something petty that I miss, but if you could just point me in the right direction to look into I'd be grateful.

ZmuA
  • 151
  • 1
  • 13

1 Answers1

1

The problem is that your destroy view is accessed by using the delete method in a request to the snippet-detail url. But that method is not available to html forms

So you need to another way to access this view, something like:

snippet_destroy = SnippetViewSet.as_view({
    'post': 'destroy',
})

urlpatterns = format_suffix_patterns([
    url(r'^snippets/(?P<pk>[0-9]+)/delete$', snippet_destroy, name='snippet-delete'),
])

and use

<form action="{% url 'snippet-delete' pk=queryset.pk %}" method="post">
    {% csrf_token %}
    <input type="submit" value="Delete">
</form>

You maybe also want your destroy view to return a response with a redirection to another url.


Original answer:

You need to use a form for both actions, one for the update, and one for the delete. A form can make a request with the delete method, a a tag can not.

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

<form action="{% url 'snippet-detail' pk=queryset.pk %}" method="delete">
    {% csrf_token %}
    <input type="submit" value="Delete">
</form>
Nadège
  • 931
  • 6
  • 12
  • 1
    That won't do, unfortuantely. The button generates a link with token like: 'http://localhost:8000/snippets/15/?csrfmiddlewaretoken=' but stays on the same page. – ZmuA Jul 27 '17 at 08:27
  • could you check the details of the request made when clicking the button ? specifically the Method and the response. Your destroy view return a HTTP_204_NO_CONTENT response, which I think explain why you stay an the same page after submitting the form. [see:](https://stackoverflow.com/questions/3283071/is-there-a-way-to-ignore-form-response) – Nadège Jul 27 '17 at 08:46
  • I'm getting GET method. Not sure about the kind of response but HTTP response is 200. – ZmuA Jul 27 '17 at 09:04
  • uh my bad, `delete` method is only available in XMLHttpRequest (i.e. AJAX). Not in forms. So you need to change the way you access your destroy view. – Nadège Jul 27 '17 at 09:11
  • I've already tried that - no luck. The app is unable to find the 'def destroy'. I even tried to rename the definition to delete and decorate it with @detail_route(methods='destroy'). – ZmuA Jul 27 '17 at 09:38