How to use different Pydantic Models in a view - django

Im currently trying to understand the Pydantic FastAPI setup within my Django app and have the following issue:
When I create a new Order object, I want to return the newly created object using a different Pydantic model as I use for the creation (as after the creation I now have the ID of the order that I can also return).
# routes
# Create a new Order
#router.post("/create/", response_model=schemas.OrderResponse)
def create_orders(order: schemas.OrderBase):
return views.create_order(order=order)
# views.py
from uuid import UUID
from . import models, schemas
# Create a new order
def create_order(order: schemas.OrderBase):
new_order = models.Order.objects.get_or_create(order)
return new_order
# schemas.py
# pydantic models
from uuid import UUID
from pydantic import BaseModel
# Base Class for Model "Order"
class OrderBase(BaseModel):
product: str
period: str
power: int
side: str
class Config:
orm_mode = True
class OrderResponse(OrderBase):
id: UUID
When I now send an API request with this body:
{
"product": "Base",
"period": "Hour",
"power": 10,
"side": "Buy"
}
I get this error -- how to set it up such that when creating the instance it doesnt validate for the UUID and after creation and when returning the instance it includes the UUID in the response?
pydantic.error_wrappers.ValidationError: 5 validation errors for OrderResponse
response -> product
field required (type=value_error.missing)
response -> period
field required (type=value_error.missing)
response -> power
field required (type=value_error.missing)
response -> side
field required (type=value_error.missing)
response -> id
field required (type=value_error.missing)

It seems like converting your django model instance to pydantic automatically is not supported on fastAPI.
So I add a code that convert your model to dict, and that dict will initiate OrderResponse instance.
Try this,
from django.forms.models import model_to_dict
...
#router.post("/create/", response_model=schemas.OrderResponse)
def create_orders(order: schemas.OrderBase):
return model_to_dict(views.create_order(order=order))

Related

GraphQL - JWT -change payload of verifyToken mutation - django

I wanna change the payload of verifyToken mutation. So instead of returning email I wanna return id or the whole object
schema.py
import graphql_jwt
class Query(AccountsQuery, ObjectType):
# This class will inherit from multiple Queries
# as we begin to add more apps to our project
pass
class Mutation( AccountsMutation, ObjectType):
# This class will inherit from multiple Mutations
# as we begin to add more apps to our project
token_auth = graphql_jwt.ObtainJSONWebToken.Field()
verify_token = graphql_jwt.Verify.Field()
refresh_token = graphql_jwt.Refresh.Field()
pass
schema = Schema(query=Query, mutation=Mutation)
I've seen this: https://django-graphql-jwt.domake.io/en/latest/_modules/graphql_jwt/utils.html#get_user_by_natural_key
But I dont know how I can implement that - if it's a correct approach
Any ideas how I could do that?
To modify the payload of your token, you have to create a custom jwt_payload function and you must add the path of the function to the JWT_PAYLOAD_HANDLER in your settings.GRAPHQL_JWT
projectname.settings.py
GRAPHQL_JWT = {
...
'JWT_PAYLOAD_HANDLER': 'polls.schema.jwt_payload',
...
}
polls.schema.py
import graphql_jwt
from datetime import datetime
from projectname.settings import GRAPHQL_JWT
def jwt_payload(user, context=None):
username = user.get_username()
payload = {
user.USERNAME_FIELD: username,
'user_id': user.id,
'email': user.email,
'phone': user.phone,
'exp': datetime.utcnow() + GRAPHQL_JWT['JWT_EXPIRATION_DELTA'],
...
}
original jwt payload function
Note that in your case the user.USERNAME_FIELD is your email field, it's the default field in your payload (it's also the field you need to get the token), you can remove it or you can edit it by following the django documentation, if you declare a custom USERNAME_FIELD you can choose from all columns in the 'auth_user' table, but you have to declare it bevor you run python manage.py migrate for the first time

Django filter geometry given a coordinate

I want to get a row from a postgis table given a coordinate/point. With raw sql I do it with:
SELECT * FROM parcelas
WHERE fk_area=152
AND ST_contains(geometry,ST_SetSRID(ST_Point(342884.86705619487, 6539464.45201204),32721));
The query before returns one row.
When I try to do this on django it doesn't return me any row:
from django.contrib.gis.geos import GEOSGeometry
class TestView(APIView):
def get(self, request, format=None):
pnt = GEOSGeometry('POINT(342884.86705619487 6539464.45201204)', srid=32721)
parcelas = Parcelas.objects.filter(fk_area=152,geometry__contains=pnt)
#Also tried this
#parcelas = Parcelas.objects.filter(fk_area=pk,geometry__contains='SRID=32721;POINT(342884.86705619487 6539464.45201204)')
serializer = ParcelasSerializer(parcelas, many=True)
return Response(serializer.data)
Even with django raw query it fails although in this case it returns me an internal server error (argument 3: class 'TypeError': wrong type):
class TestView(APIView):
def get(self, request, format=None):
parcelas = Parcelas.objects.raw('SELECT * FROM parcelas WHERE fk_area=152 AND ST_contains(geometry,ST_SetSRID(ST_Point(342884.86705619487, 6539464.45201204),32721))')
for p in parcelas:
#Internal server error
print(p.id)
return Response('Test')
My model parcelas look like this:
from django.contrib.gis.db import models
class Parcelas(models.Model):
id = models.BigAutoField(primary_key=True)
fk_area = models.ForeignKey(Areas, models.DO_NOTHING, db_column='fk_area')
geometry = models.GeometryField()
class Meta:
managed = False
db_table = 'parcelas'
I don't know what I'm doing wrongly if someone has any idea.
EDIT:
If I print the raw query that django made:
SELECT "parcelas"."id", "parcelas"."fk_area", "parcelas"."geometry"::bytea FROM "parcelas" WHERE ("parcelas"."fk_area" = 152 AND ST_Contains("parcelas"."geometry", ST_Transform(ST_GeomFromEWKB('\001\001\000\000 \321\177\000\000C\224\335w\223\355\024A\350\303\355\0342\362XA'::bytea), 4326)))
Seems like django is not converting it to the correct srid (32721) but I don't know why
EDIT 2:
If in my model I specify the SRID it works correctly:
class Parcelas(models.Model):
geometry = models.GeometryField(srid=32721)
The problem is that the SRID can be variable depending on the query the rows have one SRID or another so I don't want to set it to always being one.
Test database is created separately and does not contain the same data as the main application database. Try using pdb and listing all entries inside the parcelas table. Unless TestView means just a mock view for the time being.
Using pdb:
import pdb, pdb.set_trace()
Parcelas.objects.all()
In case the records geometry needs to be compared to a geojson like object one approach is to convert the object to GEOSGeometry and then find the record using .get(), .filter() etc.
For example, in case of an API JSON request payload that contains somewhere in the payload the following field:
"geometry": {
"type": "Polygon",
"coordinates": [
[
[21.870314, 39.390873],
[21.871913, 39.39319],
[21.874029, 39.392443],
[21.873401, 39.391328],
[21.873369, 39.391272],
[21.873314, 39.391171],
[21.872715, 39.390024],
[21.870314, 39.390873]
]
]
}
One can use the following code:
import json
from django.contrib.gis.geos import GEOSGeometry
# Assuming the python dictionary containing the geometry field is geometry_dict
payload_geometry = GEOSGeometry(json.dumps(geometry_dict))
parcel = Parcel.objects.get(geometry=payload_geometry)

With drf-yasg, how can I support multiple serializers in the Response?

With a response from my drf just containing the data given by a single serializer, we can implement it as:
#swagger_auto_schema(
operation_id='ID example',
operation_description="Description example.",
responses={status.HTTP_200_OK: Serializer4ModelA(many=True)},
)
Which works fantastic, but with some requests constructing a dictionary, where two or three of the keys correspond to different serializers, e.g.
response = {
"a": serializer_data_for_model_a,
"b": serializer_data_for_model_b,
"c": serializer_data_for_model_c
}
How can we describe that in the auto schema? I've tried a few different approaches, mostly similar to the following:
#swagger_auto_schema(
operation_id='ID example',
operation_description="Description example.",
responses={status.HTTP_200_OK: openapi.Response(
description='response description',
schema=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'a': Serializer4ModelA(many=True),
'b': Serializer4ModelB(many=True),
'c': Serializer4ModelC(many=True)
})
)}
)
But always fails when loading the documentation, with flex saying:
"/usr/local/lib/python3.6/site-packages/flex/utils.py", line 125, in
get_type_for_value raise ValueError("Unable to identify type of
{0}".format(repr(value)))
ValueError: Unable to identify type of
Serializer4ModelA(many=True):
I've read the documentation over and over again, and scoured over github for an example, but I couldn't find an example or anyone doing this. So my question is how to successfully manually define a schema for a response that contains different serializers for different keys in the returned response?
What I usually do is to create another serializer (just so that drf-yasg can generate the docs).
For example if I have an endpoint that returns:
{
"results": [..list of serialized results with serializer X...]
}
I create a second serializer:
class Y(serializers.Serializer):
results = X(many=True)
and use Y serializer in the swagger_auto_schema decorator.
I ended up being able to do it, although probably not the most elegant solution but it does work.
My drf has a custom app-label format, so all my apps are in a folder, and let's call this folder apps.
In my question, for a serializer, we can replace Serializer4ModelA in the properties section of the openapi.Schema with a custom function, lets say get_serializer(Serializer4ModelA()).
So my idea was to basically construct the schema myself by getting the information automatically and automatically constructing the properties dictionary. It's very hacky, but useful for me because in my documentation I also want to pass in the serializers for Dynamodb, so I made a very similar function for Dynamodb serializers.
I only just made it, and it works, but obviously needs more attention to cover all fields in the field mapping, better dealing with SerializerMethodFields.
But none the less, it is a solution that works but is not generic, tweaks and stuff will have to be made depending on your particular project.
I implemented the function roughly as follows:
from drf_yasg import openapi
from drf_yasg.inspectors import SwaggerAutoSchema
from drf_yasg.utils import swagger_auto_schema
from drf_yasg.inspectors import FieldInspector
from drf_yasg.utils import swagger_serializer_method
import rest_framework
rest_framework_openapi_field_mapping = {
"ListField": openapi.TYPE_ARRAY,
"CharField": openapi.TYPE_STRING,
"BooleanField": openapi.TYPE_BOOLEAN,
"FloatField": openapi.TYPE_NUMBER,
"DateTimeField": openapi.TYPE_STRING,
"IntegerField": openapi.TYPE_INTEGER,
"SerializerMethodField": openapi.TYPE_STRING
}
def parse_rest_framework_field(field):
rest_framework_field_type = field.split("(")[0]
openapi_field_type =
rest_framework_openapi_field_mapping[rest_framework_field_type]
if "help_text=" in field:
field_description = field.split("help_text='")[-1].split("'")[0]
else:
field_description = None
return openapi.Schema(type=openapi_field_type, description=field_description)
def parse_serializer(serializer):
properties = {}
for k,v in serializer.get_fields().items():
if v.__module__ == "rest_framework.fields":
properties[k] = parse_rest_framework_field(str(v))
elif v.__module__.startswith("apps."):
serializer = str(v).strip().split("(")[0]
exec(f"from {v.__module__} import {serializer}")
eval_serializer = eval(f"{serializer}()")
properties[k] = openapi.Schema(type=openapi.TYPE_OBJECT, properties=parse_serializer(eval_serializer))
else:
pass
return properties
def get_serializer(serializer, description):
""" Needs to return openapi.Schema() """
properties = parse_serializer(serializer)
return_openapi_schema = openapi.Schema( type=openapi.TYPE_OBJECT, properties=properties, description=description)
return return_openapi_schema
I faced this problem and was looking if there is another way than my initial solution (same as how #Hernan explained it) but found none. The code of drf_yasg.openapi.Schema (drf_yasg==1.20.0) showed that it doesn't accept any serializer object. So as already said by #Hernan, the way around this is to have an additional serializer and define there the nested child serializers. Then, pass it to either the swagger_auto_schema.responses directly or through an openapi.Response.schema (as below):
from django.urls import path
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework import serializers, status, views
class Serializer4ModelA(serializers.Serializer):
dog = serializers.CharField(label="My dog is a good boy")
class Serializer4ModelB(serializers.Serializer):
perro = serializers.CharField(label="Mi perro es un buen chico")
hund = serializers.CharField(label="Mein Hund ist ein guter Junge")
aso = serializers.CharField(label="Ang aso ko ay mabait na bata")
class Serializer4ModelC(serializers.Serializer):
eey = serializers.CharField(label="Eygaygu waa wiil fiican")
class SampleResponseSerializer(serializers.Serializer):
a = Serializer4ModelA(many=True)
b = Serializer4ModelB(many=True)
c = Serializer4ModelC(many=True)
class SampleView(views.APIView):
#swagger_auto_schema(
responses={
status.HTTP_200_OK: openapi.Response(
description="response description",
schema=SampleResponseSerializer,
)
}
)
def get(self, request):
pass
urlpatterns = [
path("sample/", SampleView.as_view()),
]
Output:

Graphene-Django: In schema combine Query-objects (only takes first argument)

I am trying to combine multiple Query schemas located in different apps in Django 2.1. Using graphene-django 2.2 (have tried 2.1 with same problem). Python 3.7.
The Query class only registers the first variable. As an example shop.schema.Query.
import graphene
import graphql_jwt
from django.conf import settings
import about.schema
import shop.schema
import landingpage.schema
class Query(about.schema.Query, shop.schema.Query, landingpage.schema.Query, graphene.ObjectType):
pass
class Mutation(shop.schema.Mutation, graphene.ObjectType):
token_auth = graphql_jwt.ObtainJSONWebToken.Field()
verify_token = graphql_jwt.Verify.Field()
refresh_token = graphql_jwt.Refresh.Field()
schema = graphene.Schema(query=Query, mutation=Mutation)
Why is it like this? Have something changed with classes in python 3.7? The graphene tutorial says this will inherit for multiple...
class Query(cookbook.ingredients.schema.Query, graphene.ObjectType):
# This class will inherit from multiple Queries
# as we begin to add more apps to our project
pass
schema = graphene.Schema(query=Query)
I am exporting my schema to schema.json for using it with react relay. I do find my object "collection" Query schema from landingpage(the 3. variable). Relay returns:
ERROR: GraphQLParser: Unknown field collection on type Viewer.
Source: document AppQuery file: containers/App/index.js.
Is it a problem with Relay reading my schema.json?
I managed to solve it shortly after writing this. My problem was that I had a Viewer object in every app. Because I find it useful to have a viewer-graphql-root, like this:
graphql'
viewer {
collection {
somestuff
}
}
'
I moved the Viewer object up to the root schema.py like this:
class Viewer(about.schema.Query, landingpage.schema.Query, shop.schema.Query, graphene.ObjectType):
class Meta:
interfaces = [relay.Node, ]
class Query(graphene.ObjectType):
viewer = graphene.Field(Viewer)
def resolve_viewer(self, info, **kwargs):
return Viewer()
class Mutation(shop.schema.Mutation, graphene.ObjectType):
token_auth = graphql_jwt.ObtainJSONWebToken.Field()
verify_token = graphql_jwt.Verify.Field()
refresh_token = graphql_jwt.Refresh.Field()
schema = graphene.Schema(query=Query, mutation=Mutation)
In setting.py add a new file as schema.py
Combine your Queries and Mutations in schema.py as follows:
import graphene
import about.schema as about
import shop.schema as projects
import landingpage.schema as projects
then add:
class Query(about.schema.Query, shop.schema.Query, landingpage.schema.Query, graphene.ObjectType):
pass
class Mutation(about.schema.Mutation, shop.schema.Mutation, landingpage.schema.Mutation, graphene.ObjectType):
pass
schema = graphene.Schema(query=Query, mutation=Mutation)
Configure your combined schema in settings.py as follows:
GRAPHENE = {
"SCHEMA": "core.schema.schema",
}

Only lists and tuples may be used in a list field Validation Error

Hi I am implementing test cases for my models.
I am using Mongoengine0.9.0 + Django 1.8
My models.py
class Project(Document):
# commented waiting for org-group to get finalize
project_name = StringField()
org_group = ListField(ReferenceField(OrganizationGroup, required=False))
My Serializers.py
class ProjectSerializer(DocumentSerializer):
class Meta:
model = Project
depth = 1
test.py file
def setUp(self):
# Every test needs access to the request factory.
self.factory = RequestFactory()
self.user = User.objects.create_user(
username='jacob', email='jacob#jacob.com', password='top_secret')
def test_post_put_project(self):
"""
Ensure we can create new clients in mongo database.
"""
org_group = str((test_utility.create_organization_group(self)).id)
url = '/project-management/project/'
data = {
"project_name": "googer",
"org_group": [org_group],
}
##import pdb; pdb.set_trace()
factory = APIRequestFactory()
user = User.objects.get(username='jacob')
view = views.ProjectList.as_view()
# Make an authenticated request to the view...
request = factory.post(url, data=data,)
force_authenticate(request, user=user)
response = view(request)
self.assertEqual(response.status_code, 200)
When I am running test cases I am getting this error
(Only lists and tuples may be used in a list field: ['org_group'])
The complete Stack Trace is
ValidationError: Got a ValidationError when calling Project.objects.create().
This may be because request data satisfies serializer validations but not Mongoengine`s.
You may need to check consistency between Project and ProjectSerializer.
If that is not the case, please open a ticket regarding this issue on https://github.com/umutbozkurt/django-rest-framework-mongoengine/issues
Original exception was: ValidationError (Project:None) (Only lists and tuples may be used in a list field: ['org_group'])
Not getting why we cant pass object like this.
Same thing when I am posting as an request to same method It is working for me but test cases it is failing
The tests should be running using multipart/form-data, which means that they don't support lists or nested data.
You can override this with the format argument, which I'm guessing you probably want to set to json. Most likely your front-end is using JSON, or a parser which supports lists, which explains why you are not seeing this.