I'm currently struggling to make this current unit-test pass:
def test_markNotifications(self):
request_url = f'Notifications/mark_notifications/'
view = NotificationsViewSet.as_view(actions={'post': 'mark_notifications'})
request = self.factory.post(request_url)
request.POST = {'id_notifs': "1"}
force_authenticate(request, user=self.user)
response = view(request)
self.assertEqual(response.status_code, 200)
Here's the associated view:
#action(detail=False, methods=['POST'])
def mark_notifications(self, request, pk=None):
"""
Put Notifications as already read.
"""
id_notifs = request.POST.get("id_notifs")
if not id_notifs:
return Response("Missing parameters.", status=400)
id_notifs = str(id_notifs).split(",")
print(id_notifs)
for id in id_notifs:
notif = Notification.objects.filter(pk=id).first()
if not notif:
return Response("No existant notification with the given id.", status=400)
notif.isRead = True
notif.save()
return Response("Notifications have been marked as read.", status=200)
The problem is that even though I'm passing "id_notifs" through the request in test, I'm getting None when I do id_notifs = request.POST.get("id_notifs").
It seems that the id_notifs I'm passing in the POST request are neither in the body and the form-data. In this context, I have no idea on how to access them.
Looking forward some help, thanks.
I want to send post request from testing. Its working for postman but it didnot work for my test case. How can I give the data by post request.
Views.py,
class PersonalInfoAPI(viewsets.ViewSet):
permission_classes = [IsOwnerPermission]
def get_object(self, pk):
obj = get_object_or_404(Employee.objects.all(), pk=pk)
self.check_object_permissions(self.request, obj)
return obj
def create(self, request):
personal_info = JSONParser().parse(request)
.....
return ...
test.py
url = reverse('employee1-list')
self.client = Client(HTTP_AUTHORIZATION='Token ' + token.key)
resp1 = self.client.post(url, {"data": {
"appraisal_master_id": 1,
"personal_info": {
"employee_id": 1,
"experience": "abcd",
"education": "Nonoooo"
}
}
}, format='json')
print(resp1)
self.assertEqual(resp1.status_code, 200)
I have got 400 error. Please, tell anyone how can I pass data im properway,..
I got solution. When I gave request which passes dictionary data. then, I coverted into json its working fine.
url = reverse('employee1-list')
self.client = Client(HTTP_AUTHORIZATION='Token ' + token.key)
resp1 = self.client.post(url, data=data, content_type="application/json")
print(resp1)
self.assertEqual(resp1.status_code, 201)
I am new to Django and a small detail has been bothering me.I have an api endpoint that returns the details of one patient. I have made a successful get request and tested on postman. It returns data for a particular patient with id = 996(I have hard coded the id). But I need to set it so it can pick the id from params in postman instead of the hard coded one here. How can I set params and append them on the url so that I use the id fed in postman instead of hard coding? Kindly assist
views.py
class PatientDetailsView(GenericAPIView):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
#classmethod
#encryption_check
def get(self, request, *args, **kwargs):
try:
result = {}
auth = cc_authenticate()
res = getPatientDetails(auth["key"], id)
result = res
return Response(result, status=status.HTTP_200_OK)
except Exception as e:
error = getattr(e, "message", repr(e))
result["errors"] = error
result["status"] = "error"
return Response(result, status=status.HTTP_400_BAD_REQUEST)
api_service.py
def getPatientDetails(auth, id):
print("getting patientdetails from Callcenter")
try:
print(auth)
# print(url)
id= 996
headers = {
"Authorization": f'Token {auth}'
}
url = f'{CC_URL}/patients/v1/details/?id={id}'
print(url)
res = requests.get(url, headers=headers)
print("returning patientdetails response", res.status_code)
return res.json()
except ConnectionError as err:
print("connection exception occurred")
print(err)
return err
urls.py
path("details/", views.PatientDetailsView.as_view(), name="patient_info"),
This is the code I needed
id = request.GET.get('<id>')
I'm using django-all auth with GMail login.
There one of my view that will receive HTTP-POST from a Hotspot login page in other server (actualy it's mikrotik hotspot redirect).
I need to read their posted data AFTER social login.
I read https://stackoverflow.com/a/32250781/5901318
Looks like the safest way is to store the POST data in session, and later my view will get it from request.session
but I don't know how to 'store data safely in request.session before the authentication occurs'.
def my_login_required(function):
#https://stackoverflow.com/a/39256685/5901318
def wrapper(request, *args, **kwargs):
decorated_view_func = login_required(request)
if not decorated_view_func.user.is_authenticated:
if request.method == "POST" :
print('my_login_required POST:',request.POST.__dict__)
print('my_login_required ARGS:',args)
print('my_login_required KWARGS:',kwargs)
print('my_login_required SESSION:',request.session.__dict__)
wrapper.__doc__ = function.__doc__
wrapper.__name__ = function.__name__
return wrapper
##receiver(user_logged_in)
#csrf_exempt
#my_login_required
def hotspotlogin(request,*args,**kwargs):
print('HOTSPOTLOGIN')
I tried to access it using requests :
r = requests.post('http://mysite:8000/radius/hotspotlogin/', json={"NAMA": "BINO"}, headers = {'Content-type': 'application/json', 'Accept': 'text/plain'})
but in django shell I only got :
my_login_required POST: {'_encoding': 'utf-8', '_mutable': False}
my_login_required ARGS: ()
my_login_required KWARGS: {}
my_login_required SESSION: {'storage_path': '/opt/djangos/radius03/mysessions/', 'file_prefix': 'sessionid', '_SessionBase__session_key': None, 'accessed': True, 'modified': False, 'serializer': <class 'django.core.signing.JSONSerializer'>, '_session_cache': {}}
Kindly please give me any clue to do that.
Sincerely
-bino-
Got priceless help from a friend, and here is the solution.
def my_login_required(function):
def wrapper(request, *args, **kwargs):
old_data=dict()
try :
old_data['POST'] = dict(request.POST)
except :
old_data['POST'] = dict()
try :
old_data['GET'] = dict(request.GET)
except :
old_data['GET'] = dict()
old_data['method'] = request.method
decorated_view_func = login_required(request)
if not decorated_view_func.user.is_authenticated: #Only if user not authenticated
request.session['old'] = old_data #put old data in request.session['old']
return decorated_view_func(request) # return redirect to signin
return function(request, *args, **kwargs)
wrapper.__doc__ = function.__doc__
wrapper.__name__ = function.__name__
return wrapper
#my_login_required
def testview(request,*args,**kwargs):
print('SESSION DATA:', request.session.get('old')) #take original post/get data from request.session['old']
I have this middleware
import logging
request_logger = logging.getLogger('api.request.logger')
class LoggingMiddleware(object):
def process_response(self, request, response):
request_logger.log(logging.DEBUG,
"GET: {}. POST: {} response code: {}. response "
"content: {}".format(request.GET, request.DATA,
response.status_code,
response.content))
return response
The problem is that request in process_response method has no .POST nor .DATA nor .body. I am using django-rest-framework and my requests has Content-Type: application/json
Note, that if I put logging to process_request method - it has .body and everything I need. However, I need both request and response in a single log entry.
Here is complete solution I made
"""
Api middleware module
"""
import logging
request_logger = logging.getLogger('api.request.logger')
class LoggingMiddleware(object):
"""
Provides full logging of requests and responses
"""
_initial_http_body = None
def process_request(self, request):
self._initial_http_body = request.body # this requires because for some reasons there is no way to access request.body in the 'process_response' method.
def process_response(self, request, response):
"""
Adding request and response logging
"""
if request.path.startswith('/api/') and \
(request.method == "POST" and
request.META.get('CONTENT_TYPE') == 'application/json'
or request.method == "GET"):
request_logger.log(logging.DEBUG,
"GET: {}. body: {} response code: {}. "
"response "
"content: {}"
.format(request.GET, self._initial_http_body,
response.status_code,
response.content), extra={
'tags': {
'url': request.build_absolute_uri()
}
})
return response
Note, this
'tags': {
'url': request.build_absolute_uri()
}
will allow you to filter by url in sentry.
Andrey's solution will break on concurrent requests. You'd need to store the body somewhere in the request scope and fetch it in the process_response().
class RequestLoggerMiddleware(object):
def process_request(self, request):
request._body_to_log = request.body
def process_response(self, request, response):
if not hasattr(request, '_body_to_log'):
return response
msg = "method=%s path=%s status=%s request.body=%s response.body=%s"
args = (request.method,
request.path,
response.status_code,
request._body_to_log,
response.content)
request_logger.info(msg, *args)
return response
All answers above have one potential problem -- big request.body passed to the server. In Django request.body is a property. (from framework)
#property
def body(self):
if not hasattr(self, '_body'):
if self._read_started:
raise RawPostDataException("You cannot access body after reading from request's data stream")
try:
self._body = self.read()
except IOError as e:
six.reraise(UnreadablePostError, UnreadablePostError(*e.args), sys.exc_info()[2])
self._stream = BytesIO(self._body)
return self._body
Django framework access body directly only in one case. (from framework)
elif self.META.get('CONTENT_TYPE', '').startswith('application/x-www-form-urlencoded'):
As you can see, property body read the entire request into memory. As a result, your server can simply crash. Moreover, it becomes vulnerable to DoS attack.
In this case I would suggest using another method of HttpRequest class. (from framework)
def readlines(self):
return list(iter(self))
So, you no longer need to do this
def process_request(self, request):
request._body_to_log = request.body
you can simply do:
def process_response(self, request, response):
msg = "method=%s path=%s status=%s request.body=%s response.body=%s"
args = (request.method,
request.path,
response.status_code,
request.readlines(),
response.content)
request_logger.info(msg, *args)
return response
EDIT: this approach with request.readlines() has problems. Sometimes it does not log anything.
It's frustrating and surprising that there is no easy-to-use request logging package in Django.
So I created one myself. Check it out: https://github.com/rhumbixsf/django-request-logging.git
Uses the logging system so it is easy to configure. This is what you get with DEBUG level:
GET/POST request url
POST BODY if any
GET/POST request url - response code
Response body
It is like accessing the form data to create a new form.
You must use request.POST for this (perhaps request.FILES is something you'd log as well).
class LoggingMiddleware(object):
def process_response(self, request, response):
request_logger.log(logging.DEBUG,
"GET: {}. POST: {} response code: {}. response "
"content: {}".format(request.GET, request.POST,
response.status_code,
response.content))
return response
See Here for request properties.
You can use like below:
"""
Middleware to log requests and responses.
"""
import socket
import time
import json
import logging
request_logger = logging.getLogger(__name__)
class RequestLogMiddleware:
"""Request Logging Middleware."""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
log_data = {}
# add request payload to log_data
req_body = json.loads(request.body.decode("utf-8")) if request.body else {}
log_data["request_body"] = req_body
# request passes on to controller
response = self.get_response(request)
# add response payload to our log_data
if response and response["content-type"] == "application/json":
response_body = json.loads(response.content.decode("utf-8"))
log_data["response_body"] = response_body
request_logger.info(msg=log_data)
return response
# Log unhandled exceptions as well
def process_exception(self, request, exception):
try:
raise exception
except Exception as e:
request_logger.exception("Unhandled Exception: " + str(e))
return exception
You can also check this out - log requests via middleware explains this
Also note, that response.content returns bytestring and not unicode string so if you need to print unicode, you need to call response.content.decode("utf-8").
You cannot access request.POST (or equivalently request.body) in the process_response part of the middleware. Here is a ticket raising the issue. Though you can have it in the process_request part. The previous answers give a class-based middleware. Django 2.0+ and 3.0+ allow function based middlewares.
from .models import RequestData # Model that stores all the request data
def requestMiddleware(get_response):
# One-time configuration and initialization.
def middleware(request):
# Code to be executed for each request before
# the view (and later middleware) are called.
try : metadata = request.META ;
except : metadata = 'no data'
try : data = request.body ;
except : data = 'no data'
try : u = str(request.user)
except : u = 'nouser'
response = get_response(request)
w = RequestData.objects.create(userdata=u, metadata=metadata,data=data )
w.save()
return response
return middleware
Model RequestData looks as follows -
class RequestData(models.Model):
time = models.DateTimeField(auto_now_add=True)
userdata = models.CharField(max_length=10000, default=' ')
data = models.CharField(max_length=20000, default=' ')
metadata = models.CharField(max_length=20000, default=' ')