Django-tastypie. Output in JSON to the browser by default - django

I see 'Sorry, not implemented yet. Please append "?format=json" to
your URL.'. I need always append string "?format=json". Can I make a
output in JSON by default?
Regards,
Vitaliy

From the tastypie cookbook, in order to change the default format, you need to override the determine_format() method on your ModelResource:
class MyResource(ModelResource):
....
def determine_format(self, request):
return 'application/json'
The above link demonstrates alternative methods of determining output format.
Also, I don't think a valid answer is essentially "You don't need this".
Edit
It appears GregM's answer is probably (I haven't tested it) the most correct with the new version of TastyPie, as per documentation putting the following in your settings.py will restrict the serialization output to json.
TASTYPIE_DEFAULT_FORMATS = ['json']

As of tastypie 0.9.13, if you do not need XML support you can disable it globally by setting TASTYPIE_DEFAULT_FORMATS to just ['json'] in your settings.py file. Requests should then default to JSON.

I've tested setting TASTYPIE_DEFAULT_FORMATS to ['json'] but it doesn't prevent the "Sorry not implemented yet" message when viewing the API from a browser.
I am able to make that warning go away by forcing the "Accept" header to 'application/json' in a middleware:
class TastyJSONMiddleware(object):
"""
A Django middleware to make the Tastypie API always output in JSON format
instead of telling browsers that they haven't yet implemented text/html or
whatever.
WARNING: This includes a hardcoded url path for /api/. This is not 'DRY'
because it means you have to edit two places if you ever move your API
path.
"""
api_prefix = '/api/'
def process_request(self, request):
if request.path.startswith(self.api_prefix):
request.META['HTTP_ACCEPT'] = 'application/json'

Tasytpie has the defaults set as 'application/json'. But that is overridden by Browser request.
According to Tastypie, the default is overridden by Request Header ACCEPT and your format specification in GET ie. ?format=json. When you send request from browsers, if you see the Request HTTP Header sent, its something like -
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
The application/xml overrides the default in Tastypie Resource. Therefore, either you can set Browser Header to have 'application/json' (Bad idea) or you just specify in GET.
If you hit the same API url using CURL, you will see the JSON output without specifying that in GET.

To examine/test your REST API, use a Rest client instead of a browser, preferably one that knows how to pretty print JSON. I use the Postman plugin for Google Chrome.
If you want pretty json in command line:
curl https://api.twitter.com/1.1/search/tweets.json | python -m json.tool

Related

Django drf-spectacular - Can you exclude specific paths?

We have a bunch of api's with different versions in urls.py, eg
api/v1
api/v2
api/v3
. We want to implement swagger with drf-spectacular, but we only want to expose api/v3 endpoints.
Is there a way to do this? I cannot make sense of the documentation.
Thanks
This works for me:
def preprocessing_filter_spec(endpoints):
filtered = []
for (path, path_regex, method, callback) in endpoints:
# Remove all but DRF API endpoints
if path.startswith("/api/"):
filtered.append((path, path_regex, method, callback))
return filtered
In settings:
"PREPROCESSING_HOOKS": ["common.openapi.preprocessing_filter_spec"],
Worked it out. Copied the custom preprocessing hook function, changed the pass statement to filter what I did not require in the endpoints, then mapped the location of the file and function in my spectacular settings for preprocessed hooks.

Django URLs: URL Issues [duplicate]

Is there a way to check if a request is AJAX in Python?
The equivalent of PHP's $_SERVER['HTTP_X_REQUESTED_WITH'] == 'xmlhttprequest'?
If the AJAX framework sets the X-Requested-With header in its requests, then you will be able to use that header to detect AJAX calls. It's up to the client-side framework to do this.
Getting hold of the HTTP headers depends on your Python framework of choice. In Django, the request object has an is_ajax method you can use directly.
is_ajax() is deprecated since Django 3.1 (as of 28th May, 2021) as they stated that it depends on jQuery ways of sending request but since people use fetch method a lot to make call Ajax calls, it has become unreliable in many cases.
Usually, text/html is requested in the header in the first option in the browser when it just the loads the page. However, if you were making API call, then it would become */* by default, if you attach Accept: application/json in the headers, then it become
application/json
So, you can check easily if the request is coming from ajax by this
import re # make sure to import this module at the top
in your function
requested_html = re.search(r'^text/html', request.META.get('HTTP_ACCEPT'))
if not requested_html:
# ajax request it is
Check this how to check if request is ajax in turbogears
also this for how to do this in Django(it depends in your framework): How to check contents of incoming HTTP header request
Just in case someone can find this useful, the Sec-Fetch-Dest can hint if the request is an ajax call or not.
So one could have something like this:
is_ajax_call = all(
request.headers.get("Sec-Fetch-Dest", "")
not in secFetchDest for secFetchDest in ["document", "iframe"]
)

POST request to Django DRF call working in cURL but not with Postman

I'm following the instructions to support TokenAuthentication in my rest-api site, shown here. Using cURL, I have been able to obtain my user's token (username - example, password - example), through the following command:
curl -X POST -d "username=example&password=example" localhost:8000/api/login/
This returns a successful response, with example's authentication token.
Yet when I do (what I think is) the same thing through Postman, it simply does not work. See image below.
From the error code (400 - Bad request), it seems like it's not even receiving the POST parameters at all. Can anyone help me here?
See your URL in postman. There is attached query String with the URL.So remove that query String from the URL and send parameters as a post request like this.
http://localhost:8000/api/login/
Even this is very old question, but if this answer would be helpful...
I had exactly same issue
solution:
don't put username and password in address bar,but only
and in body put json data of your username and password as below
be careful, don't use single quotation marks'', but use double quotation marks "" instead, otherwise will fail, no clue why
Depending on how your API is set up, you probably need to specify the content type in your request headers, Content-Type: application/json.

Custom HTTP headers are different for tests and httpie

I'm using Django Rest Framework. In a permissions class I check the value of two custom http headers: mt_api_token and mt_api_key.
In tests I call the API with my custom headers. sign_request() returns {'mt_api_key': api_client.client_id, 'mt_api_token': token}.
class APIv1SimpleTestCase(APITestCase):
[...]
response = self.client.get(url, format='json', **sign_request("", api_client=oauth2_client))
This is a legacy system and headers must have underscores instead of dashes.
The permissions class:
class AuthPermissions(permissions.BasePermission):
def is_token_valid(self, request):
mt_api_token = request.META['mt_api_token']
[...]
Tests pass.
However, if I call the api using httpie:
$ http :8000/api/v1/endpoint1/ mt_api_key:0a9..66 mt_api_token:7b2...8
I get keyError exception because request.META['mt_api_key'] and request.META['mt_api_token'] don't exist.
I have to use request.META['HTTP_MT_API_TOKEN'], which makes sense according to DRF documentation for request.META, but I can't find out why tests pass. if I change the implementation to request.META['HTTP_...'] my tests fail.
This is working fine in the tests, because the tests are manually setting the request.META keys to match what you are passing in, mt_api_token and mt_api_key. You aren't seeing any errors because this is perfectly valid, but it doesn't match what you are expecting. This is covered very briefly in the Django advanced testing documentation, but essentially the extra arguments are directly added to the META dictionary.
When your request goes through the WSGI handler provided by Django, any HTTP headers are automatically prefixed with HTTP_ per the WSGI specification. This applies to most client-provided HTTP headers, including the Authorization header which is typically used to authorize users on an API (using Basic authentication, OAuth, etc.), which is put into the request.META dictionary as HTTP_AUTHORIZATION. Note that the keys are also always uppercase, as HTTP header names are case-insensitive.
You can fix your tests by having your sign_request method return the keys as HTTP_MT_API_TOKEN and HTTP_MT_API_KEY, which are what they would be passed in as when coming through a browser. You will also need to adjust your view code to reference those new keys.

Django jQuery ajax post to https fails

I'm writing a chrome extension that use GET and POST ajax calls to an api made with Django and Tastypie.
GET ajax calls are successful and I can access data. However POST calls are failing only in production because the api is hosted with https://, in local environment it works (htt://localhost:8000).
I am providing a correct CSRF token in the header.
It seems that the error is happening here: https://github.com/toastdriven/django-tastypie/blob/master/tastypie/authentication.py#L259 where the api require a Referer header to check that the call is secure.
It does not seem possible to set in my ajax headers the Referer value directly, only if the name of the header starts with X-.
Thanks in advance for any solutions or tips to solve this issue.
Maxime.
The reason for that check is explained in the original source of that method:
# Suppose user visits http://example.com/
# An active network attacker (man-in-the-middle, MITM) sends a
# POST form that targets https://example.com/detonate-bomb/ and
# submits it via JavaScript.
#
# The attacker will need to provide a CSRF cookie and token, but
# that's no problem for a MITM and the session-independent
# nonce we're using. So the MITM can circumvent the CSRF
# protection. This is true for any HTTP connection, but anyone
# using HTTPS expects better! For this reason, for
# https://example.com/ we need additional protection that treats
# http://example.com/ as completely untrusted. Under HTTPS,
# Barth et al. found that the Referer header is missing for
# same-domain requests in only about 0.2% of cases or less, so
# we can use strict Referer checking.
So, you may or may not benefit from the referrer check - it's up to you to decide.
If you wish to override it, just set your models to authenticate using a subclass of SessionAuthentication, and override the is_authenticated(self, request, **kwargs) function to your needs. The original method is quite succinct, so honestly I'd just copy-paste it and remove the offending if request.is_secure(): block rather than tricking the superclass into thinking the request has a referrer.
You can also skip checking the referer in AJAX calls by making the request object think it was not made by a secure call. That way, you keep your CSRF checks and everything else, just skip the referer. Here's a sample middleware:
class UnsecureAJAX(object):
def process_request(self, request):
setattr(request, '_is_secure_default', request._is_secure)
def _is_secure():
if request.is_ajax():
return False
else:
return request._is_secure_default()
setattr(request, '_is_secure', _is_secure)
It is also now technically possible to change the Referer part of a request with webRequest API.
You would need an onBeforeSendHeaders listener with ["blocking", "requestHeaders"] options, and corresponding permissions. Then you can intercept your own requests and modify the header.