I have to create API endpoint that will take data from GET request body. It will find necessary data in already created database to perform calculation and then it should return calculation result as response.
So I found a way to do it but I'm not sure if it should look like this. It works but you know :)
urls.py
schema_view = get_schema_view(
openapi.Info(
title="API Test",
default_version='0.0.1',
description="Test",
),
public=True,
)
urlpatterns = [
path('docs/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
path('equation/1/example', EquationView.as_view()),
#... more paths to other equations...
]
view.py
def get_emission_val(req_emiss_fac):
try:
return Emission.objects.get(emission_factor=req_emiss_fac)
except Emission.DoesNotExist:
raise Http404("Emission value does not exist in database")
equation_response = openapi.Response('response description', EquationResponseSerializer)
#swagger_auto_schema(
query_serializer=EquationRequestSerializer,
responses={
'404':'Emission factor does not exist in database',
'200': equation_response
},
)
class EquationView(APIView):
def get(self, request):
emission_val = get_emission_val(request.data['emission_factor'])
combusted_val = request.data['combusted_value']
emissions = combusted_val * emission_val.co2_emission
return Response({'emissions':emissions})
serializers.py
class EquationRequestSerializer(serializers.Serializer):
emission_factor = serializers.CharField()
combusted_value = serializers.FloatField()
class EquationResponseSerializer(serializers.Serializer):
emission = serializers.FloatField()
What do you think about this approach? I'm not sure if I have to do it in this way. I'm using swagger and additional serializers for gerenerating api docs
Example get request body:
{
"emission_factor":"Test",
"combusted_value":1.0
}
Example response:
{
"emissions": 1.5129
}
Related
Last week I posted a kind of vague question as I was trying to join data from an external Rest API with a local SQLAlchemy schema. Unsurprisingly, I didn't get a lot of responses and after some experimentation with parsing the response json into a temporary table I've decided to move away from this approach and add a new resolver for the external API that can be called separately from the resolver for SQLAlchemy. This is what I have so far for the querying the external API:
class Red(graphene.ObjectType):
redName = graphene.String()
accessLevel = graphene.String()
numBytes = graphene.Int()
class Query(graphene.ObjectType):
red = graphene.Field(Red, redName = graphene.String(required=True))
def resolve_red(self, info, **kwargs):
dataset_Red = <custom library for querying API>.dataset(kwargs['redName'])
dataset_Red.get()
resp = dataset_Red.properties
return json.dumps(resp)
GraphiQL recognizes my new resolver and will run the query with no errors but it returns no data. For example, if this is my query:
query{red(redName:"<dataset_name>") {
accessLevel
numBytes
}
}
I get the following response:
{
"data": {
"red": {
"accessLevel": null,
"numBytes": null
}
}
}
What did I miss? I'm thinking there's an issue with class definition. Can someone show me what I did wrong?
I think you should return Red type from resolver resolve_red instead of json dumped string:
class Red(graphene.ObjectType):
redName = graphene.String()
accessLevel = graphene.String()
numBytes = graphene.Int()
class Query(graphene.ObjectType):
red = graphene.Field(Red, redName = graphene.String(required=True))
def resolve_red(self, info, **kwargs):
dataset_Red = <custom library for querying API>.dataset(kwargs['redName'])
dataset_Red.get()
resp = dataset_Red.properties
return Red(
redName=resp.get('redName'),
accessLevel=resp.get('accessLevel'),
NumBytes=resp.get('numBytes')
)
Or assuming fields in response are the same as Red attributes:
class Red(graphene.ObjectType):
redName = graphene.String()
accessLevel = graphene.String()
numBytes = graphene.Int()
class Query(graphene.ObjectType):
red = graphene.Field(Red, redName = graphene.String(required=True))
def resolve_red(self, info, **kwargs):
dataset_Red = <custom library for querying API>.dataset(kwargs['redName'])
dataset_Red.get()
resp = dataset_Red.properties
return Red(**resp)
Sound I found a solution that seems to work. The issue was the response type, I had to serialize and then deserialize it for flask (obvious right?).
I added this two functions:
def _json_object_hook(d):
return namedtuple('X', d.keys())(*d.values())
def json2obj(data):
return json.loads(data, object_hook=_json_object_hook)
And then changed my resolver to call json2obj on the response:
def resolve_red(self, info, **kwargs):
dataset_Red = <custom library for querying API>.dataset(kwargs['redName'])
dataset_Red.get()
resp = dataset_Red.properties
return json2obj(json.dumps(resp))
I found this solution in another stackoverflow post but now I can't find the original. If anyone else finds it, please add a link in the comments and I'll add it to this answer.
I am using social-auth-app-django for GoogleOauth2 authentication. It works fine for all users but in case of django admin it gives me following error:
AuthStateMissing at /oauth/complete/google-oauth2/
Session value state missing.
I have tried all answers posted on stackoverflow but the error still persists. This is the result it returns.
The state value seems to be present there but either it gets null or overridden somehow.
This is my GoogleOAuth2 class, created by overriding social-auth-app-django's GoogleOAuth2 class. Though there is not much difference except for pipeline from base class. It works fine for non-admin user login.
class GoogleOAuth2(GoogleOAuth2):
"""Google OAuth2 authentication backend"""
name = 'google-oauth2'
REDIRECT_STATE = False
AUTHORIZATION_URL = 'https://accounts.google.com/o/oauth2/auth'
ACCESS_TOKEN_URL = 'https://accounts.google.com/o/oauth2/token'
ACCESS_TOKEN_METHOD = 'POST'
REVOKE_TOKEN_URL = 'https://accounts.google.com/o/oauth2/revoke'
REVOKE_TOKEN_METHOD = 'GET'
# The order of the default scope is important
DEFAULT_SCOPE = ['openid', 'email', 'profile']
EXTRA_DATA = [
('refresh_token', 'refresh_token', True),
('expires_in', 'expires'),
('token_type', 'token_type', True)
]
def pipeline(self, pipeline, pipeline_index=0, *args, **kwargs):
out = self.run_pipeline(pipeline, pipeline_index, *args, **kwargs)
user_ip = get_request_ip_address(self.strategy.request)
if not isinstance(out, dict):
return out
user = out.get('user')
if user:
user.social_user = out.get('social')
user.is_new = out.get('is_new')
if user.is_new:
logger.info(f'Register attempt', extra={"email": user.email, "remote_ip": user_ip, "status": "success", "user_id": user.pk, "oauth_backend": "google"})
else:
logger.info(f'Login attempt', extra={"email": user.email, "remote_ip": user_ip, "status": "success", "user_id": user.pk, "oauth_backend": "google"})
return user
I have tried following solutions, setting these values in settings.py file:
SOCIAL_AUTH_REDIRECT_IS_HTTPS = True
SESSION_COOKIE_SAMESITE = None
SESSION_COOKIE_HTTPONLY = False
this is the call back URL which am getting from the payment gateway I am integrating with
http://127.0.0.1:8000/checkout/mfsuccess/?paymentId=060630091021527961&Id=060630091021527961
I want to extract the paymentId int from this URL so I can use it in my function
this is the URL line am using
path('checkout/mfsuccess/?<str:paymentId>', gateway_Success, name='mf_success'),
and this is my function
def gateway_Success(request, id):
payment_id = request.GET.get('id')
print(payment_id)
context = {
"payment_id": payment_id
}
return render(request, "carts/checkout-mf-success.html")
How I do that since the way am doing not working I am getting the following error
Not Found: /checkout/mfsuccess/
[20/Nov/2020 03:38:59] "GET /checkout/mfsuccess/?paymentId=060630091121528060&Id=060630091121528060 HTTP/1.1" 404 4378
You don't need to adjust your path() function to catch the URL query parameters
path('checkout/mfsuccess/', gateway_Success, name='mf_success'),
also, change your view as,
def gateway_Success(request):
id_ = request.GET.get('Id') # retrieving the `Id`
payment_id = request.GET.get('paymentId') # retrieving the `paymentId`
context = {
"payment_id": payment_id,
"id_": id_
}
return render(request, "carts/checkout-mf-success.html", context=context)
This is enough to catch the redirection.
I am trying to handle the signal valid_ipn_received from the package django-paypal (docs: https://django-paypal.readthedocs.io/en/stable/standard/ipn.html)
engine/signals.py
from paypal.standard.models import ST_PP_COMPLETED
from paypal.standard.ipn.signals import valid_ipn_received
from engine.models import DatasetRequest
from django.views.decorators.csrf import csrf_exempt
def show_me_the_money(sender, **kwargs):
print('test')
ipn_obj = sender
if ipn_obj.payment_status == ST_PP_COMPLETED:
# WARNING !
# Check that the receiver email is the same we previously
# set on the `business` field. (The user could tamper with
# that fields on the payment form before it goes to PayPal)
if ipn_obj.receiver_email != "paypalemail#gmail.com":
# Not a valid payment
return
# ALSO: for the same reason, you need to check the amount
# received, `custom` etc. are all what you expect or what
# is allowed.
if ipn_obj.mc_gross == ipn_obj.amount and ipn_obj.mc_currency == 'USD':
pk = ipn_obj.invoice
dsr = DatasetRequest.objects.get(pk=pk)
dsr.is_paid = True
dsr.save()
else:
pass
valid_ipn_received.connect(show_me_the_money)
engine/apps.py
from django.apps import AppConfig
class EngineConfig(AppConfig):
name = 'engine'
def ready(self):
import engine.signals
engine/views.py
def pay_for_dataset_request(request, dsr_pk):
# dsr_pk = dataset request primary key
dsr = DatasetRequest.objects.get(pk=dsr_pk)
paypal_dict = {
"business": "paypalemail#gmail.com",
"amount": dsr.reward,
"item_name": dsr.title,
"invoice": dsr.pk,
"notify_url": request.build_absolute_uri(reverse('paypal-ipn')),
"return": request.build_absolute_uri(reverse('datasetrequest_detail', kwargs={'pk': dsr.pk, 'slug': dsr.slug})),
"cancel_return": request.build_absolute_uri(reverse('datasetrequest_detail', kwargs={'pk': dsr.pk, 'slug': dsr.slug})),
}
# Create the instance.
form = PayPalPaymentsForm(initial=paypal_dict)
context = {"form": form}
return render(request, "payment.html", context)
"valid_ipn_received" is not firing when i make payments on 127.0.0.1:8000, nor ngrok, nor my production server. What is wrong with my code? I am new to signals.
Have you made sure that your apps.py file is actually being run? You can test by inserting breakpoint() or just a print() call.
You may need to add a line to your module's __init__.py like so:
default_app_config = 'app.engine.apps.EngineConfig'
or similar to make sure that it's loaded. See the documentation on AppConfigs.
I'm working on my Django API Rest but it's pretty new for me and I have one more question about my code.
I'm reading all the time Django Rest Doc
------------------------------------------------------------------------------------------------------------------------------------
FIRST PART - My code :
------------------------------------------------------------------------------------------------------------------------------------
I have a serializer.py file which describe my ModelSerializer like this :
class IndividuCreateSerializer(serializers.ModelSerializer) :
class Meta :
model = Individu
fields = [
'Etat',
'Civilite',
'Nom',
'Prenom',
'Sexe',
'Statut',
'DateNaissance',
'VilleNaissance',
'PaysNaissance',
'Nationalite1',
'Nationalite2',
'Profession',
'Adresse',
'Ville',
'Zip',
'Pays',
'Mail',
'Telephone',
'Image',
'CarteIdentite',
]
def create(self, validated_data):
print('toto')
obj = Individu.objects.create(**validated_data)
Identity_Individu_Resume(self.context.get('request'), obj.id)
return obj
This create function calls another function which is not in my API module, but in my main module : Identity_Individu_Resume
As you can see here, this function takes the last created object and applies multiple processes :
#login_required
def Identity_Individu_Resume(request, id) :
personne = get_object_or_404(Individu, pk=id)
NIU = lib.Individu_Recherche.NIUGeneratorIndividu(personne)
personne.NumeroIdentification = NIU
if personne.Image != " " :
NewImageName = 'pictures/' + personne.Nom +'_'+ personne.Prenom +'_'+ NIU + '.jpg'
FilePath = settings.MEDIA_ROOT
FilePathOLD = settings.MEDIA_ROOT + str(personne.Image)
FilePathNEW = settings.MEDIA_ROOT + NewImageName
file = os.path.exists(FilePath)
if file :
os.rename(FilePathOLD, FilePathNEW)
personne.Image = NewImageName
if personne.CarteIdentite != " " :
NewCarteName = 'Carte_Identite/' + 'Carte_Identite_' + personne.Nom +'_'+ personne.Prenom +'_'+ NIU + '.jpg'
FilePath = settings.MEDIA_ROOT
FilePathOLD = settings.MEDIA_ROOT + str(personne.CarteIdentite)
FilePathNEW = settings.MEDIA_ROOT + NewCarteName
file = os.path.exists(FilePath)
if file :
os.rename(FilePathOLD, FilePathNEW)
personne.CarteIdentite = NewCarteName
else :
pass
personne.save()
context = {
"personne" : personne,
"NIU" : NIU,
}
return render(request, 'Identity_Individu_Resume.html', context)
Then, I have in my API module a views.py file with a specific class named IndividuCreateAPIView which is very simple and call my serializer class describes above :
class IndividuCreateAPIView(CreateAPIView) :
queryset = Individu.objects.all()
serializer_class = IndividuCreateSerializer
Then I have my urls.py file :
urlpatterns = [
url(r'^$', IndividuListAPIView.as_view() , name="IndividuList"),
url(r'^docs/', schema_view),
url(r'^create/$', IndividuCreateAPIView.as_view() , name="Create"),
]
------------------------------------------------------------------------------------------------------------------------------------
SECOND PART - Using API REST Interface :
------------------------------------------------------------------------------------------------------------------------------------
In this part, I'm using my API Rest interface with http://localhost:8000/Api/Identification/
When I create an object, the create function in my serializers.py file works well and I get a well-created object in my database :
So there is none issue !
------------------------------------------------------------------------------------------------------------------------------------
THIRD PART - Using API REST with pythonic file :
------------------------------------------------------------------------------------------------------------------------------------
In this case, I'm getting an issue.
I have a file API_create.py which is executed from my terminal and should simulate an external application which try to connect to my API and create an object.
import requests
url = 'http://localhost:8000/Api/Identification/create/'
filename1 = '/Users/valentin/Desktop/Django/DatasystemsCORE/Media/pictures/photo.jpg'
filename2 = '/Users/valentin/Desktop/Django/DatasystemsCORE/Media/Carte_Identite/carte_ID.gif'
files = {'Image' : open(filename1,'rb'), 'CarteIdentite': open(filename2,'rb')}
data = {
"Etat": "Vivant",
"Civilite": "Monsieur",
"Nom": "creation",
"Prenom": "via-api",
"Sexe": "Masculin",
"Statut": "Célibataire",
"DateNaissance": "1991-11-23",
"VilleNaissance": "STRASBOURG",
"PaysNaissance": "FR",
"Nationalite1": "FRANCAISE",
"Nationalite2": "",
"Profession": "JJJ",
"Adresse": "12, rue des fleurs",
"Ville": "STRASBOURG",
"Zip": 67000,
"Pays": "FR",
"Mail": "",
"Telephone": ""
}
response = requests.post(url, files=files, data=data)
print(response.text)
But, when I execute this script, my object is well-created BUT the function create in my serializers.py is not called !
In this case, I'm getting that :
My question is : Why my create function doesn't work ? I don't understand because my url is correct and should call this function in order to replace my NIU (NULL) by the generated NIU and change pictures names too ...
Could you help me to find why it doesn't work ?
Probably this is because of login_required decorator in Identity_Individu_Resume view. You can try to remove it or you need to provide auth token with request: response = requests.post(url, files=files, data=data, headers={'Authorization': 'Token <MY_TOKEN>'}).
UPD
Actually I suppose it would be better to move common part of api and non-api views to third function and call it separately from both views. In this case you probaly would like to add login_required to IndividuCreateAPIView view also. In this case you need to add IsAuthenticated permission like this:
from rest_framework.permissions import IsAuthenticated
class IndividuCreateAPIView(CreateAPIView) :
queryset = Individu.objects.all()
serializer_class = IndividuCreateSerializer
permission_classes = (IsAuthenticated,)