DRF - Define 'mandatory' fields in request - django

I was looking at documentation but I am maybe missing something. If I want to receive certain fields in the request's data, how can I define them? I'd like to avoid the POST or GET method to be executed if the complete fields are not in the request's data.
Example:
I need to receive (application/json):
{
"city": "Port Orange",
"state": "FL",
"formatted_address": "Peach Blossom Blvd 5329",
"_zip": "32128"
}
But if I get only
{
"city": "Port Orange",
"state": "FL",
"formatted_address": "Peach Blossom Blvd 5329"
}
I'd like return a Response with an error message 400 Bad Request. But without executing the POST method. How could I do this?
PS. I am using APIView

Do something like this
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
In your view, return the serializer errors along with the correct status code from rest_framework.status

Related

Generate a custom response for an API endpoint in Django Rest Framework using drf-spectacular

I'm trying to generate the documentation for an function based view in DRF using the drf-spectacular library.
The response that I'm trying to generate should look like this:
As you can see "data" is a list.
I tried to do the following:
class DocumentSerializer(serializers.Serializer):
date = serializers.IntegerField(default=123)
total_documents = serializers.IntegerField(default=1890)
#extend_schema(
parameters=[
OpenApiParameter(name='authorization', description='Authorization token', required=True, type=str, location=OpenApiParameter.HEADER),
],
description='Info about the endpoint',
responses={
200: inline_serializer(
name='Successfull response',
fields={
"result_code": serializers.IntegerField(default=200),
"result_description": serializers.CharField(default="Transaccion Exitosa"),
"data": DocumentSerializer(),
}
),
},
)
#api_view(["GET"])
def my_endpoint_function(request):
pass
As you can see "data" should be a list of DocumentSerializer, but I don't know how to achieve that. The result I'm obtaining with the code above is the following:
Because I don't know how to make that data has a list of DocumentSerializer.
It will be great if you can help me, I've searching in the documentation but at this point I'm stuck.
I'm not 100% sure, but adding many=True should definitely generate a list.
"data": DocumentSerializer(many=True)

spotify api "player/currently-playing" enpoint is not return currently playing song data

I am trying to get data about a song playing on one of my devices from the spotify API. I have created a view that fetches data from the API and part of it looks like this:
class Song(viewsets.ModelViewSets):
....
room_code = request.data['room_code']
room = Room.objects.filter(code=room_code)[0]
host = room.host
endpoint = 'player/currently-playing'
response = execute_spotify_api_request(host, endpoint)
item = response.get('item')
duration = item.get('duration_ms')
progress = response.get('progress_ms')
album_cover = item.get('album').get('images')[0].get('url')
return Response(response, status=status.HTTP_200_OK)
The execute_spotify_api_request(host, endpoint) is a utility function and it looks like this:
def execute_spotify_api_request(session_id, endpoint, post_=False, put_=False):
tokens = get_user_tokens(session_id)
headers = {'Content-Type': 'application/json',
'Authorization': "Bearer " + tokens.access_token}
if post_:
post(BASE_URL + endpoint, headers=headers)
if put_:
post(BASE_URL + endpoint, headers=headers)
response = get(BASE_URL, {}, headers=headers)
try:
return response.json()
except:
return {'error': 'Could not retrieve a response'}
The full url from which im fetching is ""https://api.spotify.com/v1/me/player/currently-playing"
The problem is with the response that im getting from the API, the response is not an error but data that im not expecting to get. Im getting a response that looks like this:
response = {
"display_name": "Tanatswamanyakara",
"external_urls": {
"spotify": "https://open.spotify.com/user/dlnsysel6bndktbvduz6cl79w"
},
"followers": {
"href": null,
"total": 0
},
"href": "https://api.spotify.com/v1/users/dlnsysel6bndktbvduz6cl79w",
"id": "dlnsysel6bndktbvduz6cl79w",
"images": [],
"type": "user",
"uri": "spotify:user:dlnsysel6bndktbvduz6cl79w"
}
I was hoping to get data (the progress, title, duration, album, artist etc) about the song I am playing on my spotify account but instead I get that response, how do I fix that?
N.B
My access tokens and refresh tokens are working as they should. (so I think)
If the data is not what you are expecting then there's going to be something wrong with your API endpoint. You can use the Spotify developer console to generate the endpoint link, it would be worth debugging your execute_spotify_api_request code and the url it generates against the value in the console.
Having just re-read your code half way through answering, I've noticed that you aren't appending your endpoint variable to your GET url:
response = get(BASE_URL, {}, headers=headers)
This means that the get doesn't have 'player/currently-playing' and just returns the base URL which is probably 'https://api.spotify.com/v1/me/' - hence the response you receive is just your profile data.

Telegram bot sendMediaGroup() in Postman - JSON-serialized array?

I'm using Postman app to interact with a Telegram bot api. I've sent photos using the sendPhoto() method, like this:
https://api.telegram.org/botToken/sendPhoto?chat_id=00000000&photo=AgAC***rgehrehrhrn
But I don't understand the sendMediaGroup() method. Can someone post an example how to compose the https string to send two photos?
Thanks
You need to send a POST request at the url https://api.telegram.org/botToken/sendPhoto with a JSON body. You are using the url to specify all the parameters of the request but urls are only 2000 characters long. The body of a POST request, instead, has no limits in terms of size. The JSON body should look something like this:
{
"chat_id": 777000,
"media": [
{
"type": "photo",
"media": "https://example.com/first_photo_url.png",
"caption": "an optional description of the first photo",
"parse_mode": "optional (you can delete this parameter) the parse mode of the caption"
},
{
"type": "photo",
"media": "https://example.com/fsecond_photo_url.png",
"caption": "an optional description of the second photo",
"parse_mode": "optional (you can delete this parameter) the parse mode of the caption"
}
],
}
For more info see:
how to send JSON (raw) data with Postman
and
sendMediaGroup Telegram API's method.
You must send JSON as a string, or serialized JSON, to Telegram API. The format is as same as #GioIacca9's answer.
Note: only the caption in the first image will be showen.
Have a try this Python code.
def send_photos(api_key, chat_id, photo_paths):
params = {
'chat_id': chat_id,
'media': [],
}
for path in photo_paths:
params['media'].append({'type': 'photo', 'media': path})
params['media'] = json.dumps(params['media'])
url = f'https://api.telegram.org/bot{api_key}/sendMediaGroup'
return requests.post(url, data=params)
if __name__ == '__main__':
send_photos('your_key', '#yourchannel', ['http://your.image.one', 'http://your.image.two'])

Python requests POST don't contain all sended data

I'm trying to create new order on PayU, via their REST api. I'm sending "get access token", and i have correct answer. Then i send 'create new order', aaaaand i ve got 103 error, error syntax.
I was trying on https://webhook.site/ , and realized why syntax is bad - i have no values in list.
Code of sending POST, when creating new order:
data = {
"notifyUrl": "https://your.eshop.com/notify",
"customerIp": "127.0.0.1",
"merchantPosId": "00000",
"description": "RTV market",
"currencyCode": "PLN",
"totalAmount": "15000",
"products": [{
"name": "Wireless mouse",
"unitPrice": "15000",
"quantity": "1"}]}
headers = {
"Content-Type": "application/json",
"Authorization": str('Bearer ' + access_token).encode()}
r = requests.post('https://webhook.site/9046f3b6-87c4-4be3-8544-8a3454412a55',
data=payload,
headers=headers)
return JsonResponse(r.json())
Webhooc show what i have posted:
customerIp=127.0.0.1&notifyUrl=https%3A%2F%2Fyour.eshop.com%2Fnotify&currencyCode=PLN&products=name&products=unitPrice&products=quantity&description=RTV+market&merchantPosId=00000&totalAmount=15000
There is no values of 'name', 'unitprice' and 'quantity'. PayU confirmed thats the only problem.
Why? What is wrong?
Sending simple POST request to get a token is always successful.
If you want to send JSON, use the json argument of post():
r = requests.post('https://webhook.site/9046f3b6-87c4-4be3-8544-8a3454412a55',
json=payload, # Use the json argument
headers=headers)
Otherwise the data will be sent as form-encoded data, which I guess isn't what you want, given you're expecting to send the nested products list.
When you use the json argument, the content type is automatically set to application/json so you don't have to set it yourself.
headers = {
# Content-Type not required
"Authorization": str('Bearer ' + access_token).encode()
}
Further info on using requests to send JSON here

Custom error message json object with flask-restful

It is easy to propagate error messages with flask-restful to the client with the abort() method, such as
abort(500, message="Fatal error: Pizza the Hutt was found dead earlier today
in the back seat of his stretched limo. Evidently, the notorious gangster
became locked in his car and ate himself to death.")
This will generate the following json output
{
"message": "Fatal error: Pizza the Hutt was found dead earlier today
in the back seat of his stretched limo. Evidently, the notorious gangster
became locked in his car and ate himself to death.",
"status": 500
}
Is there a way to customise the json output with additional members? For example:
{
"sub_code": 42,
"action": "redirect:#/Outer/Space"
"message": "You idiots! These are not them! You've captured their stunt doubles!",
"status": 500
}
People tend to overuse abort(), while in fact it is very simple to generate your own errors. You can write a function that generates custom errors easily, here is one that matches your JSON:
def make_error(status_code, sub_code, message, action):
response = jsonify({
'status': status_code,
'sub_code': sub_code,
'message': message,
'action': action
})
response.status_code = status_code
return response
Then instead of calling abort() do this:
#route('/')
def my_view_function():
# ...
if need_to_return_error:
return make_error(500, 42, 'You idiots!...', 'redirect...')
# ...
I don't have 50 reputation to comment on #dappiu, so I just have to write a new answer, but it is really related to "Flask-RESTful managed to provide a cleaner way to handle errors" as very poorly documented here
It is such a bad document that took me a while to figure out how to use it. The key is your custom exception must inherit from flask_restful import HTTPException. Please note that you cannot use Python Exception.
from flask_restful import HTTPException
class UserAlreadyExistsError(HTTPException):
pass
custom_errors = {
'UserAlreadyExistsError': {
'message': "A user with that username already exists.",
'status': 409,
}
}
api = Api(app, errors=custom_errors)
Flask-RESTful team has done a good job to make custom exception handling easy but documentation ruined the effort.
As #Miguel explains, normally you shouldn't use exceptions, just return some error response. However, sometimes you really need an abort mechanism that raises an exception. This may be useful in filter methods, for example. Note that flask.abort accepts a Response object (check this gist):
from flask import abort, make_response, jsonify
json = jsonify(message="Message goes here")
response = make_response(json, 400)
abort(response)
I disagree with #Miguel on the pertinence of abort(). Unless you're using Flask to build something other than an HTTP app (with the request/response paradigm), I believe that you should use as much of the HTTPExceptions as possible (see the werkzeug.exceptions module). It also means using the aborting mechanism (which is just a shortcut to these exceptions). If instead you opt to explicitly build and return your own errors in views, it leads you into a pattern where you need to check values with a series of if/else/return, which are often unnecessary. Remember, your functions are more than likely operating in the context of a request/response pipeline. Instead of having to travel all the way back to the view before making a decision, just abort the request at the failing point and be done with it. The framework perfectly understands and has contingencies for this pattern. And you can still catch the exception in case you need to (perhaps to supplement it with additional messages, or to salvage the request).
So, similar to #Miguel's but maintaining the intended aborting mechanism:
def json_abort(status_code, data=None):
response = jsonify(data or {'error': 'There was an error'})
response.status_code = status_code
abort(response)
# then in app during a request
def check_unique_username(username):
if UserModel.by__username(username):
json_abort(409, {'error': 'The username is taken'})
def fetch_user(user_id):
try:
return UserModel.get(user_id)
except UserModel.NotFound:
json_abort(404, {'error': 'User not found'})
I had to define attribute code to my subclassed HttpException for this custom error handling to work properly:
from werkzeug.exceptions import HTTPException
from flask_restful import Api
from flask import Blueprint
api_bp = Blueprint('api',__name__)
class ResourceAlreadyExists(HTTPException):
code = 400
errors = {
'ResourceAlreadyExists': {
'message': "This resource already exists.",
'status': 409,
},
}
api = Api(api_bp, errors=errors)
and then later, raise the exception
raise ResourceAlreadyExists
It's obviously late, but in the meanwhile Flask-RESTful managed to provide a cleaner way to handle errors, as pointed out by the docs.
Also the issue opened to suggest the improvement can help.
Using Flask-RESTful (0.3.8 or higher)
from flask_restful import Api
customErrors = {
'NotFound': {
'message': "The resource that you are trying to access does not exist",
'status': 404,
'anotherMessage': 'Another message here'
},
'BadRequest': {
'message': "The server was not able to handle this request",
'status': 400,
'anotherMessage': 'Another message here'
}
}
app = Flask(__name__)
api = Api(app, catch_all_404s=True, errors=customErrors)
The trick is to use the exceptions from Werkzeug Docs
So for instance, if you want to handle a 400 request, you should add BadRequest to the customErrors json object.
Or if you want to handle 404 errors, then use NotFound in your json object
and so on