I've the following class based view in django rest,
class UserRoom(views.APIView):
def add_user_to_persistent_room(self, request):
try:
user = User.objects.get(id=int(request.data['user_id']))
club = Club.objects.get(id=int(request.data['club_id']))
location = Location.objects.get(id=int(request.data['location_id']))
name = location.city + '-' + club.name
room, created = PersistentRoom.objects.get_or_create(name=name,
defaults={'club': club, 'location': location})
room.users.add(user)
room.save()
return Response(PersistentRoomSerializer(room).data, status=status.HTTP_201_CREATED)
except User.DoesNotExist:
return Response("{Error: Either User or Club does not exist}", status=status.HTTP_404_NOT_FOUND)
def find_all_rooms_for_user(self, request, **kwargs):
try:
user = User.objects.get(id=int(kwargs.get('user_id')))
persistent_rooms = user.persistentroom_set.all()
floating_rooms = user.floatingroom_set.all()
rooms = [PersistentRoomSerializer(persistent_room).data for persistent_room in persistent_rooms]
for floating_room in floating_rooms:
rooms.append(FloatingRoomSerializer(floating_room).data)
return Response(rooms, status=status.HTTP_200_OK)
except User.DoesNotExist:
return Response("{Error: User does not exist}", status=status.HTTP_404_NOT_FOUND)
This is my urls.py
urlpatterns = [
url(r'^rooms/persistent/(?P<user_id>[\w.-]+)/(?P<club_id>[\w.-]+)/(?P<location_id>[\w.-]+)/$',
UserRoom.add_user_to_persistent_room(),
name='add_user_to_persistent_room'),
url(r'^rooms/all/(?P<user_id>[\w.-]+)/$', UserRoom.find_all_rooms_for_user(), name='find_all_rooms')
]
When I run this I get the following error,
TypeError: add_user_to_persistent_room() missing 2 required positional arguments: 'self' and 'request'
I understand the reason for this error clearly, my question is how do I pass the request object in the urls.py?
I think you used Class Based Views the wrong way. Your method must be named get or post. For example:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import authentication, permissions
from django.contrib.auth.models import User
class ListUsers(APIView):
"""
View to list all users in the system.
* Requires token authentication.
* Only admin users are able to access this view.
"""
authentication_classes = (authentication.TokenAuthentication,)
permission_classes = (permissions.IsAdminUser,)
def get(self, request, format=None):
"""
Return a list of all users.
"""
usernames = [user.username for user in User.objects.all()]
return Response(usernames)
More info here: http://www.django-rest-framework.org/api-guide/views/#class-based-views
Related
I have the following view that requires a user to be authenticated to access it, otherwise redirects to the login page:
In my urls.py file:
from . import views
urlpatterns = [
path("settings", login_required(views.Settings.as_view()), name="settings"),
]
In my views.py file:
class Settings(View):
def get(self, request):
return render(request, "project/settings.html")
How can I test this view?
Here is what I tried:
class TestViews(TestCase):
def setUp(self):
self.user = User.objects.create(
username="testUser",
password="testPassword",
)
def test_settings_authenticated(self):
client = Client()
client.login(username="testUser", password="testPassword")
response = client.get("/settings")
self.assertEquals(response.status_code, 200)
self.assertTemplateUsed(response, "project/settings.html")
But this returns a 302 status, implying the user was not authenticated.
Instead of User.objects.create(...) use User.objects.create_user(...) since the create method will not automatically hash the password.
This solved my problem.
I am just getting started with Django Rest framework and want to create a feature such that it allows superuser to create a message in admin.py and allow it only to be seen by a certain group(s) ie. "HR","Managers","Interns" etc.
in other words, only a user belonging to "HR" group will be allowed to get data from view assigned to "HR" group by admin. I would like to have only one view that appropriately gives permission.
Something like
#views.py
class message_view(APIView):
def get(request):
user = request.user
group = get_user_group(user) #fetches user's respective group
try:
#if message assigned to 'group' then return API response
except:
#otherwise 401 Error
I need some guidance with this.
I propose you to define a permission in your app (<your-app>/permissions.py) as below:
class HasGroupMemberPermission(permissions.BasePermission):
message = 'Your custom message...'
methods_list = ['GET', ]
def has_permission(self, request, view):
if request.method not in methods_list:
return True
group_name ='put_your_desired_group_name_here'
if request.user.groups.filter(name__exact=group_name).exists():
return False
return True
Then, import the above permission in your views module (<your-app>/views.py) as well as the serializer of the Message model (let's assume MessageSerializer).
from rest_framework.generics import RetrieveAPIView
from .permissions import HasGroupMemberPermission
from .serializers import MessageSerializer
class MessageView(RetrieveAPIView):
# use indicative authentication class
authentication_classes = (BasicAuthentication, )
permission_classes = (HasGroupMemberPermission, )
serializer_class = MessageSerializer
lookup_url_kwarg = 'message_id'
def get_object(self):
""" Do your query in the db """
qs = Message.objects.get(pk=self.kwargs['message_id'])
return qs
def get(self, request, *args, **kwargs):
return super().get(request, *args, **kwargs)
401 will be returned if user is not authenticated. 403 will be returned if the logged in user is not member of the desired group. 200 will be returned if logged in user is member of the group and the message_id exists.
I am trying to implement an api version of a play button on a django website.
This is how far I got:
models.py
class Note(models.Model):
plays = models.ManyToManyField(settings.AUTH_USER_MODEL,blank=True,related_name='track_plays')
def get_play_url(self):
return "/play/{}/play".format(self.pk)
def get_api_like_url(self):
return "/play/{}/play-api-toggle".format(self.pk)
views.py
class TrackPlayToggle(RedirectView):
def get_redirect_url(self,*args,**kwargs):
id = self.kwargs.get("id")
obj = get_object_or_404(Note,id=id)
url_ = obj.get_absolute_url()
user = self.request.user
if user.is_authenticated():
if user in obj.plays.all():
obj.plays.add(user)
else:
obj.plays.add(user)
return url_
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import authentication,permissions
from rest_framework.decorators import api_view
class TrackPlayAPIToggle(RedirectView):
authentication_classes = (authentication.SessionAuthentication,)
permission_classes = (permissions.IsAuthenticated,)
#api_view(['GET', 'POST', ])
def get(self,request,format=None):
obj = get_object_or_404(Note,id=id)
url_ = obj.get_absolute_url()
user = self.request.user
updated = False
played = False
if user.is_authenticated():
if user in obj.plays.all():
played = True
obj.plays.add(user)
else:
played = True
obj.plays.add(user)
played = False
updated = True
data = {
"updated":updated,
"played":played
}
return Response(data)
urls.py
url(r'^(?P<id>\d+)/play/', TrackPlayToggle.as_view(), name='play-toggle'),
url(r'^api/(?P<id>\d+)/play/', TrackPlayAPIToggle.as_view(), name='play-api-toggle'),
Ive added the API Decorator, because without it, I get a TypeError:
get() got an unexpected keyword argument 'id'
and when I try to add id=None I get an AssertionError:
.accepted_renderer not set on Response
Is this because I used id instead of slug?
Thank you for any suggestions
I don't understand why you thought adding the #api_view decorator would solve your TypeError. That decorator is for function-based views; it has no use in class-based views, where you define which methods are supported by simply defining the relevant methods. Remove the decorator.
The way to solve the original problem is to add the id parameter to the method; and the way to solve the problem with the renderer is to inherit from the correct parent class, which should clearly not be RedirectView.
class TrackPlayAPIToggle(GenericAPIView):
authentication_classes = (authentication.SessionAuthentication,)
permission_classes = (permissions.IsAuthenticated,)
def get(self, request, id, format=None):
...
I'm writing an application with the Django Rest Framework.
I created a custom permission. I provided a message attribute to the custom permission, but still the default detail gets returned.
Let me give you my code.
permissions.py:
from annoying.functions import get_object_or_None
from rest_framework import permissions
from intquestions.models import IntQuestion
from ..models import Candidate, CandidatePickedIntChoice
CANDIDATE_ALREADY_ANSWERED = "This candidate already answered all questions."
class CandidateAnsweredQuestionsPermission(permissions.BasePermission):
"""
Permission to check if the candidate has answered all questions.
Expects candidate's email or UUID in the request's body.
"""
message = CANDIDATE_ALREADY_ANSWERED
def has_permission(self, request, view):
candidate = None
email = request.data.get("email", None)
if email:
candidate = get_object_or_None(Candidate, email=email)
else:
uuid = request.data.get("candidate", None)
if uuid:
candidate = get_object_or_None(Candidate, uuid=uuid)
if candidate:
picked_choices = CandidatePickedIntChoice.objects.filter(
candidate=candidate
).count()
total_int_questions = IntQuestion.objects.count()
if picked_choices >= total_int_questions:
return False
return True
views.py:
from annoying.functions import get_object_or_None
from rest_framework import generics, status
from rest_framework.response import Response
from ..models import Candidate, CandidatePickedIntChoice
from .permissions import CandidateAnsweredQuestionsPermission
from .serializers import CandidateSerializer
class CandidateCreateAPIView(generics.CreateAPIView):
serializer_class = CandidateSerializer
queryset = Candidate.objects.all()
permission_classes = (CandidateAnsweredQuestionsPermission,)
def create(self, request, *args, **kwargs):
candidate = get_object_or_None(Candidate, email=request.data.get("email", None))
if not candidate:
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
else:
serializer = self.get_serializer(candidate, data=request.data)
serializer.is_valid(raise_exception=True)
return Response(serializer.data, status=status.HTTP_200_OK)
Note: The app I'm building lets candidates answer questions. The reason I overwrote the create function like this, is so that candidates who haven't yet finished all questions are still able to answer all the questions.
Why is the permission message the default "Authentication credentials were not provided." instead of my own?
The message Authentication credentials were not provided. says that, you are not provided the credentials. It differs from credentials are wrong message
Next thing is, there is not attribute message for the BasePermission class, so it won't use your message attribute unless you forced. ( Source Code )
How to show the custom PermissionDenied message?
The PermissionDenied exception raised from permission_denied() method ove viewset, ( Source Code )
So your view should be like,
from rest_framework import exceptions
class CandidateCreateAPIView(generics.CreateAPIView):
# your code
def permission_denied(self, request, message=None):
if request.authenticators and not request.successful_authenticator:
raise exceptions.NotAuthenticated()
raise exceptions.PermissionDenied(detail=CANDIDATE_ALREADY_ANSWERED)
Per Tom Christie (author of DRF):
I'd suggest raising a PermissionDenied explicitly if you don't want
to allow the "unauthenticated vs permission denied" check to run.
He doesn't go on to mention explicitly as to where the best place for this would be. The accepted answer seems to do it in the view. However, IMHO, I feel like the best place to do this would be in the custom Permission class itself as a view could have multiple permissions and any one of them could fail.
So here is my take (code truncated for brevity):
from rest_framework import exceptions, permissions # <-- import exceptions
CANDIDATE_ALREADY_ANSWERED = "This candidate already answered all questions."
class CandidateAnsweredQuestionsPermission(permissions.BasePermission):
message = CANDIDATE_ALREADY_ANSWERED
def has_permission(self, request, view):
if picked_choices >= total_int_questions:
raise exceptions.PermissionDenied(detail=CANDIDATE_ALREADY_ANSWERED) # <-- raise PermissionDenied here
return True
I had the same problem, finally i found the key point:
Do not use any AUTHENTICATION
REST_FRAMEWORK = {
# other settings...
'DEFAULT_AUTHENTICATION_CLASSES': [],
'DEFAULT_PERMISSION_CLASSES': [],
}
I had the same problem, and i found a solution to it, you don't even need AllowAny Permission, just set the authentication_classes to be an empty array, in example:
class TestView(generics.RetrieveAPIView):
renderer_classes = (JSONRenderer,)
permission_classes = (isSomethingElse,)
serializer_class = ProductSerializer
authentication_classes = [] # You still need to declare it even it is empty
def retrieve(self, request, *args, **kwargs):
pass
Hope it still helps.
settings.py
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny',
],
'DEFAULT_AUTHENTICATION_CLASSES': [
],
}
In my case, this solution works.
I'm working on an app that has three "profile" models types. Each of these models has a foreign key to the contrib.auth.models User model.
I would like to have a single sign on for each model type and provide a redirect via classmethod depending on which "profile" model type is related to the logged in user.
Here's some pseudo-code to illustrate what I would like to do:
from django.contrib.auth import authenticate, login
from django.http import HttpResponse, HttpresponseRedirect
from lib.forms import Loginform #extends the built-in AuthenticationForm
def login_user(request):
if request.method == 'POST':
form = LoginForm(data=request.POST)
if form.is_valid():
cleaned_data = form.cleaned_data
user = authenticate(username=cleaned_data.get('username'), \
password=cleaned_data.get('password'))
if user:
login(request, user)
#Determine the content type of the model related to the user
#get the correct redirect value from an #classmethod
#called: after_login_redirect for that type and...
return HttpResponseRedirect(klass.after_login_redirect())
else:
response = form.errors_as_json()
return HttpResponse(json.dumps(response, ensure_ascii=False), \
mimetype='application/json')
Is it possible to leverage the ContentTypes framework to do this? or am I better off just writing a class resolver that loops over an array of the "profile" classes? I'm not seeing a way I can do this with ContentTypes from the User class, unless someone knows a workaround.
Thanks in advance,
Brandon
[SOLVED]
So, what I ended up doing was creating a UserContentType junction model that looks like this:
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
class UserContentType(models.Model):
user = models.ForeignKey(User)
content_type = models.ForeignKey(ContentType)
Then, I made a pre-save signal to fire a get or create if the instance of one of the three "profile" models I have doesn't have an id:
from django.contrib.contenttypes.models import ContentType
from lib.models import UserContentType
def set_content_type_for_user(sender, **kwargs):
instance = kwargs.get('instance')
content_type = ContentType.objects.get_for_model(instance)
user = instance.user
if not instance.id:
user_content_type, \
created = UserContentType.objects.get_or_create(user=user, \
content_type=content_type, \
defaults={'user' : user, 'content_type' : content_type})
Then, in my custom login view which is posted to via ajax, I can get the content type associated with the User instance and get the URL to redirect to from the 'after_login_redirect()' classmethod on that content type. :)
def login_user(request):
if request.method == 'POST':
form = LoginForm(data=request.POST)
if form.is_valid():
cleaned_data = form.cleaned_data
user = authenticate(username=cleaned_data.get('username', ''),
password=cleaned_data.get('password', ''))
if user:
login(request, user)
user_content_type = UserContentType.objects.get(user=user)
redirect_path =
user_content_type.content_type.\
model_class().after_login_redirect_path()
response = {'redirect_path' : redirect_path}
else:
response = form.errors_as_json()
return HttpResponse(json.dumps(response, \
ensure_ascii=False), mimetype='application/json')
This way, I don't have to monkeypatch User. I hope this helps someone out. If there's a way I can improve on this, I'd love to hear some ideas.
Thanks,
Brandon