I'm working on a project using Python(3.6) in which I need to implement GitHub API.
I have tried by using JSON apis as:
from views.py:
class GhNavigator(CreateView):
def get(self, request, *args, **kwargs):
term = request.GET.get('search_term')
username = 'arycloud'
token = 'API_TOKEN'
login = requests.get('https://api.github.com/search/repositories?q=' + term, auth=(username, token))
print(login)
return render(request, 'navigator/template.html', {'login': login})
but it simply returns status 200, I want to get a list of repositories for term which is passed by the user.
How can I achieve that?
Help me, please!
Thanks in advance!
The requests library will return a Response object if you perform a .get(..), .post(..) or anything like that. Since responses can be very huge (hundreds of lines), it does not print the content by default.
But the developers attached some convenient functions to it, for example to interpet the answer as a JSON object. A response object has a .json() function that aims to interpret the content as a JSON string, and returns its Vanilla Python counterpart.
So you can access the response (and render it the way you want), by calling .json(..) on it:
class GhNavigator(CreateView):
def get(self, request, *args, **kwargs):
term = request.GET.get('search_term')
username = 'arycloud'
token = 'API_TOKEN'
response = requests.get('https://api.github.com/search/repositories?q=' + term, auth=(username, token))
login = response.json() # obtain the payload as JSON object
print(login)
return render(request, 'navigator/template.html', {'login': login})
Now of course it is up to you to interpret that object according to your specific "business logic", and render a page that you think contains the required information.
Related
I'm working at the moment on an implementation of webauthn on a project. The main point is to give the possibility to user to use FaceId or fingerprint scan on their mobile on the website.
I tried the djoser version of webauthn but I wanted to give the possibility to user that already have an account so I took the implementation of webauthn of djoser and I updated it to make it working with already created account.
I can ask for the signup request of a webauthn token and create the webauthn token with the front (Angular) where I use #simplewebauthn/browser ("#simplewebauthn/browser": "^6.3.0-alpha.1") . Everything is working fine there.
I use the latest version of djoser by pulling git and the version of webauthn is 0.4.7 linked to djoser.
djoser #git+https://github.com/sunscrapers/djoser.git#abdf622f95dfa2c6278c4bd6d50dfe69559d90c0
webauthn==0.4.7
But when I send back to the backend the result of the registration, I have an error:
Authentication rejected. Error: Invalid signature received..
Here's the SignUpView:
permission_classes = (AllowAny,)
def post(self, request, ukey):
co = get_object_or_404(CredentialOptions, ukey=ukey)
webauthn_registration_response = WebAuthnRegistrationResponse(
rp_id=settings.DJOSER["WEBAUTHN"]["RP_ID"],
origin=settings.DJOSER["WEBAUTHN"]["ORIGIN"],
registration_response=request.data,
challenge=co.challenge,
none_attestation_permitted=True,
)
try:
webauthn_credential = webauthn_registration_response.verify()
except RegistrationRejectedException as e:
return Response(
{api_settings.NON_FIELD_ERRORS_KEY: format(e)},
status=status.HTTP_400_BAD_REQUEST,
)
user = User.objects.get(username=request.data["username"])
user_serializer = CustomUserSerializer(user)
co.challenge = ""
co.user = user
co.sign_count = webauthn_credential.sign_count
co.credential_id = webauthn_credential.credential_id.decode()
co.public_key = webauthn_credential.public_key.decode()
co.save()
return Response(user_serializer.data, status=status.HTTP_201_CREATED)
And I based my work on https://github.com/sunscrapers/djoser/blob/abdf622f95dfa2c6278c4bd6d50dfe69559d90c0/djoser/webauthn/views.py#L53
Here's also the SignUpRequesrtView where I edited some little things to make it working the way I want:
class SignupRequestView(APIView):
permission_classes = (AllowAny,)
def post(self, request):
CredentialOptions.objects.filter(username=request.data["username"]).delete()
serializer = WebauthnSignupSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
co = serializer.save()
credential_registration_dict = WebAuthnMakeCredentialOptions(
challenge=co.challenge,
rp_name=settings.DJOSER["WEBAUTHN"]["RP_NAME"],
rp_id=settings.DJOSER["WEBAUTHN"]["RP_ID"],
user_id=co.ukey,
username=co.username,
display_name=co.display_name,
icon_url="",
)
return Response(credential_registration_dict.registration_dict)
And I also updated the WebAuthnSignupSerializer to retrieve an check if there's an account with the username given and if yes, create the CredentialOptions:
class WebauthnSignupSerializer(serializers.ModelSerializer):
class Meta:
model = CredentialOptions
fields = ("username", "display_name")
def create(self, validated_data):
validated_data.update(
{
"challenge": create_challenge(
length=settings.DJOSER["WEBAUTHN"]["CHALLENGE_LENGTH"]
),
"ukey": create_ukey(length=settings.DJOSER["WEBAUTHN"]["UKEY_LENGTH"]),
}
)
return super().create(validated_data)
def validate_username(self, username):
if User.objects.filter(username=username).exists():
return username
else:
raise serializers.ValidationError(f"User {username} does not exist.")```
EDIT: TL;DR;
#simplewebauthn/browser encodes the signature as base64url while duo-labs/py_webauthn expects a hex encoded signature.
Well, it's not really an answer, but rather a little "assistance".
You can check whether the signature is valid using this little tool (at the bottom of the page): https://webauthn.passwordless.id/demos/playground.html
At least, using that, you will know if your data is correct or if something was stored wrongly. There are so many conversions from bytes to base64url and back that it's not always easy to track. Perhaps it is a data format/convertion issue? Like not converting to bytes, or accidentally double encoding as base64url.
Lastly, the stored public key has a different format depending on the algorithm. Either "raw" or "ASN.1" wrapped, in case you have a problem with the key itself.
Good luck!
EDIT:
While delving a bit into the source code of sunscrapers/djoser, I noticed something quite odd. While all data is encoded as base64, it appears the signature is hex encoded instead, see their test app
That seems to be because it uses duo-labs/py_webauthn as dependency which expects a hex encoded signature. On the other hand, the #simplewebauthn/browser lib encodes it into base64url, like all other data.
The verify() method expects a RegistrationResponse object as an argument, but you're passing it the entire request data. You need to extract the registrationResponse field from the request data and pass that to the verify() method instead.
Change this:
webauthn_registration_response = WebAuthnRegistrationResponse(
rp_id=settings.DJOSER["WEBAUTHN"]["RP_ID"],
origin=settings.DJOSER["WEBAUTHN"]["ORIGIN"],
registration_response=request.data, # <-- update this line
challenge=co.challenge,
none_attestation_permitted=True,)
To this:
webauthn_registration_response = WebAuthnRegistrationResponse(
rp_id=settings.DJOSER["WEBAUTHN"]["RP_ID"],
origin=settings.DJOSER["WEBAUTHN"]["ORIGIN"],
registration_response=request.data['registrationResponse'], # <-- update this line
challenge=co.challenge,
none_attestation_permitted=True,)
I am using DRF for the first time and I would like to know what the "format" in the following code snippet from their website do:
class CommentList(APIView):
def get(self, request, format=None):
# do stuff...
def post(self, request, format=None):
# do stuff...
I read the docs, but I am not sure how it works still. Can someone enlighten me with an example? Thanks
For example,
Suppose their is an APIView with following code :
class HelloAPIView(APIView):
def get(self, request, format):
# do stuff...
def post(self, request, format):
# do stuff...
And endpoint url for HelloAPIView is :
http://example.com/api/users
Now if u want the data in response to be in json format then the URL you would access
will be like
http://example.com/api/users.json
So when you access urls like above , then value " json " will be passed as 3rd argument (1st is self , 2nd is request and 3rd is format) to get() or post() methods of HelloAPIView.
So basically format parameter is used to define in which format you want the response.
For more details refer
https://www.django-rest-framework.org/api-guide/format-suffixes/
https://www.django-rest-framework.org/tutorial/2-requests-and-responses/#adding-optional-format-suffixes-to-our-urls
Format is added to handel multiple content types in DRF
Using format suffixes gives us URLs that explicitly refer to a given format, and means our API will be able to handle URLs such as
http://localhost/api/items/4.json
Adding format-suffix patterns to each individual entry in the URLconf for your API is error-prone and non-DRY, so REST framework provides a shortcut to adding these patterns to your URLConf.
for more details refer this
I have an endpoint where I need to make make a request to a third party API to get a list of items and return the results to the client.
Which of the following, or any other approach would be better suited to DRF?
Make input parameter validation and the call to the third party API and in the view method, pass the list of items in the response to a serializer for serialization and return serializer data to the client
Pass the request parameters to the serializer as write-only fields, make the field validation, api call and serialization in the serializer
A mixture of 1 and 2; use 2 different serializers, one that takes request parameters as write only fields, validates input parameters and makes the request to the 3rd party api, and another serializer that takes the resulting list from the first serializer and serializes the items for use of client
Since your question not mentioning anything about writing data into DB, undoubtedly you can go with Method-1.
Let's look into this sample api, which return a list of items (a list api).
Case - 1 : We need show the same response as we got from third-party api
In that case, we don't need any serializer or serialization process, all we need is pass the data from third-party API to the client.
from rest_framework.decorators import api_view
from rest_framework.response import Response
import requests
#api_view()
def my_view(request):
tp_api = "https://jsonplaceholder.typicode.com/posts"
response = requests.get(tp_api)
return Response(data=response.json())
Case - 2 : If you don't need complete data, but few parts (id and body)
In this particular situation, you can go with pythonic loop or DRF serializer.
# using DRF serializer
from rest_framework import serializers
# serializer class
class Myserializer(serializers.Serializer):
id = serializers.CharField()
body = serializers.CharField()
#api_view()
def my_view(request):
tp_api = "https://jsonplaceholder.typicode.com/posts"
response_data = requests.get(tp_api).json()
my_serializer = Myserializer(data=response_data, many=True)
my_serializer.is_valid(True)
return Response(data=my_serializer.data)
#Python loop way
#api_view()
def my_view(request):
tp_api = "https://jsonplaceholder.typicode.com/posts"
response_data = requests.get(tp_api).json()
data = [{"id": res['id'], "body": res['body']} for res in response_data]
return Response(data=data)
In case-2, I would reccomend to use DRF serializer, which does lots of things like validation, etc
When coming into your second approch, doing validation of the input data would depends on your requirement. As you said in comments, you need to provide some inputs to the third-party api. So, the validation should be carried out before accessing the third-party api
# Validation example
class MyInputSerializer(serializers.Serializer):
postId = serializers.IntegerField(max_value=10)
class MyOutputSerializer(serializers.Serializer):
id = serializers.CharField()
body = serializers.CharField()
#api_view()
def my_view(request):
input = MyInputSerializer(data=request.GET)
input.is_valid(True)
tp_api = "https://jsonplaceholder.typicode.com/comments?postId={}".format(input.data['postId'])
response_data = requests.get(tp_api).json()
my_serializer = MyOutputSerializer(data=response_data, many=True)
my_serializer.is_valid(True)
return Response(data=my_serializer.data)
Conclusion
The DRF is flexible enough to get desired output format as well as taking data into the system. In short, It all depends on your requirements
I am writing an API in Python Django and rest framework. I am using a python packaged called python-amazon-simple-product-api to access amazon advertising API. I am trying to feed the results into the rest framework and return the results as JSON Here is my code so far.
class AmazonProductsViewSet(viewsets.ViewSet):
def list(self, request, format=None):
products = amazon.search(Brand="Microsoft", SearchIndex="Software",
ResponseGroup="Images,ItemAttributes,Accessories,Reviews,VariationSummary,Variations")
products = list(products)
With this code I get the following error;
TypeError: Object of type 'AmazonProduct' is not JSON serializable
So I am trying to find a way of making the AmazonProduct object serializable or a better solution.
Not JSON serializable means that your response is an object not a primitive data that can be sent over a network.
You need to write a serializer for that model. Something like this:
class AmazonProductSerializer(serializers.Serializer):
color = serializers.CharField()
title = serializers.CharField()
and use it like this:
products = amazon.search(Brand="Microsoft", SearchIndex="Software", ResponseGroup="Images,ItemAttributes,Accessories,Reviews,VariationSummary,Variations")
data = AmazonProductSerializer(products, many=True).data
return Response(data, status=status.HTTP_200_OK)
Hope it helps!
Probably the best approach is to use the bottlenose [ https://github.com/lionheart/bottlenose ] instead if you just want to get the amazon results as they're and convert to JSON. Here is how I have done it;
amazon = bottlenose.Amazon(access_key_id, secret_key, associate_tag)
class AmazonProductsViewSet(viewsets.ViewSet):
def list(self, request, format=None):
response = amazon.ItemSearch(Keywords="Kindle 3G", SearchIndex="All")
return Response(xmltodict.parse(response)) #json.dumps(xmltodict.parse(response))
Now I get the entire XML document as JSON.
I am trying to write something elegant where I am not relying on Request object in my code. All the examples are using:
(r'^hello/(?P.*)$', 'foobar.views.hello')
but it doesn't seem like you can post to a URL like that very easily with a form. Is there a way to make that URL respond to ..../hello?name=smith
Absolutely. If your url is mapped to a function, in this case foobar.views.hello, then that function might look like this for a GET request:
def hello(request):
if request.method == "GET":
name_detail = request.GET.get("name", None)
if name_detail:
# got details
else:
# error handling if required.
Data in encoded forms, i.e. POST parameters, is available if you HTTP POST from request.POST.
You can also construct these yourself if you want, say, query parameters on a POST request. Just do this:
PARAMS = dict()
raw_qs = request.META.get('QUERY_STRING', '') # this will be the raw query string
if raw_qs:
for line in raw_qs.split("&"):
key,arg = line.split("=")
PARAMS[key] = arg
And likewise for form-encoded parameters in non POST requests, do this:
FORM_PARAMS = QueryDict(request.raw_post_data)
However, if you're trying to use forms with Django, you should definitely look at django.forms. The whole forms library will just generally make your life easier; I've never written a html form by hand using Django because this part of Django takes all the work out of it. As a quick summary, you do this:
forms.py:
class FooForm(forms.Form):
name = fields.CharField(max_length=200)
# other properties
or even this:
class FooForm(forms.ModelForm):
class Meta:
model = model_name
Then in your request, you can pass a form out to the template:
def pagewithforminit(request):
myform = FooForm()
return render_to_response('sometemplate.html', {'nameintemplate': myform},
context_instance=RequestContext(request))
And in the view that receives it:
def pagepostingto(request):
myform = FooForm(request.POST)
if myform.is_valid(): # check the fields for you:
# do something with results. if a model form, this:
myform.save()
# creates a model for you.
See also model forms. In short, I strongly recommend django.forms.
You can't catch GET parameters in a URL pattern. As you can see in django.core.handlers.base.BaseHandler.get_response, only the part of the URL that ends up in request.path_info is used to resolve an URL:
callback, callback_args, callback_kwargs = resolver.resolve(
request.path_info)
request.path_info does not contain the GET parameters. For handling those, see Ninefingers answer.