Django Webhook receive post request for Mailgun - django

I've setup a webhook in Django to receive updates from Mailgun.
The mailgun POST payload is delivered to the webhook in the below format:
{
“signature”:
{
"timestamp": "1529006854",
"token": "a8ce0edb2dd8301dee6c2405235584e45aa91d1e9f979f3de0",
"signature": "d2271d12299f6592d9d44cd9d250f0704e4674c30d79d07c47a66f95ce71cf55"
}
“event-data”:
{
"event": "opened",
"timestamp": 1529006854.329574,
"id": "DACSsAdVSeGpLid7TN03WA",
// ...
}
}
If I try and retrieve event parameter using the below code, I get an error saying TypeError: 'method' object is not subscriptable
#csrf_exempt
#require_POST
def mailgun(request):
event_data = request.POST.get['event-data']['event']
return HttpResponse(event_data, status=200)
Any help is appreciated.

After a lot of troubleshooting, the answer was hidden in the StackOverflow link below. In Python 3.0 to Python 3.5.x, json.loads() will only accept a unicode string, so you must decode request.body (which is a byte string) before passing it to json.loads().
Trying to parse `request.body` from POST in Django
body_unicode = request.body.decode('utf-8')
body = json.loads(body_unicode)
content = body['event-data']
recipient = content['recipient']

Try to update .get to call function and add some input checks, ie:
if request.POST.get('event-data'):
event_data = request.POST.get('event-data')['event']
return HttpResponse(event_data, status=200)
else:
return HttpResponse("[unknown event]", status=400)
or if you sure event-data never be empty or null call directly:
request.POST['event-data']['event']

Related

Cannot get values from request in Django - Empty QueryDict

I’m new to ViewSets and am trying to get the values sent from the front-end fetch method to Django’s request object in the create function. I don’t know whether it’s just a simple syntax error or whether the data isn’t being sent properly from the front-end, but I think it’s a back-end issue.
The stringified data in the post method seems to log correctly at the front-end like with this test:
{"title":"test","type":"landing","slug":"","notes":""}
Printing variables in the ViewSet’s create function however shows these:
print(request.POST["title"]) # fails with keyerror: 'title' MultiValueDictKeyError(key) django.utils.datastructures.MultiValueDictKeyError: 'title'
print(request["title"]) # fails with TypeError: 'Request' object is not subscriptable
print(request.POST.get("title", “test”)) # fails as print test
print(request.POST.get("title")) # fails as it just returns None
print(request.get("title")) # fails with AttributeError: 'WSGIRequest' object has no attribute 'get'
print(self.request.query_params.get("title", None)) # prints None
print(self.request.query_params) # prints empty QueryDict: <QueryDict: {}>
Here’s the create function:
class PagesViewSet(viewsets.ViewSet):
def create(self, request):
# printing went here
page = Page.objects.create(
title="testing", type="other", slug="test-slug", notes="test notes"
)
serializer = PageSerializer(page)
return Response(serializer.data)
I’ve just chucked in demo data inside the page create method to ensure it works, which it does, and now want to use the real data which should be in the request.
Does anyone know what might be the issue here?
For visibility, here’s the front-end API-request function:
const createPage = async (data: CreatePageFormInputs) => {
console.log('stringified: ', JSON.stringify(data)); // logs correctly
const res = await fetchCreatePage('http://localhost:8000/api/pages/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
};
Maybe irrelevant but in case you're wondering what fetchCreatePage is, it's just this part of a custom react hook:
const fetchCreatePage: FetchDataFn = async (url, options?) => {
const setFailed = () => {
setFetchError(true);
setLoading(false);
};
const setSuccess = (data: any) => {
setData(data);
setLoading(false);
};
try {
setLoading(true);
const res = await fetch(url, options);
if (!res.ok) {
console.log('Error detected. Returning...');
setFailed();
return;
}
if (res.status === 204) {
setSuccess({
success: true,
info: 'Item successfully deleted',
});
return;
}
const data = await res.json();
setSuccess(data);
} catch (e) {
setFailed();
}
}
I assume the POST method is correct. Any help would be appreciated, thanks.
You wrote the data as body of the request in a JSON format. You thus should decode the JSON format to a dictionary with:
import json
data = json.loads(request.body)
print(data['title'])
If you are using a request from the Django REST framework, you work with request.data:
import json
print(request.data['title'])
request.data will look for POST parameters and a JSON body.

Callback URL for the API Response Django

I am building a code editor using the Hackerearth API.I have made the code to send a asynchronous API Request as it would speed up performance and reduce waiting time.
I referred their docs about sending an async request.I need to specify a callback url.Currently my project is running locally.So I couldn't figure out how to specify the callback URL and render out the Response from that callback URL.The logic to process the response received at the callback url is also specified in their docs.
def compileCode(request):
if request.is_ajax():
source = request.POST.get('source')
lang = request.POST.get('lang')
client_secret = settings.CLIENT_SECRET
data = {
"client_secret": client_secret,
"async": 1,
'id': 123,
'callback': **what to do here**,
"source": source,
"lang": lang,
}
res = requests.post(RUN_URL, data=data)
return JsonResponse(res.json(), safe=False)
return HttpResponseBadRequest()
Code to process the Response from the callback URL
def api_response(request):
payload = request.POST.get('payload', '')
payload = json.loads(payload)
run_status = payload.get('run_status')
o = run_status['output']
return HttpResponse('API Response Recieved!')
Any help is welcome :)
A callback URL "calls back" a web address rather than a bit of code and it can be invoked by API method, you can call after it's done. That URL can be anything. It doesn't have to be a static URL. Often it is a script to perform certain functions.
As here you don't need to perform anything after receiving results.
You don't need to pass the callback url, it will work even without it.
I made it work by just passing by below code.
RUN_URL = "https://api.hackerearth.com/v3/code/run/"
CLIENT_SECRET = 'your-client-secret-from-hackerearth'
data = {
'client_secret': CLIENT_SECRET,
'async': 1,
'source': source,
'lang': lang,
}
r = requests.post(RUN_URL, data=data)

How do i flatten error messages to display a single consistent error message in Django?

I am making a mobile app that uses Django Rest Framework. When one of my models fails validation, it uses one of the model.attributes as a key inside the error message, for example:
{'status_code': 400, 'name': [u'Ensure this field has no more than 32 characters.']}
{'status_code': 400, 'password': [u'Ensure this field has no more than 32 characters.']}
{'status_code': 400, 'arbitrary_field': [u'Ensure this field has no more than 32 characters.']}
This is very difficult to scale in a mobile, so I want to take the error messages and deliver a consistent 'error' key to the mobile device. For example,
{'status_code': 400, 'error': [u' Name: Ensure this field has no more than 32 characters.']}
{'status_code': 400, 'error': [u'Password: Ensure this field has no more than 32 characters.']}
{'status_code': 400, 'error': [u'Arbitrary Field: Ensure this field has no more than 32 characters.']}
In Rails, I could do this by saying:
model.errors.full_messages
But I'm not sure what the equivalent in Django is?
Thanks
You can define a property custom_full_errors in your serializer which will return the errors formatted according to your requirement. Doing serializer.custom_full_errors will give you the desired response.
We first call the serializer.errors to get the default errors dictionary. Then we iterate on this dictionary and create our desired response.
class MySerializer(serializers.ModelSerializer):
#property
def custom_full_errors(self):
"""
Returns full errors formatted as per requirements
"""
default_errors = self.errors # default errors dict
errors_messages = []
for field_name, field_errors in default_errors.items():
for field_error in field_errors:
error_message = '%s: %s'%(field_name, field_error)
errors_messages.append(error_message) # append error message to 'errors_messages'
return {'error': errors_messages}
...
For example:
# serializer.errors
{'name': [u'Ensure this field has no more than 32 characters.']}
will translate to
# serializer.custom_full_errors
{'error': [u'Name: Ensure this field has no more than 32 characters.']}
In case of multiple errors for a single field, we will get the following output on doing serializer.custom_full_errors:
# serializer.custom_full_errors
{'error': [u'Name: Error1', u'Name: Error2', u'Password: Error1', u'Password: Error2' ]}
I read this blog article, am yet to test it out
https://masnun.com/2015/11/06/django-rest-framework-custom-exception-handler.html
You create a custom error handler that concatenates the key-value pairs, after passing the exception through the default error handler method
from rest_framework.views import exception_handler
def custom_exception_handler(exc, context):
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exc, context)
if response is not None:
data = response.data
response.data = {}
errors = []
for field, value in data.items():
errors.append("{} : {}".format(field, " ".join(value)))
response.data['errors'] = errors
response.data['status'] = False
response.data['exception'] = str(exc)
return response
Then you point DRF to your custom error handler
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
),
'EXCEPTION_HANDLER': 'api.utils.custom_exception_handler'
}
In case somebody is still looking for a solution, here is a great library that helps a lot in dealing with custom error validation: https://pypi.org/project/drf-standardized-errors/, here is the GitHub repo: https://github.com/ghazi-git/drf-standardized-errors
The response will be flat and instead of this:
{
"shipping_address":
{
"non_field_errors": [ErrorDetail("We do not support shipping to the provided address.", code="unsupported")]
}
}
You'll get this:
{
"code": "unsupported",
"detail": "We do not support shipping to the provided address.",
"attr": "shipping_address.non_field_errors"
}
It deals pretty good with List Serializers, changing such response:
{
"recipients": [
{"name": [ErrorDetail("This field is required.", code="required")]},
{"email": [ErrorDetail("Enter a valid email address.", code="invalid")]},
]
}
To this:
{
"type": "validation_error",
"errors": [
{
"code": "required",
"detail": "This field is required.",
"attr": "recipients.0.name"
},
{
"code": "invalid",
"detail": "Enter a valid email address.",
"attr": "recipients.1.email"
}
]
}
Based on this flat response it's much easier to form one string message to represent all the errors if you need to.
Basically, it handles the hardest part - getting a flat response out of a complex Django structure (all the nested fields, dicts, lists, etc.)
I know it is a bit late but I made this one using backtracking in case you have a lot of embedded nested serialisers.
from rest_framework.views import exception_handler
def custom_exception_handler(exc, context):
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exc, context)
stack = [response.data]
response.data = {}
errors = {}
if response is not None:
while len(stack) > 0:
data = stack.pop()
for field, value in data.items():
if type(value) is dict:
stack.append(value)
else:
if value is not list:
errors[field] = value
continue
for val in value:
code = val.code
message = str(val)
errors[field] = {
'message': message,
'code': code
}
response.data['errors'] = errors
return response

AngularJS/Django Post Response Data

I'm using AngularJS for the front-end and Django for the backend of a web app I'm working on. Right now I'm working on logging in users and I'm having a strange problem. Heres the relevant Angular code:
app.factory('AuthService', ["$http", "$q", "Session", "URL", function($http, $q, Session, URL) {
return {
login: function(credentials) {
var deferred = $q.defer();
$http.post(URL.login, credentials)
.then(function(data, status, headers, config) {
data=data.data; //WHY DOES THIS WORK?
if (data.success == true) {
alert("logged in");
Session.create(credentials.username, data.api_key);
deferred.resolve();
}
else {
deferred.reject("Login failed!");
}
}, function(data, status, headers, config) {
deferred.reject("Login failed!");
});
return deferred.promise
},
And here is the corresponding Django view:
def login_user(request):
'''
Given a username and password, returns the users API key.
'''
if request.method == 'POST':
username = request.POST.get('username',None)
password = request.POST.get('password',None)
user = authenticate(username=username,password=password)
if user is not None:
api_key = ApiKey.objects.get(user=user)
response_data = {}
response_data["api_key"] = str(api_key).split(" ")[0]
response_data["success"] = True
return HttpResponse(json.dumps(response_data), content_type="application/json")
else:
return HttpResponse(json.dumps({"username":username,"success":False}),content_type="application/json")
return HttpResponseBadRequest()
When the user logs in a POST request is sent and handled by the above Django code. The response is then picked up by the AngularJS code above. As you can see the then() method in the Angular code takes the usual four parameters: data, status, config and headers. I expect to see data contain the dictionary output from the Django code, appropriately serialized into a JSON object.
However what happens is that the only parameter of the then() method which is not undefined is data, and this contains EVERYTHING; headers, data, status code,etc.
The line commented 'WHY DOES THIS WORK' fixes the problem, by accessing the data inside. However, I want to know why this is happening and if there is any way to avoid this. My best guess is that it has something to do with the way Django serializes a response but I'm not sure.
I'm using Django 1.6.5.
That is actually how Angular promises work according to the docs. Here is the relevant quote.
Since the returned value of calling the $http function is a promise,
you can also use the then method to register callbacks, and these
callbacks will receive a single argument – an object representing the
response. See the API signature and type info below for more details.
The emphasis was mine.

interaction Django and Ajax

I'm trying to send data to the server and use with AJAX
function stats(e){
jQuery.ajax({
type: "POST",
url:"stats",
data:{'csrfmiddlewaretoken': document.getElementsByName('csrfmiddlewaretoken')[0].value,
'test':{}},
success: function(data) {alert("Congratulations!"+data);},
error: function(data) {
alert("Please report this error: "+data.responseText);}
});
}
function in views.py:
def stats(request):
if request.is_ajax():
if request.user.is_authenticated():
if request.POST:
return HttpResponse(request.POST['test'])
else:
return HttpResponse("post_no_exists")
else:
return HttpResponse("no authenticate")
else:
raise Http404
Django raise MultiValueDictKeyError 'key "test" not found in QueryDict'.
When I change "test":{} -> "test":1 it succeds.
Whats my error?
Here:
...
data: {
'csrfmiddlewaretoken': ...,
'test': {}
}
...
You're trying to send an empty object literal as part of the POST request. jQuery can't figure out what value to send for that (empty object? what would that even mean?), so it simply doesn't send the test parameter at all. Thus, on the server side, trying to access request.POST['test'] throws a KeyError.
When you change the test to a different literal (in this case a number), jQuery can easily encode and send that as the value.
You need return answer as json
payload = {'answer': 'post_no_exists'}
return HttpResponse(json.dumps(payload, cls=LazyEncoder), content_type='application/json')
in js
alert("Congratulations!"+data.answer);