I have a regular ModelViewSetin my project, and it works perfectly with GET and POST requests, but it fails with PUT, returning this error:
{
"detail": "CSRF Failed: CSRF token missing or incorrect."
}
This is my urls.py:
from django.urls import path,re_path,include
from django.utils.text import slugify,camel_case_to_spaces
from PaymentsManagerApp import views, models
from rest_framework import routers
APP_NAME = 'PaymentsManagerApp'
router = routers.DefaultRouter()
router.register(r'payments', views.PaymentViewSet)
payments_list = views.PaymentViewSet.as_view({
'get':'list',
'post':'create'
})
payment_detail = views.PaymentViewSet.as_view({
'get':'retrieve',
'put':'update',
'patch':'partial_update',
'delete':'destroy'
})
def urlpattern_from_route(route):
if "regex" in route and route['regex']:
path_method = re_path
else:
path_method = path
return path_method(route['path'],route['view'].as_view(),name=route['name'] if "name" in route else None)
routes_views = list(map(urlpattern_from_route,routes))
route_services = [
payment_detail = views.PaymentViewSet.as_view({
'get':'retrieve',
'put':'update',
'patch':'partial_update',
'delete':'destroy'
})
route_services = [
path('payments/', payments_list, name='rest_payments_list'),
path('payments/<int:pk>/', payment_detail, name='rest_payment_detail'),
]
urlpatterns = routes_views + route_services
This is my views.py:
import os
import json
from datetime import datetime, timedelta
from django.shortcuts import render
from PaymentsManagerApp import urls, models, serializers
from FrontEndApp import urls as Fronturls
from django.shortcuts import render,redirect
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.contenttypes.models import ContentType
from django.views.generic import View
from django.contrib.auth.models import Permission
from GeneralApp.utils import get_catalogs
from django.contrib.staticfiles import finders
from django.utils.text import slugify,camel_case_to_spaces
from rest_framework import viewsets, permissions
from rest_framework.response import Response
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.response import Response
from rest_framework.filters import OrderingFilter, SearchFilter
from django.db.models import Q
class PaymentViewSet(viewsets.ModelViewSet):
exclude_from_schema = True
permission_classes = (permissions.IsAuthenticated,)
queryset = models.Payment.objects.all()
serializer_class = serializers.PaymentSerializer
filter_backends = (DjangoFilterBackend, SearchFilter, OrderingFilter,)
search_fields = ('payment_type', 'creation_user__username', 'provider__name', 'invoice', 'payment_method_type', 'payment_document_number')
filter_fields = ('id', 'payment_type', 'creation_user', 'provider', 'is_payment_requested', 'is_paid', 'payment_method_type')
When I send a GET or a POST to payments_manager/payments/, it works perfectly. Also when I send a GET to pyments_manager/payments/<int:pk>/it works well.
The problem is when I send a PUT to payments_manager/payments/<int:pk>/, because I get the following:
I don't know why but DRF loses the logged user information (you can see the log in label, instead of the username).
EDIT
This is my REST_FRAMEWORK in settings.py:
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAuthenticated',),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
),
'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',),
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 20,
'DEFAULT_METADATA_CLASS': 'rest_framework.metadata.SimpleMetadata'
}
EDIT
I found that the error rises only if I access the endpoint directly from the browser using DRF default interfase (127.0.0.1:8000/es/payments_manager/payments/1/):
My PUT requests work perfectly from my javascript ajax.
In order for to use session auth AND do a POST (is a little weird) / PUT / PATCH / DELETE / etc. you MUST pass a header.
see: https://github.com/django/django/blob/8b3f1c35dd848678225e8634d6880efeeab5e796/django/middleware/csrf.py#L306
I also created a little test for you:
https://gist.github.com/kingbuzzman/20dffbc34d22a899661ac3c065e3f747#file-django_rest_framework_session_vs_token-py-L209
response = self.client.post('/session-login/', data={'username': 'user', 'password': 'pass'})
self.assertEqual(302, response.status_code)
self.assertIn('csrftoken', response.cookies)
self.assertIn('sessionid', response.cookies)
# Don't want to go through the trouble of having to get the CSRF from the login form
self.client.handler.enforce_csrf_checks = True
csrftoken = self.client.cookies.get('csrftoken').value
# NOTE: The only reason this works it's because we're passing a header along with the request.
response = self.client.patch('/payments/%s/' % (self.payment.id), content_type='application/json',
data=json.dumps({'is_paid': 'Y'}), HTTP_X_CSRFTOKEN=csrftoken)
self.assertEqual(200, response.status_code)
self.assertEqual('Y', response.json()['is_paid'])
# NOTE: The reason this DOES NOT works it's because we're NOT passing a header along with the request.
response = self.client.patch('/payments/%s/' % (self.payment.id), content_type='application/json',
data=json.dumps({'is_paid': 'N'}))
self.assertEqual(403, response.status_code)
Edit.
Added a user, logged in, navigated to localhost/payments/ and added a record, then went to the record localhost/payments/1/ and updated it (PUT). Everything works. Please add your django/drf versions.
Related
I have been trying for hours but cannot figure out how to pass a url argument through an APIRequestFactory put request. I have tried it through Postman when running my server and the url variable is passed just fine, but when I run it in my tests it stops working.
What I mean is that when I send a Postman PUT request to '/litter/1/' it will successfully take in the 1 as the variable litterId since my url is setup like this
path('litter/', include('apps.litter.urls')),
and
path('<int:litterId>/', LitterView.as_view(), name='litter-with-id')
But when I try and send an APIRequestFactory put request to that same url, for some reason the 1 will not go through as the litterId anymore.
Some relevant pieces of code...
My top level url.py
from rest_framework.authtoken import views
from apps.litter.views import LitterView
urlpatterns = [
path('admin/', admin.site.urls),
path('auth/', include('apps.my_auth.urls')),
path('litter/', include('apps.litter.urls')),
]
This is my app specific urls.py
from .views import LitterView
urlpatterns = [
path('', LitterView.as_view(), name='standard-litter'),
path('<int:litterId>/', LitterView.as_view(), name='litter-with-id'),
]
Here is my views.py
import json
from django.contrib.auth.models import User
from django.db import IntegrityError
from django.views.decorators.csrf import csrf_exempt
from rest_framework import authentication, permissions
from rest_framework.parsers import JSONParser
from rest_framework.permissions import IsAuthenticated
from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response
from rest_framework.views import APIView
from django.db import models
from .models import Litter
from .serializers import LitterSerializer
##csrf_exempt
class LitterView(APIView):
"""
View for litter related requests
* Requres token auth
"""
permission_classes = (IsAuthenticated,)
authentication_classes = [authentication.TokenAuthentication]
renderer_classes = [JSONRenderer]
def put(self, request, litterId=0):
"""
Updates an old litter
"""
try:
litterModel = Litter.objects.get(user=request.user, id=litterId)
except Litter.DoesNotExist:
returnData = {'status': 'fail',
'error': 'Could not find object with that id.'}
return Response(returnData)
serializer_class = LitterSerializer
serialized = LitterSerializer(litterModel, data=request.data)
if serialized.is_valid():
litterModel = serialized.save()
returnData = {'status': 'okay',
'litter': [serialized.data]}
return Response(returnData)
else:
return Response(serialized.errors, status=400)
And here is the relevant test.
def test_easy_successful_put_type(self):
"""
Testing a simple put
"""
user = UserFactory()
amount = 40
amountChange = 20
litter = LitterFactory(user=user, amount=amount)
data = {'typeOfLitter': litter.typeOfLitter,
'amount': litter.amount + amountChange,
'timeCollected': litter.timeCollected}
url = '/litter/' + str(litter.id) + '/'
request = self.factory.put(url, data, format='json')
force_authenticate(request, user=user)
view = LitterView.as_view()
response = view(request).render()
responseData = json.loads(response.content)
No matter what I do, I cannot get the int:litterId to get passed in, the put function always has the default value of 0. Any help would be greatly appreciated.
Your problem is here:
response = view(request).render()
You are manually passing the request to the view, also not passing the kwarg litterId, instead use APIClient and make a put request to the url. First import the required modules:
from django.urls import reverse
from rest_framework.test import APIClient
then:
user = UserFactory()
amount = 40
amountChange = 20
litter = LitterFactory(user=user, amount=amount)
data = {
'typeOfLitter': litter.typeOfLitter,
'amount': litter.amount + amountChange,
'timeCollected': litter.timeCollected
}
url = reverse('litter-with-id', kwargs={'litterId': litter.id})
client = APIClient()
client.force_authenticate(user=user)
response = client.put(url, data, format='json')
I'm trying to add an API using the Django REST framework to an existing codebase which uses the Django OAuth2 toolkit. The existing views make use of the fact that the OAuth2 toolkit's backend modifies the behavior of Django's login_required decorator so that it uses OAuth2 authentication. The function-based views look similar to the one in the tutorial example:
from django.contrib.auth.decorators import login_required
from django.http.response import HttpResponse
#login_required()
def secret_page(request, *args, **kwargs):
return HttpResponse('Secret contents!', status=200)
I'm trying to adapt the example given on https://django-oauth-toolkit.readthedocs.io/en/latest/rest-framework/getting_started.html to my situation, but I'm finding it's not an exact correspondence, because the example uses the DRF's ModelViewSet classes whereas my current view uses a (less-integrated) generic class-based view:
from rest_framework import generics
from ..models import Session
from ..serializers import SessionSerializer
class SessionDetail(generics.UpdateAPIView):
queryset = Session.objects.all()
serializer_class = SessionSerializer
where I've set the default permission class to IsAuthenticated in settings.py:
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}
This should allow a Session object to be updated using PATCH requests, but it appears to return a 403 Forbidden response for use cases similar to the ones for the current views decorated with login_required.
How can I update the SessionDetail view so that it behaves the same as a function view decorated with login_required? I suspect that I need to use the TokenHasReadWriteScope permission class, since this appears to be the only one for which the required_scopes is optional. (The views decorated with login_required don't provide required_scopes either).
Would it be like this:
from rest_framework import generics
from rest_framework.permissions import IsAuthenticated
from oauth2_provider.contrib.rest_framework import OAuth2Authentication, TokenHasReadWriteScope
from ..models import Session
from ..serializers import SessionSerializer
class SessionDetail(generics.UpdateAPIView):
authentication_classes = [OAuth2Authentication]
permission_classes = [TokenHasReadWriteScope]
queryset = Session.objects.all()
serializer_class = SessionSerializer
Also, would I have to update my REST_FRAMEWORK setting with a DEFAULT_AUTHENTICATION_CLASSES like this:
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
'DEFAULT_AUTHENTICATION_CLASSES': (
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
)
}
Right now if I run the server and try to make a patch request with the browsable API, I get a 401 Unauthorized error:
Also if I Log in using a username and password that I just created using python manage.py createsuperuser, it goes straight back to this 401 page instead of one with a form to make a PATCH request as I'd expect.
Any ideas on how to fix this?
I am getting a 403 response in my test script which uses Django REST and OAuth2. I am using force_authenticate.
In urls.py:
urlpatterns = [
url(r'^user-id/$', views.UserIDView.as_view(), name='user-id'),
...
In views.py:
from oauth2_provider.ext.rest_framework import TokenHasReadWriteScope
class StdPerm(TokenHasReadWriteScope):
pass
StdPermClasses = (IsAuthenticated, StdPerm)
class UserIDView(APIView):
permission_classes = StdPermClasses
renderer_classes = (JSONRenderer,)
def get(self, request, format=None):
return Response({'id': request.user.id})
In tests.py:
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from rest_framework import status
from rest_framework.test import APITestCase
class CreateUserTest(APITestCase):
def setUp(self):
self.user = User.objects.create_user('daniel', 'daniel#test.com',
password='daniel')
self.user.save()
def test_get_user_id(self):
self.client.login(username='daniel', password='daniel')
self.client.force_authenticate(user=self.user)
response = self.client.get(reverse('user-id'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
Usually I use curl which works no problem:
curl -X GET "http://127.0.0.1:8000/user-id/" -H "Authorization: Bearer b3SlzXlpRSxURyh2BltwdHmhrlqNyt"
Update I changed some lines in test_get_user_id:
token = Token.objects.create(user=self.user)
self.client.force_authenticate(user=self.user, token=token)
Now I get the error:
assert False, ('TokenHasScope requires either the' AssertionError:
TokenHasScope requires either the`oauth2_provider.rest_framework
.OAuth2Authentication` authentication class to be used.
I found a solution to this problem. Basically my code was missing two things, namely the OAuth2 application record and an access token specific to OAuth2. I added the following to setUp:
app = Application(
client_type='confidential',
authorization_grant_type='password',
name='MyAppTest',
user_id=1
)
app.save()
...for generating a suitable access token:
app = Application.objects.get(name='MyAppTest')
token = generate_token()
expires = now() + timedelta(seconds=oauth2_settings. \
ACCESS_TOKEN_EXPIRE_SECONDS)
scope = 'read write'
access_token = AccessToken.objects.create(
user=self.user,
application=app,
expires=expires,
token=token,
scope=scope
)
...and then to use the token:
self.client.force_authenticate(user=self.user, token=access_token)
The import section ended up like so:
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from rest_framework import status
from rest_framework.test import APITestCase
from oauth2_provider.settings import oauth2_settings
from oauthlib.common import generate_token
from oauth2_provider.models import AccessToken, Application
from django.utils.timezone import now, timedelta
This worked for me
from oauth2_provider.settings import oauth2_settings
from oauth2_provider.models import get_access_token_model,
get_application_model
from django.contrib.auth import get_user_model
from django.utils import timezone
from rest_framework.test import APITestCase
Application = get_application_model()
AccessToken = get_access_token_model()
UserModel = get_user_model()
class Test_mytest(APITestCase):
def setUp(self):
oauth2_settings._SCOPES = ["read", "write", "scope1", "scope2", "resource1"]
self.test_user = UserModel.objects.create_user("test_user", "test#example.com", "123456")
self.application = Application.objects.create(
name="Test Application",
redirect_uris="http://localhost http://example.com http://example.org",
user=self.test_user,
client_type=Application.CLIENT_CONFIDENTIAL,
authorization_grant_type=Application.GRANT_AUTHORIZATION_CODE,
)
self.access_token = AccessToken.objects.create(
user=self.test_user,
scope="read write",
expires=timezone.now() + timezone.timedelta(seconds=300),
token="secret-access-token-key",
application=self.application
)
# read or write as per your choice
self.access_token.scope = "read"
self.access_token.save()
# correct token and correct scope
self.auth = "Bearer {0}".format(self.access_token.token)
def test_success_response(self):
url = reverse('my_url',)
# Obtaining the POST response for the input data
response = self.client.get(url, HTTP_AUTHORIZATION=self.auth)
# checking wether the response is success
self.assertEqual(response.status_code, status.HTTP_200_OK)
Now everything will work as expected. Thanks
I'm trying to setup a class based view in Django Rest Framework.
This is my urls.py -
from django.conf.urls import patterns, include, url
from rest_framework.urlpatterns import format_suffix_patterns
from django.contrib import admin
from TidalDEV import views
admin.autodiscover()
urlpatterns = patterns('',
url(r'^test/(?P<pk>[0-9]+)/$', views.TESTXMLDetail.as_view()),
)
And this is my view -
import os, tempfile, zipfile
from django.shortcuts import render
from django.core import serializers
from django.core.serializers import serialize
from django.core.servers.basehttp import FileWrapper
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.renderers import JSONRenderer, XMLRenderer
from rest_framework.parsers import JSONParser
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated, AllowAny
from itertools import chain
from TAPI.renderers import *
from TAPI.models import *
from TAPI.serializers import JobdtlSerializer, JobmstSerializer, TrgjobSerializer, JobdepSerializer
class TESTXMLDetail(APIView):
permission_classes = (AllowAny,)
"""
Retrieve, update or delete a snippet instance.
"""
def get_object(self, pk):
try:
mst = Jobmst.objects.db_manager('AdmiralDEV').raw("""
query1""", [pk])
dtl = Jobdtl.objects.db_manager('AdmiralDEV').raw("""
query2""", [pk])
dep = Jobdep.objects.db_manager('AdmiralDEV').raw("""
query3""", [pk])
trg = Trgjob.objects.db_manager('AdmiralDEV').raw("""
query4""", [pk])
except Jobmst.DoesNotExist:
return HttpResponse(status=404)
def get(self, request, pk, format=None):
jobmststring = JobmstSerializer(mst)
jobdtlstring = JobdtlSerializer(dtl)
jobdepstring = JobdepSerializer(dep, many=True)
trgjobstring = TrgjobSerializer(trg, many=True)
jobmst_serialized = {'jobmst': jobmststring.data}
jobdtl_serialized = {'jobdtl': jobdtlstring.data}
jobdep_serialized = [{'jobdep':item} for item in jobdepstring.data]
trgjob_serialized = [{'trgjob':item} for item in trgjobstring.data]
jobgroup = jobmst_serialized, jobdtl_serialized, jobdep_serialized, trgjob_serialized
jobgroupresponse = TESXMLResponse(jobgroup)
return jobgroupresponse
When I run the URL all I get is the XML format but then it says -
<detail>Method 'GET' not allowed.</detail>
I've tried setting the permission class in the view as above and I've also put the following lines in my settings.py what gives?
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.AllowAny',
),
You're getting this error because the DRF APIView doesn't have a get method (or a method for any HTTP request type actually).To get your code working you can either rename your get_object method to get (making sure you add the expected parameters request and format), or you can write a get method to return your response:
class TESTXMLDetail(APIView):
def get(self, request, pk, format=None):
return self.get_object(pk)
def get_object(self, pk):
...
...
I have used custom field in django registration form , every thing is working fine but whenever it try to redirect , it shows following error.
I dont know what i missed here.
NoReverseMatch at /accounts/register/
Reverse for 'registration_complete' with arguments '()' and keyword arguments '{}' not found.
I tried following
URL
url(r'^accounts/register/$', register, {'backend': 'registration.backends.default.DefaultBackend','form_class': RegistrationFormEx}, name='registration_register'),
registrationForm.py
from django import forms
from registration.forms import RegistrationForm
from django.utils.translation import ugettext_lazy as _
from registration.models import RegistrationProfile
class RegistrationFormEx(RegistrationForm):
#ADD ALL CUSTOM FIELDS BELOW
name=forms.CharField()
models.py
import hashlib
import datetime
import hmac
from django.db import models
from django.contrib.auth.models import User
from ecpCommon.models import StateModel
from ecpCommon.enum import enumauto
from ecpPayments.models import PaymentCard
from registration.signals import user_registered
from apps.ecpUser.models import UserProfile
from apps.ecpMerchant.registrationForm import RegistrationFormEx
from apps.ecpCommon.thumbs import ImageWithThumbsField
class MerchantProfile(StateModel):
name = models.CharField('Merchant Name', max_length=64)
def user_created(sender, user, request, **kwargs):
form = RegistrationFormEx(data=request.POST)
new_user = User.objects.get(username=request.POST['username'])
digest=hmac.new(str(request.POST['username'])+str(request.POST['password1']), str(request.POST['password1']),hashlib.sha1).hexdigest()
new_profile = UserProfile(user=new_user,api_key=digest)
new_profile.save()
#now add other fields including password hash as well
uid = new_profile.id
merchant_profile = MerchantProfile(user_id=uid,
create_time=datetime.datetime.now(),
modified_time=datetime.datetime.now(),
payment_card_id=uid,
current_state=1,
name=request.POST['name'],
)
merchant_profile.save()
return new_user
user_registered.connect(user_created)
It's likely because of the registration success redirection in your views is redirecting to a URL: registration_complete, that does not exist.
To fix it, you should add a url record similar to the one you have for registration_register
url(r'^accounts/register/$', register, {'backend': 'registration.backends.default.DefaultBackend','form_class': RegistrationFormEx}, name='registration_register'),
that points to the correct url with name=registration_complete.