I want to use mongoengine with Flask.
from mongoengine import *
connect('db_test', port=27017)
class Client(Document):
name = StringField(required=True, max_length=200,unique=True)
tel_nummer = StringField(required=True)
age = StringField(required=True, max_length=50)
#app.route('/api/clients', methods=['GET'])
def get_clients():
all_clients=Client.objects.all()
return jsonify(result=all_clients.to_json())
The output is:
result "[{\"_id\": {\"$oid\": \"5cdb1ed17218d7a0b5218832\"}, \"name\": \"Tom\", \"telnummer\": \"34634534\", \"age\": \"34534534\",}]"
Thanks.
Client.objects.all().to_json() is already returning a json, you shouldn't jsonify it again
Related
My models have multiple attributes, all of which are being serialized.
According to the user's request, I want my API to return only limited information
This is my model's code
from django.db import models
class User(models.Model):
name = models.CharField(max_length=50)
age = models.PositiveSmallIntegerField()
description = models.CharField(max_length=200)
def __str__(self) -> str:
return self.name
This is my Serializer's code
from rest_framework import serializers
from .models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = "__all__"
This is my View's code
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .models import User
from .serializers import UserSerializer
from rest_framework import serializers
from rest_framework import status
from django.http import JsonResponse
#api_view(['GET'])
def viewUsers(request):
users = User.objects.all()
serializer = UserSerializer(users, many=True)
return JsonResponse({'id': serializer.data['id'], 'name': serializer.data['name']})
The code below works if I want to return a single attribute/field
return JsonResponse(serializer.data['id'])
but when I try to fetch more than one attribute by the dictionary method, it throws this error
list indices must be integers or slices, not str
many=True passed to UserSerializer indicates that serializer.data will contain a list of dicts where each particular dict will be a result of serializing User instance to expected dict
Taking into account serializer.data is a list then if you try to execute serializer.data['id'] it raises
list indices must be integers or slices, not str
You may want to update the return statement to
return JsonResponse([{'id': item['id'] for item in serializer.data}])
or specify that you expect only id field to be returned by the serializer
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ("id",)
and then in the view
return JsonResponse(serializer.data)
I'm new to flask and in order to refactor an existing route method on a Flask API, i'm looking for the best practice to reduce it and call method inside the route method.
Acutally the route is designed like that :
#qman.route('/add_report/', methods=['POST'])
def create_report():
"""
Check if data send throught http POST request, is correct based on the report
schema and not already recorded in the table report of the DB.
:param: data from POST request
:return: Ok, valide and imported -> 201, Correct but AlreadyKnown -> 208,
InvalideScheme -> 422
"""
jsonData = request.get_json()
reportSchema = ReportSchema()
try:
data = reportSchema.load(jsonData)
except ValidationError as validation_err:
return(validation_err.messages), 422
nameReportCheck = data["report_name"]
report = Report.query.filter_by(report_name=nameReportCheck).first()
if report is None:
# Create new report
report = Report(
report_name=nameReportCheck,
hostname=data["hostname"],
status=data["status"],
date=data["date"],
nb_analysis_erreur=data["nb_analysis_erreur"]
)
db.session.add(report)
db.session.commit()
NewResult = reportSchema.dump(Report.query.get(report.reportID))
return{"message" : "Created new report" , "report" : NewResult}, 201
else :
reportAlreadyKnown = reportSchema.dump(Report.query.get(report.reportID))
return{"message" : "This report is already in the DB", "report" : reportAlreadyKnown}, 208
In the facts i would like to call a function named valid_schema(_schema, _jsondata) to check if the data send throught POST request match with my schema of model Report().
This function return a Response() object with serialized data and a 200 code if it's serialization is possible or an error that i cath inside try/except with 400 error code.
def valid_schema(_schema, _jsondata):
schema = _schema()
try:
data = schema.load(_jsondata)
except ValidationError as validation_err:
response = Response(validation_err.messages, 422)
return response
response = Response(data, 200, mimetype="application/json")
return response
Then the route method call an other function named create_report(report_data) if valid_schema(_schema, _jsondata) return report_data and 200 code in response object.
With his args, this method check if the records is not already in the DB and if is not, he create a Report() object from report_data arg and insert this one as a new record into the DB.
In fact I guess I can easily call this method inside the route function but it seem weird and there is probably an other way that I can't find, maybe decorator ?
One possibility for refactoring is the use of webargs, Flask-Marshmallow and marshmallow-sqlalchemy.
With Flask-Marshmallow you can check the input by specifying fields and validators. Webargs offers you the option of validating the defined scheme in a decorator and passing it on to the route as an argument. Using marshmallow-sqlalchemy in combination, this is immediately converted into a database model.
The following example is based on your information and gives you a brief overview of the usage. By defining your own error handler, the error messages can also be sent as JSON. Use in blueprints, views or the like is possible.
from flask import Flask
from flask import jsonify
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
from marshmallow.validate import Length, OneOf
from webargs.flaskparser import use_args
app = Flask(__name__)
db = SQLAlchemy(app)
ma = Marshmallow(app)
class Report(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, unique=True)
hostname = db.Column(db.String)
status = db.Column(db.String)
date = db.Column(db.DateTime)
nb_analysis_error = db.Column(db.String)
class ReportSchema(ma.SQLAlchemyAutoSchema):
class Meta:
model = Report
load_instance = True
sqla_session = db.session
name = ma.Str(required=True, validate=Length(min=3))
hostname = ma.Str(required=True)
date = ma.DateTime(required=True)
status = ma.Str(required=True, validate=OneOf(['online', 'offline']))
nb_analysis_error = ma.Str(missing='Unknown Error')
#app.route('/add_report', methods=['POST'])
#use_args(ReportSchema(), location='json')
def add_report(report):
report_schema = ReportSchema()
_report = Report.query.filter_by(name=report.name).first()
if _report:
report_data = report_schema.dump(_report)
return jsonify(message='Already Reported', report=report_data), 208
else:
db.session.add(report)
db.session.commit()
report_data = report_schema.dump(report)
return jsonify(message='Created', report=report_data), 201
with app.app_context():
db.drop_all()
db.create_all()
From the docs under 'Deserializing to Objects':
from marshmallow import Schema, fields, post_load
class UserSchema(Schema):
name = fields.Str()
email = fields.Email()
created_at = fields.DateTime()
#post_load
def make_user(self, data, **kwargs):
return User(**data)
But I when I run this code, I get:
AttributeError: 'User' object has no attribute 'data'
What am I missing?
Try returning the data dictionary instead of an instance of your model.
I stumbled upon issue this while using marshmallow-sqlalchemy and flask-marshmallow
from marshmallow import Schema, fields, post_load
class UserSchema(Schema):
name = fields.Str()
email = fields.Email()
created_at = fields.DateTime()
#post_load
def make_user(self, data, **kwargs):
data['extra_attribute'] = 'extra value'
return data
Using automap_base from sqlalchemy.ext.automap to map my tables.
Not able to shema.dumps(result);
getting
raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type Decimal is not JSON serializable
Tried using JSON custom decoders, but no use.
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm import Session
from sqlalchemy.ext.automap import automap_base
from flask_marshmallow import Marshmallow
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///database.db'
db = SQLAlchemy(app)
ma = Marshmallow(app)
engine = db.engine
session = Session(engine)
Base = automap_base()
Base.prepare(engine, reflect=True)
MyTable = Base.classes.my_table
class MyTableSchema(ma.ModelSchema):
class Meta:
model = MyTable
#app.route("/")
def api():
all_rows = session.query(MyTable).all()
schema = MyTableSchema(many=True)
response = schema.dumps(all_rows)
return response
if __name__ == '__main__':
app.run(debug=True)
An easy workaround to transform an SQLAlchemy result object in JSON is using simplejson.
You just need to import it (import simplejson) and it works.
Using your example:
import simplejson
...
#app.route("/")
def api():
all_rows = session.query(MyTable).all()
response = simplejson.dumps(all_rows)
Add .data to response like below
def api():
all_rows = session.query(MyTable).all()
schema = MyTableSchema(many=True)
response = schema.dumps(all_rows)
return response.data
I am having trouble creating a customizable swagger schema in the Django Rest Framework. I have read pages of documentation, but have not found a clear cut example on how to generate swagger annotations in python.
I am aware that swagger/schema documentation is readily generated when using ViewSets in Django. However, I am solely using APIViews and want to write a customized schema. I have tried creating a CoreAPI schema but am unaware of how to implement it. I am enclosing some of my sample code and some screenshots as well. The screen shots go from what I have to what I want.
Sample code:
urls.py
from django.conf.urls import url, include
from rest_framework.urlpatterns import format_suffix_patterns
from Views import SampleView as sv
from rest_framework_swagger.views import get_swagger_view
from rest_framework.documentation import include_docs_urls
from rest_framework.renderers import CoreJSONRenderer
from rest_framework.schemas import get_schema_view
schema_view enter code here= get_swagger_view(
title='Sample API')
urlpatterns = [
url(r'^sample/$', pv.SampleList.as_view()),
url(r'^sample/(?P<id>[a-f\d]{24})/$', sv.SampleDetail.as_view()),
url('^schema/$', schema_view),
]
urlpatterns = format_suffix_patterns(urlpatterns)
views.py
from rest_framework.views import APIView
from Manager.SampleManager import SampleManager as sm
_sampleManager = sm()
class SampleList(APIView):
"""
get:
Return a list of all the existing samples.
post:
Create a new sample.
"""
def get(self, request, format=None):
return _sampleManager.getAll()
def post(self, request, format=None):
return _sampleManager.create( request)
class SampleDetail(APIView):
"""
get:
Get a sample.
put:
Update a sample.
delete:
Delete a sample.
"""
def get(self, request, id, format =None):
return _sampleManager.getById( id)
def put(self, request, id, format =None):
return _sampleManager.update( request, id)
def delete(self, request, id, format =None):
return _sampleManager.deleteById( id)
Serializers.py
from rest_framework_mongoengine.serializers import DocumentSerializer
from .modles import Sample, SampleInner
from Serializers.SampleInnerSerializer import SampleInnerSerializer
class SampleSerializer(DocumentSerializer):
other = SampleInnerSerializer(many=True)
class Meta:
model = Sample
fields = '__all__'
def create(self, validated_data):
samples = validated_data.pop('other')
created_instance = super(SampleSerializer, self).create(validated_data)
for sample_data in samples:
created_instance.other.append(SampleInner(**sample_data))
created_instance.save()
return created_instance
def update(self, instance, validated_data):
samples = validated_data.pop('other')
updated_instance = super(SampleSerializer, self).update(instance, validated_data)
for sample_data in samples:
updated_instance.other.append(SampleInner(**sample_data))
updated_instance.save()
return updated_instance
Schema.py
import coreapi
from rest_framework.decorators import api_view, renderer_classes
from rest_framework import renderers, response
schema = coreapi.Document(
title='Sample API',
content={
'sample': coreapi.Link(
url='/sample/',
action='post',
fields=[
coreapi.Field(
name='from',
required=True,
location='query',
description='City name or airport code.'
),
coreapi.Field(
name='to',
required=True,
location='query',
description='City name or airport code.'
),
coreapi.Field(
name='date',
required=True,
location='query',
description='Flight date in "YYYY-MM-DD" format.'
)
],
description='Create partner'
)
}
)
#api_view()
#renderer_classes([renderers.CoreJSONRenderer])
def schema_view(request):
return response.Response(schema)
There is two solution for you in this senario, One "go with the GenricApiView" Two "Create Custom row Schema"
let's go with
-- >solution one.
urls.py
schema_view = get_swagger_view(title='Test All API')
urlpatterns = [
path('swagger2/', schema_view),
]
view.py
class LoginAPIView(generics.GenericAPIView):
serializer_class = LoginSerializer
permission_classes = [permissions.AllowAny]
def post(self, request):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
return Response(serializer.data, status=status.HTTP_200_OK)
Result
--> Solution two:
urls.py
configuration is same as before
views.py
class BlacklistTokenAdding(APIView):
permission_classes = [permissions.AllowAny]
schema = ManualSchema(fields=[
coreapi.Field(
"first_field",
required=True,
location="body",
schema=coreschema.String()
),
coreapi.Field(
"second_field",
required=True,
location="body",
schema=coreschema.String()
),
])
def post(self, request, format='json'):
try:
refresh_token = request.data["refresh_token"]
token = RefreshToken(refresh_token)
token.blacklist()
return Response(status=status.HTTP_200_OK)
except Exception as e:
return Response(status=status.HTTP_400_BAD_REQUEST)
//Note the first_field & second_field is to demonstration you can add here as much field as you want.
Result