Forbidden HTTP 403 DRF + ReactJS request.session - python

I am working on a Quiz Application using DjangoRestFramework and ReactJS. My App is comprised of two apps, api and frontend. In my api app, I have multiple API views for many different things. One of my API Views is called JoinQuiz. When I call it on my frontend, I get this error in the console:
Forbidden: /api/join-quiz
[16/Dec/2020] "POST /api/join-quiz HTTP/1.1" 403 58
I don't think my problem is due to a CSRF error because my other API views are working perfectly fine. I may be wrong on this point.
[16/Dec/2020] "GET /api/get-question?id=3 HTTP/1.1" 200 10009
[17/Dec/2020 01:15:47] "POST /api/create-quiz HTTP/1.1" 201 11959
I suspect that my request.session may be the issue because when I go directly to /api/join-quiz and make a POST request with my code, nothing goes wrong and I have a successful post.
Files
Views.py
class QuizView(generics.ListAPIView):
queryset = Quiz.objects.all()
serializer_class = QuizSerializer
class GetQuiz(APIView):
""" Searches for a quiz given its code and returns the Quiz with is_host info"""
serializer_class = QuizSerializer
lookup_url_kwarg = 'code'
def get(self, request, format=None): # This is an HTTP GET request
code = request.GET.get(self.lookup_url_kwarg)
if code != None: # Check if code is not equal to None
quiz = Quiz.objects.filter(code=code)
if len(quiz) > 0: # If there is a quiz...
data = QuizSerializer(quiz[0]).data
data['is_host'] = self.request.session.session_key == quiz[0].host
return Response(data, status=status.HTTP_200_OK)
return Response({'Quiz not found': 'Invalid Code'}, status=status.HTTP_404_NOT_FOUND)
return Response({'Bad Request': 'Code Parameter not found in request'}, status=status.HTTP_400_BAD_REQUEST)
class CreateQuizView(APIView):
"""Creates A new Quiz given nested question and answer data"""
serializer_class = QuizSerializer
def post(self, request, format=None):
""" Create the User's Account first"""
if not self.request.session.exists(self.request.session.session_key):
self.request.session.create()
data = request.data
data['host'] = self.request.session.session_key
serializer = self.serializer_class(data=data)
if serializer.is_valid():
quiz = serializer.create(validated_data=data)
self.request.session['quiz_code'] = quiz.code
return Response(
self.serializer_class(quiz).data,
status=status.HTTP_201_CREATED
)
return Response({'Bad Request': 'Invalid Data'}, status=status.HTTP_400_BAD_REQUEST)
class JoinQuiz(APIView):
"""Join a quiz based on the quiz code"""
lookup_url_kwarg = 'code'
def post(self, request, format=None):
if not self.request.session.exists(self.request.session.session_key):
self.request.session.create()
print(self.request.session.session_key)
code = request.data.get(self.lookup_url_kwarg)
if code != None:
quiz_result = Quiz.objects.filter(code=code)
if len(quiz_result) > 0:
self.request.session['quiz_code'] = code
return Response({'message': 'Quiz Joined!'}, status=status.HTTP_200_OK)
return Response({'Quiz Not Found': 'Invalid Quiz Code'}, status=status.HTTP_404_NOT_FOUND)
return Response({'Bad Request': 'Invalid Post Data'}, status=status.HTTP_400_BAD_REQUEST)
serializers.py
class QuizSerializer(serializers.ModelSerializer):
questions = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = Quiz
fields = ['id', 'code', 'questions', 'name']
def create(self, validated_data):
questions_data = validated_data.pop('questions')
quiz = Quiz.objects.create(**validated_data)
for question_data in questions_data:
answers_data = question_data.pop('answers')
question = Question.objects.create(quiz=quiz, **question_data)
for answer_data in answers_data:
Answer.objects.create(question=question, **answer_data)
return quiz
Frontend Request
const quizButtonPressed = () => {
const requestOptions = {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({
code: quizCode
})
};
fetch('/api/join-quiz', requestOptions)
.then((response) => {
if (response.ok) {
props.history.push(`/play/${quizCode}`);
} else {
setError("Quiz not found")
}
})
.catch((error) => console.log(error));
}
EDIT
I followed the solution from #Blusky and it worked!
Following the Django docs link from #Blusky solved the problem: https://docs.djangoproject.com/en/3.1/ref/csrf/#ajax

Even though you checked it, it might be a CSRF problem. You should check the body of the 403 Error, it might contains further information.
Only when authenticated, POST request on Django requires a CSRF token (it might be why your first POST is working)
If it's the case, you can check this snippet: https://docs.djangoproject.com/en/3.1/ref/csrf/#ajax

Related

How to use update method in viewsets in Django Rest Framework and perform some task(like sending mail) along with it?

I am working on a simple performance management system with react on frontend and django on the backend. They are supervisors who can give reviews to supervisees and supervisees can respond. I want all employees to receive email when they receive reviews from their supervisors and all the supervisors to receive email when their reviews are responded. For reviews and responses I am using two different serializers but same model.
Serializer for Response is:
class ResponseSerializer(serializers.ModelSerializer):
supervisor_name = serializers.SerializerMethodField('get_supervisor_name')
supervisor_email = serializers.SerializerMethodField('get_supervisor_email')
supervisee_name = serializers.SerializerMethodField('get_supervisee_name')
supervisee_email = serializers.SerializerMethodField('get_supervisee_email')
class Meta:
model = Review
fields = (
'id', 'review_text', 'response_text', 'date_of_review', 'date_of_response', 'supervisor', 'supervisor_name',
'supervisor_email', 'supervisee', 'supervisee_name', 'supervisee_email')
read_only_fields = ('review_text', 'date_of_review', 'supervisor', 'supervisee')
def get_supervisor_name(self, obj):
return obj.supervisor.first_name + " " + obj.supervisor.last_name
def get_supervisor_email(self, obj):
return obj.supervisor.email
def get_supervisee_name(self, obj):
return obj.supervisee.first_name + " " + obj.supervisee.last_name
def get_supervisee_email(self, obj):
return obj.supervisee.email
For sending mail I am using send_mail method from django.core And I am using Viewsets for Reviews and Responses.
Now Response operation will always be an update operation because Response will always be used to update existing Review object in which response_text field will be updated.
class ResponseViewSet(viewsets.ModelViewSet):
queryset = Review.objects.all()
permission_classes = [
# permissions.IsAuthenticated,
permissions.AllowAny,
]
serializer_class = ResponseSerializer
def update(self, request, *args, **kwargs):
serializer = ResponseSerializer(data=request.data)
if serializer.is_valid():
supervisor = serializer.data["supervisor_name"]
supervisee = serializer.data["supervisee_name"]
query = serializer.save()
mail_text = "Hi {}\n\nYou got a response for your 1:1 from {}.\n\nClick below to see the response:\n\n{}".format(
supervisor,
supervisee,
"https://example.com/#/pms/reviewsBySupervisor",
)
try:
if not settings.DEFAULT_EMAIL_RECIPIENTS:
settings.DEFAULT_EMAIL_RECIPIENTS.append(
str(serializer.data["supervisor_email"])
)
send_mail(
subject="New Response Received",
message=mail_text,
from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=settings.DEFAULT_EMAIL_RECIPIENTS,
fail_silently=False,
)
except (SMTPRecipientsRefused, SMTPSenderRefused):
LOGGER.exception("There was a problem submitting the form.")
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
So, the problem that I am facing is that when I try to send mail with update method in ResponseViewset as shown above. I get the following error:
Internal Server Error: /UMS/api/responses/38/
Traceback (most recent call last):
File "/home/shishir/Projects/performance_management/performance_management/reviews/serializers.py", line 77, in get_supervisor_name
return obj.supervisor.first_name + " " + obj.supervisor.last_name
AttributeError: 'NoneType' object has no attribute 'first_name'
So, what is happening is due to some reason, all the fields of that particular review are getting set to null as soon as I try to update and hence getting NoneType object has no attribute. I have checked in database table(MySQL), all the fields are getting set to null. Can anyone tell me why is this happening ? Where am I going wrong ? And what is the correct way to do it?
Finally I found my solution by changing the method update to partial_update. Apparently update method updates all the field while in above case I am attempting the field called response_text in the Review model which are setting other fields to null if they could be. Also after doing that I had to change the request from PUT to PATCH in frontend. Also I had to do some other minor coding changes like removing supervisor and supervisee fields from read_only_fields from ResponseSerializer. Updated code for ResponseViewset is shown below:
class ResponseViewSet(viewsets.ModelViewSet):
queryset = Review.objects.all()
permission_classes = [
permissions.IsAuthenticated,
#permissions.AllowAny,
]
serializer_class = ResponseSerializer
def partial_update(self, request,*args, **kwargs):
obj = self.get_object()
serializer = self.serializer_class(obj, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
mail_text = "Hi {},\n\nYou got a response for your 1:1 from {}.\n\nClick below to see the response:\n\n{}".format(
serializer.data["supervisor_name"],
serializer.data["supervisee_name"],
get_product_link("UMS/reviewsForDR"),
)
try:
if not settings.DEFAULT_EMAIL_RECIPIENTS:
settings.DEFAULT_EMAIL_RECIPIENTS.append(
# supervisor_email
serializer.data["supervisor_email"]
)
send_mail(
subject="New Response Received",
message=mail_text,
from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=settings.DEFAULT_EMAIL_RECIPIENTS,
fail_silently=False,
)
settings.DEFAULT_EMAIL_RECIPIENTS = []
except (SMTPRecipientsRefused, SMTPSenderRefused):
LOGGER.exception("There was a problem submitting the form.")
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Unable to store all the data into Django database, using Python Requests

Unable to store all the data into Django database. Its printing the correct list of data from an open API but not storing.
But if i give direct values of data1 in ( client.py ) for eg:
data1 = {
'employee_name':'Name',
'employee_salary':'20000',
'employee_age':'22',
'profile_image':' '
}
Then it store the above dictionary values into Django database.But the data i am getting using requests is a list.It prints in cmd but doesn't store in DB.
client.py file
def get_alldata():
url1 = "http://dummy.restapiexample.com/api/v1/employees"
url = "http://127.0.0.1:8000/employee/"
my_response = requests.get(url1)
token = get_token()
header = {'Authorization' : f'Token {get_token()}'}
data1 = [ my_response.json() ]
for d in range(len(data1)):
payload = data1[d]
res = requests.post(url, data=data1[d] , headers=header )
print(data1[d])
get_alldata()
This is the Api.py file in which includes the get and post method using serializers in django rest framework.
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.authtoken.models import Token
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status
from employee.serializers import *
class EmployeeAuthentication(ObtainAuthToken):
def post(self,request, *args, **kwargs):
serializer = self.serializer_class(data=request.data, context={'request':request})
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
token , created = Token.objects.get_or_create(user=user)
return Response(token.key)
class EmployeeView(APIView):
def get(self, request):
model = Employee.objects.all()
serializer = EmployeeSerializer(model, many=True)
return Response(serializer.data)
def post(self, request):
serializer = EmployeeSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors,status=status.HTTP_400_BAD_REQUEST)
I got the solution.There is no problem in code yet.Problem was the data i was getting from an open API contains a Null value in a field which was unable to store into Django database.If anyone having same issue, don't GET any field having null value.

NoReverseMatch error when returning Respose object in django rest framework

I am trying to build an Instagram clone.
Clicking the follow button calls ajax.
My view def post saves "follows" and returns the Response object whose data is true/false values for whether or not the user was previously followed.
error message
‘django.urls.exceptions.NoReverseMatch: Reverse for 'profile'
with arguments '('',)' not found. 2 pattern(s) tried’
views.py
class ProfileListView(mixins.UpdateModelMixin, generics.GenericAPIView):
"""User post list"""
renderer_classes = [TemplateHTMLRenderer]
template_name = 'insta/profile_list.html'
serializer_class = InstaSerializer
permission_classes = [permissions.AllowAny]
def get(self, request, *args, **kwargs):
target = kwargs.get('username')
try:
target_user = USER.objects.get(username=target)
response = Insta.objects.filter(owner__username=target)
return Response({'posts': response, 'target_user': target_user})
except USER.DoesNotExist:
return HttpResponseRedirect(reverse('insta:dashboard'), status=HTTP_404_NOT_FOUND)
def post(self, request, *args, **kwargs):
followed_user = get_object_or_404(USER, username=kwargs.get('username'))
if request.user.is_authenticated:
follower_user = request.user
if followed_user == follower_user:
raise PermissionError('Unable to follow yourself')
else:
if follower_user in followed_user.followers.all():
followed_user.followers.remove(follower_user)
return Response({
'follow_exist': False
})
else:
follower_user.follows.add(followed_user)
return Response({
'follow_exist': True
})
else:
return redirect('insta/login')
urls.py
path('<username>/', insta_profile, name='profile'),
ajax
$.ajax({
type: 'POST',
url: '{% url 'insta:profile' username=target_user.username %}',
data: {'csrfmiddlewaretoken': '{{ csrf_token }}'},
success: function (response) {
if (response.follow_exist) {
$this.attr('class', 'btn btn-outline-secondary');
$this.text('cancel follow')
} else {
$this.attr('class', 'btn btn-primary');
$this.text('follow')
}
},
error: function (response) {
console.log(JSON.stringify(response))
}
});
Could you tell me why this happens?
Thank you in advance.
add this at your exception
return HttpResponseRedirect(reverse('insta:dashboard', args=(username,), status=HTTP_404_NOT_FOUND)
instead of
return HttpResponseRedirect(reverse('insta:dashboard'), status=HTTP_404_NOT_FOUND)
refer this

Django restframework: ReadOnlyModelViewSet create HTTP_403 but expected HTTP_405

I am creating a REST-API with django and the django rest framework, therefore I am using ReadOnlyModelViewSet. In my unit test for testing create / POST method, I am expecting HTTP_405_METHOD_NOT_ALLOWED but actually it is HTTP_403_FORBIDDEN. For the update / PUT it is HTTP_405 as expected. Is this behavior the default behavior or do I have a dumb bug?
And yes the user is authenticated.
The Viewset:
class StudentSolutionReadOnlyModelViewSet(viewsets.ReadOnlyModelViewSet):
queryset = StudentSolution.objects.all()
serializer_class = StudentSolutionSerializer
def get_permissions(self):
if self.request.method == 'POST':
return (permissions.IsAuthenticated(), )
return (permissions.IsAuthenticated(), IsStudentSolutionOwnerOrAdmin(),)
def get_queryset(self):
if self.request.user.is_staff:
return StudentSolution.objects.all(
course_assignment=self.kwargs['course_assignment_pk']
).order_by('student__last_name', 'course_assignment__due_date')
return StudentSolution.objects.filter(
student=self.request.user,
course_assignment=self.kwargs['course_assignment_pk']
).order_by('course_assignment__due_date')
Edit 1:
class StudentSolutionReadOnlyModelViewSetTests(TestCase):
def setup(self):
#[..]
def test_create_user_not_allowed(self):
data = {
'course_assignment': self.course_assignment.id,
'student': self.user.id
}
url = self.generate_url(self.course_assignment.id)
self.csrf_client.credentials(HTTP_AUTHORIZATION='JWT ' + self.user_token)
resp = self.csrf_client.post(
self.url,
data=json.dumps(data),
content_type='application/json'
)
self.assertEqual(resp.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
I am using the same authentication and csrf stuff for all methods, as well as generate_url method.

tastypie PUT works but POST doesn't

I'm trying to implement a simple Django service with a RESTful API using tastypie. My problem is that when I try to create a WineResource with PUT, it works fine, but when I use POST, it returns a HTTP 501 error. Reading the tastypie documentation, it seems like it should just work, but it's not.
Here's my api.py code:
class CustomResource(ModelResource):
"""Provides customizations of ModelResource"""
def determine_format(self, request):
"""Provide logic to provide JSON responses as default"""
if 'format' in request.GET:
if request.GET['format'] in FORMATS:
return FORMATS[request.GET['format']]
else:
return 'text/html' #Hacky way to prevent incorrect formats
else:
return 'application/json'
class WineValidation(Validation):
def is_valid(self, bundle, request=None):
if not bundle.data:
return {'__all__': 'No data was detected'}
missing_fields = []
invalid_fields = []
for field in REQUIRED_WINE_FIELDS:
if not field in bundle.data.keys():
missing_fields.append(field)
for key in bundle.data.keys():
if not key in ALLOWABLE_WINE_FIELDS:
invalid_fields.append(key)
errors = missing_fields + invalid_fields if request.method != 'PATCH' \
else invalid_fields
if errors:
return 'Missing fields: %s; Invalid fields: %s' % \
(', '.join(missing_fields), ', '.join(invalid_fields))
else:
return errors
class WineProducerResource(CustomResource):
wine = fields.ToManyField('wines.api.WineResource', 'wine_set',
related_name='wine_producer')
class Meta:
queryset = WineProducer.objects.all()
resource_name = 'wine_producer'
authentication = Authentication() #allows all access
authorization = Authorization() #allows all access
class WineResource(CustomResource):
wine_producer = fields.ForeignKey(WineProducerResource, 'wine_producer')
class Meta:
queryset = Wine.objects.all()
resource_name = 'wine'
authentication = Authentication() #allows all access
authorization = Authorization() #allows all access
validation = WineValidation()
filtering = {
'percent_new_oak': ('exact', 'lt', 'gt', 'lte', 'gte'),
'percentage_alcohol': ('exact', 'lt', 'gt', 'lte', 'gte'),
'color': ('exact', 'startswith'),
'style': ('exact', 'startswith')
}
def hydrate_wine_producer(self, bundle):
"""Use the provided WineProducer ID to properly link a PUT, POST,
or PATCH to the correct WineProducer instance in the db"""
#Workaround since tastypie has bug and calls hydrate more than once
try:
int(bundle.data['wine_producer'])
except ValueError:
return bundle
bundle.data['wine_producer'] = '/api/v1/wine_producer/%s/' % \
bundle.data['wine_producer']
return bundle
Any help is greatly appreciated! :-)
This usually means that you are sending the POST to a detail uri, e.g. /api/v1/wine/1/. Since POST means treat the enclosed entity as a subordinate, sending the POST to the list uri, e.g. /api/v1/wine/, is probably what you want.

Categories

Resources