I'm trying to make a little "Hello World" webservice with Django following a few tutorials, but I'm hitting the same barrier over and over. I've defined a view.py and soaplib_handler.py:
view.py:
from soaplib_handler import DjangoSoapApp, soapmethod, soap_types
class HelloWorldService(DjangoSoapApp):
__tns__ = 'http://saers.dk/soap/'
#soapmethod(_returns=soap_types.Array(soap_types.String))
def hello(self):
return "Hello World"
soaplib_handler.py:
from soaplib.wsgi_soap import SimpleWSGISoapApp
from soaplib.service import soapmethod
from soaplib.serializers import primitive as soap_types
from django.http import HttpResponse
class DjangoSoapApp(SimpleWSGISoapApp):
def __call__(self, request):
django_response = HttpResponse()
def start_response(status, headers):
status, reason = status.split(' ', 1)
django_response.status_code = int(status)
for header, value in headers:
django_response[header] = value
response = super(SimpleWSGISoapApp, self).__call__(request.META, start_response)
django_response.content = "\n".join(response)
return django_response
And it seems the "response = super...." line is giving me trouble. When I load up /hello_world/services.wsdl mapped in url.py I get:
AttributeError at /hello_world/service.wsdl
'module' object has no attribute 'tostring'
For the full error message, see here:
http://saers.dk:8000/hello_world/service.wsdl
Do you have any suggestion as to why I get this error? And where is ElementTree defined?
#zdmytriv The line
soap_app_response = super(BaseSOAPWebService, self).__call__(environ, start_response)
should look like
soap_app_response = super(DjangoSoapApp, self).__call__(environ, start_response)
then your example works.
not sure if this will solve your problem, but the decorator on your function hello says that it is suppose to return a String Array, but you are actually returning a String
Try _returns=soap_types.String instead
Ray
Copy/paste from my service:
# SoapLib Django workaround: http://www.djangosnippets.org/snippets/979/
class DumbStringIO(StringIO):
""" Helper class for BaseWebService """
def read(self, n):
return self.getvalue()
class DjangoSoapApp(SimpleWSGISoapApp):
def __call__(self, request):
""" Makes Django request suitable for SOAPlib SimpleWSGISoapApp class """
http_response = HttpResponse()
def start_response(status, headers):
status, reason = status.split(' ', 1)
http_response.status_code = int(status)
for header, value in headers:
http_response[header] = value
environ = request.META.copy()
body = ''.join(['%s=%s' % v for v in request.POST.items()])
environ['CONTENT_LENGTH'] = len(body)
environ['wsgi.input'] = DumbStringIO(body)
environ['wsgi.multithread'] = False
soap_app_response = super(BaseSOAPWebService, self).__call__(environ, start_response)
http_response.content = "\n".join(soap_app_response)
return http_response
Django snippet has a bug. Read last two comments from that url.
Related
I am new to testing and need some help here.
Assuming having this method:
from urllib.request import urlopen
def get_posts():
with urlopen('some url here') as data:
return json.loads(data.read().decode('utf-8'))
The question is how to test this method (using mock.patch decorator if possible)?
What I have now:
#mock.patch('mymodule.urlopen')
def test_get_post(self, mocked_urlopen):
mocked_urlopen.__enter__ = Mock(return_value=self.test_data)
mocked_urlopen.__exit__ = Mock(return_value=False)
...
But it does not seem to be working.
P.S. Is there any convenient way to work with data variable (which type is HTTPResponse) in test so it could just be simple string?
I was fighting with this as well, and finally figured it out. (Python 3 syntax):
import urllib.request
import unittest
from unittest.mock import patch, MagicMock
class TestUrlopen(unittest.TestCase):
#patch('urllib.request.urlopen')
def test_cm(self, mock_urlopen):
cm = MagicMock()
cm.getcode.return_value = 200
cm.read.return_value = 'contents'
cm.__enter__.return_value = cm
mock_urlopen.return_value = cm
with urllib.request.urlopen('http://foo') as response:
self.assertEqual(response.getcode(), 200)
self.assertEqual(response.read(), 'contents')
#patch('urllib.request.urlopen')
def test_no_cm(self, mock_urlopen):
cm = MagicMock()
cm.getcode.return_value = 200
cm.read.return_value = 'contents'
mock_urlopen.return_value = cm
response = urllib.request.urlopen('http://foo')
self.assertEqual(response.getcode(), 200)
self.assertEqual(response.read(), 'contents')
response.close()
here is my take on this
from urllib.request import urlopen
from unittest.mock import patch
class Mock():
def __init__(self, request, context):
return None
def read(self):
return self
def decode(self, arg):
return ''
def __iter__(self):
return self
def __next__(self):
raise StopIteration
with patch('urllib.request.urlopen', Mock):
# do whatever over here
with urlopen('some url here') as data is a context manager
Also, a file can be used as a context manager, so a better approach here is to use io.StringIO
import io
import json
import urllib.request
from unittest.mock import patch
def get_posts():
with urllib.request.urlopen('some url here') as data:
return json.load(data)
def test_get_posts():
data = io.StringIO('{"id": 123}')
with patch.object(urllib.request, 'urlopen', return_value=data):
assert get_posts() == {"id": 123}
Ok, so I have written simple class to simulate context manager.
class PatchContextManager:
def __init__(self, method, enter_return, exit_return=False):
self._patched = patch(method)
self._enter_return = enter_return
self._exit_return = exit_return
def __enter__(self):
res = self._patched.__enter__()
res.context = MagicMock()
res.context.__enter__.return_value = self._enter_return
res.context.__exit__.return_value = self._exit_return
res.return_value = res.context
return res
def __exit__(self, type, value, tb):
return self._patched.__exit__()
Usage:
with PatchContextManager('mymodule.method', 'return_string') as mocked:
a = mymodule.method(47) # a == 'return_string'
mocked.assert_called_with(47)
...
How can i print the id from the response in the below code.The user does exist in the DB.Also i come across this error.
from django.test import Client
c = Client(enforce_csrf_checks=False)
response = c.post('/reg/_user/', {'firstname': 'test', 'lastname' : '_test'})
views get_user
def _user(request):
try:
response_dict = {}
qd = request.POST
firstname = qd.__getitem__('firstname')
lastname = qd.__getitem__('lastname')
up = UserProfile.objects.get(first_name=firstname,last_name=lastname)
print up.id
return up.id
except:
pass
Error:
response = c.post('/reg/_user/', {'firstname': 'test', 'lastname' : '_test'})
File "/usr/local/lib/python2.7/dist-packages/django/test/client.py", line 483, in post
response = super(Client, self).post(path, data=data, content_type=content_type, **extra)
File "/usr/local/lib/python2.7/dist-packages/django/test/client.py", line 302, in post
return self.request(**r)
File "/usr/local/lib/python2.7/dist-packages/django/test/client.py", line 444, in request
six.reraise(*exc_info)
File "/usr/local/lib/python2.7/dist-packages/django/core/handlers/base.py", line 201, in get_response
response = middleware_method(request, response)
File "/usr/local/lib/python2.7/dist-packages/django/middleware/clickjacking.py", line 30, in process_response
if response.get('X-Frame-Options', None) is not None:
AttributeError: 'UserProfile' object has no attribute 'get'
The problem is not with your tests, but with the view itself. In Django a view always has to return a HttpResponse object. Sometimes this is achieved using a shortcut function like render(), but it in turn also returns an HttpResponse object.
If for some reason you just want to return an otherwise empty page with this single value you could change
return up.id
to
return HttpResponse(up.id)
Also, I wonder: Did you create the view just to test UserProfile and don't use it as a view on the actual site? If so, this code doesn't belong in a view, it should be put into the unittest itself. You should only use the test client to test actual, real views.
On an mostly unrelated, but quite important note. This:
try:
# your view code
except:
pass
is a strong antipattern. Why would you want to silence all the potential problems? You should really stop doing that.
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=' ')
I'm server an icalender file through django_ical. Problem is that the file is named download.ics. I'm trying to change this to MyCalender.ics. If found this old snippet. I would prefer using django_ical, because it ingerates nicely with django syndication.
cal = vobject.iCalendar()
cal.add('method').value = 'PUBLISH' # IE/Outlook needs this
for event in event_list:
vevent = cal.add('vevent')
icalstream = cal.serialize()
response = HttpResponse(icalstream, mimetype='text/calendar')
response['Filename'] = 'filename.ics' # IE needs this
response['Content-Disposition'] = 'attachment; filename=filename.ics'
In django_ical the ICalFeed is inherited from django.contrib.syndication.views.Feed
In your app you inherit from ICalFeed to provide items, item_title and other methods that generate data for ics file.
You can override the __call__ method. The call to super will return you HttpResponse and you will add custom headers to it.
The code will be something like:
class EventFeed(ICalFeed):
"""
A simple event calender
"""
product_id = '-//example.com//Example//EN'
timezone = 'UTC'
def items(self):
return Event.objects.all().order_by('-start_datetime')
# your other fields
def __call__(self, request, *args, **kwargs):
response = super(EventFeed, self).__call__(request, *args, **kwargs)
if response.mimetype == 'text/calendar':
response['Filename'] = 'filename.ics' # IE needs this
response['Content-Disposition'] = 'attachment; filename=filename.ics'
return response
This code is not tested, so there might be some typos. Also you need to catch if there were errors in call to super. I do it by response.mimetype == 'text/calendar' but maybe there is a better way to do it
I'm working on a django a project that will serve as the endpoint for a webhook. The webhook will POST some JSON data to my endpoint, which will then parse that data. I'm trying to write unit tests for it, but I'm not sure if I'm sending the JSON properly.
I keep getting "TypeError: string indices must be integers" in pipeline_endpoint
Here's the code:
# tests.py
from django.test import TestCase
from django.test.client import Client
import simplejson
class TestPipeline(TestCase):
def setUp(self):
"""initialize the Django test client"""
self.c = Client()
def test_200(self):
json_string = u'{"1": {"guid": "8a40135230f21bdb0130f21c255c0007", "portalId": 999, "email": "fake#email"}}'
json_data = simplejson.loads(json_string)
self.response = self.c.post('/pipeline-endpoint', json_data, content_type="application/json")
self.assertEqual(self.response.status_code, "200")
and
# views.py
from pipeline.prospect import Prospect
import simplejson
def pipeline_endpoint(request):
#get the data from the json object that came in
prospects_json = simplejson.loads(request.raw_post_data)
for p in prospects_json:
prospect = {
'email' : p['email'],
'hs_id' : p['guid'],
'portal' : p['portalId'],
}
Edit: whole traceback.
======================================================================
ERROR: test_200 (pipeline.tests.TestPipeline)
----------------------------------------------------------------------
Traceback (most recent call last):
File "F:\......\pipeline\tests.py", line 31, in test_200
self.response = self.c.post('/pipeline-endpoint', json_string, content_type="application/json")
File "C:\Python27\lib\site-packages\django\test\client.py", line 455, in post
response = super(Client, self).post(path, data=data, content_type=content_type, **extra)
File "C:\Python27\lib\site-packages\django\test\client.py", line 256, in post
return self.request(**r)
File "C:\Python27\lib\site-packages\django\core\handlers\base.py", line 111, in get_response
response = callback(request, *callback_args, **callback_kwargs)
File "F:\......\pipeline\views.py", line 18, in pipeline_endpoint
'email' : p['email'],
TypeError: string indices must be integers
----------------------------------------------------------------------
Ran 1 test in 0.095s
FAILED (errors=1)
Destroying test database for alias 'default'...
#mrmagooey is right
def test_your_test(self):
python_dict = {
"1": {
"guid": "8a40135230f21bdb0130f21c255c0007",
"portalId": 999,
"email": "fake#email"
}
}
response = self.client.post('/pipeline-endpoint/',
json.dumps(python_dict),
content_type="application/json")
use json.dumps instead of json.loads
Try:
self.client.generic('POST', '/url', json.dumps({'json': 'object'})
rest_framework's APIClient (which is the the default client_class in APITestCase) takes care of dumping dict to JSON and it sets proper content type by passing format='json'.
from rest_framework import status
from rest_framework.test import APIClient, APITestCase
class MyTestCase(APITestCase):
url = '/url'
def post(self, payload, url=None):
"""
Helper to send an HTTP post.
#param (dict) payload: request body
#returns: response
"""
if url is None:
url = self.url
return self.client.post(url, payload, format='json')
def test_my_function(self):
payload = {
'key': 'value'
}
response = self.post(payload)
self.assertEqual(response.status_code, status.HTTP_200_OK)
You can always use the HttpRequest.body which loads the raw request data. This way you can handle your own data processing.
c = Client()
json_str= json.dumps({"data": {"id": 1}})
c.post('/ajax/handler/', data= json_str, content_type='application/json',
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
def index(request):
....
print json.loads(request.body)
Since Django 3.0:
If you provide content_type as application/json, the data is
serialized using json.dumps() if it’s a dict, list, or tuple.
Serialization is performed with DjangoJSONEncoder by default, and can
be overridden by providing a json_encoder argument to Client. This
serialization also happens for put(), patch(), and delete() requests.
response = client.post(
f'/customer/{customer.id}/edit',
{'email': new_email},
content_type='application/json'
)
You can user iteritems on dictionaries to loop
for index, p in prospects_json.iteritems():
prospect={
'email': p['email'],
}
or alternatively
for index in prospect_json:
prospect={
'email': prospect_json[ index ]['email']
}
Adding to Guillaume Vincent's answer, from Django 2.1 we no longer need to use json.dumps for passing the data.
Changed in Django 2.1:
The JSON serialization described above was added. In older versions, you can call json.dumps() on data before passing it to post() to achieve the same thing.