so I'm getting this weird issue. I have my simpleJWT auth working on the server but for some reason, I can't get
permission_classes=[permissions.IsAuthenticated]
to block the view from an anonymous user.
There are many similar posts but I can't figure out where the issue is.
from rest_framework import permissions
class UsersListView(ListView):
http_method_names = ['get']
permission_classes=[permissions.IsAuthenticated]
def get_queryset(self):
return UserModel.objects.all().exclude(id=self.request.user.id)
def render_to_response(self, context, **response_kwargs):
users: List[AbstractBaseUser] = context['object_list']
data = [{
"username": user.user_name,
"pk": str(user.pk)
} for user in users]
return JsonResponse(data, safe=False, **response_kwargs)
I've tried the dumbest approach first and removed allow any from here but no luck
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny',
],
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
)
}
Can you spot the issue?
Related
I'm trying to create a test for creating a new post, but I'm getting Error 403. I've read the questions on this topic, but either they were not about testing or the solution they provided did not work (I'll provide info about what I've tried so far).
Here is my code:
urls.py:
app_name = "blog_api"
urlpatterns = [
path("<str:pk>/", PostDetail.as_view(), name="detail_create"),
path("", PostList.as_view(), name="list_create"),
]
and my permissions.py:
class PostUserWritePermission(permissions.BasePermission):
message = "Editing posts is restricted to the admin and author of the post only."
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return request.user.is_superuser or request.user == obj.author
And here is my views.py:
class PostList(generics.ListCreateAPIView):
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
queryset = Post.post_objects.all()
serializer_class = PostSerializer
class PostDetail(generics.RetrieveUpdateDestroyAPIView):
permission_classes = [PostUserWritePermission]
queryset = Post.post_objects.all()
serializer_class = PostSerializer
And finally, my tests.py:
class PostTests(APITestCase):
def test_view_posts(self):
url = reverse("blog_api:list_create")
response = self.client.get(url, format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_create_post(self):
self.test_category = Category.objects.create(name="test category")
self.test_user = User.objects.create_user(
username="test_user",
password="test_password",
)
data = {
"title": "new",
"author": 1,
"excerpt": "new",
"content": "new",
"slug": "new",
}
url = reverse("blog_api:list_create")
response = self.client.post(url, data, format="json")
print(response.data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
So, when I test using py manage.py test command, the first test is ok, but the second one returns error 403, and when I tried to see the response message using print(response.data) this is what's printed:
{'detail': ErrorDetail(string='Authentication credentials were not provided.', code='not_authenticated')}
I should also mention that I have this setting in my settings.py file:
REST_FRAMEWORK = {
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework.permissions.IsAuthenticatedOrReadOnly",
],
}
I did some searching about this, this one question was a bit similar to my problem, but I create my user using create_user method, thus his solution is irrelevant to my question.
What am I missing here?
WHAT I"VE TRIED:
I tried to comment out the permision class PostUserWritePermission in the PostDetail view, but got the same error nonetheless.
Also I tried to solve this by adding TokenAuthentication to my settings, but this didn't work neither.
I'm still having this error when trying to do a post request from postman.
{
"username": [
"This field is required."
],
"password": [
"This field is required."
]
}
I can make the same post request successfully from my DRF localhost, but when i try on postman i get the error above.
How can I solve it?
Views.py
class PlayThingList(viewsets.ModelViewSet):
serializer_class = PlayThingSerializer
queryset = PlayThing.objects.all()
class UserViewset(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
Serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'password']
extra_kwargs = {'password': {
'write_only':True,
'required':True
}}
def create(self, validated_data):
user = User.objects.create_user(**validated_data)
Token.objects.create(user)
return user
urls.py
router = DefaultRouter()
router.register('playthings', PlayThingList, basename='playthings')
router.register('users', UserViewset)
urlpatterns = [
path('playmates/', include(router.urls)),
]
Project urls.py
from django.contrib import admin
from django.urls import path, include
from rest_framework.authtoken.views import obtain_auth_token
app_name = 'playthings'
urlpatterns = [
path('admin/', admin.site.urls),
path("", include('playthings.urls')),
path('auth/', obtain_auth_token)
]
UPDATE
I made some changes based on the error messages and guides in the comments and I can now create users.
Problem is, after sending the user credentials in the form, i get this error in postman.
Try adding the following in your POSTMAN
Headers section:
KEY
Value
Accept
application/json
Body section (choose raw or x-www-form-urlencoded):
KEY
Value
username
(your username)
password
(your password)
In your userviewset, you are using UserSerializer. This way you can not create users. To create a user you will have to extend registeruser functionality.
Check out the code from rest-framework and use the same logic in your create method of userviewset. if you want to register a user.
Registeruser is all together a different thing.
Rest framework by default has a url to register users, use that url, it will handle everything for you.
Problem Solved!
The issue was with the token create() method. I changed
create(user) to create(user=user)
ref: serializers.py
def create(self, validated_data):
user = User.objects.create_user(**validated_data)
Token.objects.create(user=user)
return user
Thank you to everyone that helped!
I have a project with JWT authentication in Django Rest Framework. Usually I require user to be authenticated but in the case of GET action (both list and retrieve) I would like everyone to be able to access it with no authentication required.
The code for this functionality is very simple:
class GetUserViewSet(viewsets.GenericViewSet,
mixins.ListModelMixin,
mixins.RetrieveModelMixin):
# allowed for everyone
serializer_class = UserSerializer
permission_classes = [permissions.AllowAny]
queryset = User.objects.all()
The permissions are set to allow any but there is probably some inconsistency with default auth class in Settings.py
# --------------------------REST-framework--------------------------
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticatedOrReadOnly'
],
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework_simplejwt.authentication.JWTAuthentication",
),
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
}
The last thing that could matter are my endpoints:
urlpatterns = [
path("", UserViewSet.as_view({"post": "create"}), kwargs={'quantity': 1}),
path("<int:quantity>/", UserViewSet.as_view({"post": "create"})),
path("", GetUserViewSet.as_view({"get": "list"})),
path("<int:pk>/", GetUserViewSet.as_view({"get": "retrieve"})),
path("<int:pk>/", UserViewSet.as_view({"put": "update", "delete": "destroy"})),
]
What I don't understand is that in other app where I have register functionality there is no such an error. I will show you this viewset:
class ApiUserViewSet(viewsets.GenericViewSet, mixins.CreateModelMixin):
serializer_class = ApiUserSerializer
permission_classes = [permissions.AllowAny]
queryset = ApiUser.objects.all()
#extend_schema(request=ApiUserSerializer, responses=TokenSerializer)
def create(self, request, *args, **kwargs):
api_user_serializer = self.get_serializer(data=request.data)
api_user_serializer.is_valid(raise_exception=True)
api_user = api_user_serializer.save()
refresh = RefreshToken.for_user(api_user)
token_serializer = TokenSerializer(
data={
"access": str(refresh.access_token),
"refresh": str(refresh)
}
)
token_serializer.is_valid(raise_exception=True)
headers = self.get_success_headers(token_serializer.data)
return Response(token_serializer.data, status=status.HTTP_201_CREATED, headers=headers)
If you have any idea what could be wrong please let me know!
Thank you
Inspecting the header from postman i notice, the allowed method does not include the POST. I'm unable to make request for unauthenticated routes, i get 403.
class LoginUserAccountView(generics.CreateAPIView):
serializer_class = LoginSerializer
permission_classes = [permissions.AllowAny]
def create(self, request, *args, **kwargs):
serializer = LoginSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
try:
user = User.objects.get(email=request.data['email'])
if user.check_password(request.data['password']):
serialized_user = UserSerializer(user).data
access_token = generate_access_token(user)
return Response(data={'access_token': access_token,
'user': serialized_user}, status=status.HTTP_200_OK)
else:
return Response({'errors': 'Invalid credentials'})
except User.DoesNotExist:
return Response({'errors': 'No user with such email!'})
Here is what my REST_FRAMEWORK looks like the settings.py
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.AllowAny',
),
# 'DEFAULT_AUTHENTICATION_CLASSES': (
# 'accounts.authentication.CustomJWTAuthentication',
# ),
'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',),
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 100
}
You should name your method create instead of post.
Method names reflect actions, not HTTP methods.
list - GET, multiple objects
retrieve - GET, single object using ID
create - POST
update - PUT
partial_update - PATCH
destroy - DELETE
Take a look at ViewSet example.
i'm a newbie of django-rest-framework, i try a very simple web app
one programe
url.py
urlpatterns = [
url(r'^admin/$', "app.views.admin_index"),
]
views.py
def admin_index(request):
print request
print type(request.user)
return render(request, "admin/index.html")
output is
AnonymousUser
<class 'django.utils.functional.SimpleLazyObject'>
anthoner programe
url.py
urlpatterns = [
url(r'^admin/$', AdminViewSet.as_view({'get':'list'})),
]
views.py
class AdminViewSet(viewsets.ViewSet):
permission_classes = (permissions.IsAdminUser,)
renderer_classes = (renderers.TemplateHTMLRenderer,)
def list(self, request):
print request
print type(request.user)
return Response(template_name='admin/index.html')
output is
admin
<class 'django.contrib.auth.models.User'>
so, request.user has two different output, most important is one is AnonymousUser another one is admin, why? something wrong?
=========solution========================
settings.py
REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.AllowAny'
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
)
}
it has 'rest_framework.authentication.BasicAuthentication', so it make request.user store in http header, but not in session, so django.contrib.auth's logout failed.
solution is only use 'rest_framework.authentication.SessionAuthentication'
class 'django.utils.functional.SimpleLazyObject' is a type of promise. When evaluated, it will act as a proxy to the delayed object. There's nothing wrong here; django uses this type in a lot of places to implement laziness.