I have started to play around with the Django REST framework. So far I succeeded in creating a serializer for my object, creating the post view, post objects and return objects via Javascript's $.post(). So right now I have a proper conversion between my JSONs and Django model objects.
The problem is that I have an array of objects [A1, A2, ..., An]. Right now when I need to post such an array I do it object by object. Is there any possibility to post the whole array at once, and recover an array of objects inside my Django View? If so, what is the pattern to follow here? I guess I could define a new model which is an array of my current model, create a serializer for it, etc., but this does not seem too elegant.
Below are my view and serializer:
#serializers.py
class SearchRequestSerializer(serializers.ModelSerializer):
def create(self):
return SearchRequest(**self.validated_data)
class Meta:
model = SearchRequest
#views.py
#api_view(['POST'])
def post_calculation(request):
if request.method == 'POST':
#JSON to serializer object
serializer = SearchRequestSerializer(data=request.data, many=False)
if (serializer.is_valid() == False):
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
#create the Python object
search_request = serializer.create()
#-- I do some processing stuff with the search_request object here ---
#object back to JSON
serializer3 = SearchRequestSerializer(search_request, many=False)
return Response(serializer3.data)
There are two solutions to your problem:
The first solution is to override the .create() method of your view
By default, django rest framework assumes you are passing it a single object. To account for the possibility to pass it a list of objects you might rewrite it as follows:
def create(self, request, pk=None, company_pk=None, project_pk=None):
is_many = isinstance(request.data, list)
serializer = self.get_serializer(data=request.data, many=is_many)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
Now your view will handle both single objects in POST data as well as a list of objects.
The second solution is to use a third party package
django-rest-framework-bulk provides the above functionality plus additional features (e.g. bulk update). You can check it out and decide whether it fits your needs.
Update: The solution for function based views
In order to get it to work with your function based view, the approach is similar:
#api_view(['POST'])
def post_calculation(request):
if request.method == 'POST':
is_many = isinstance(request.data, list)
# JSON to serializer object
serializer = SearchRequestSerializer(data=request.data, many=is_many)
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
serializer.save()
#-- do some processing stuff here ---
return Response(serializer.data)
Do not call .create() method directly, use .save() instead. Also, when using many=False on serializer, the created instance is available under serializer.instance. I am not sure how to obtain the list of created instances though. You can try the same serializer.instance. If it doesn't work, try to find how to get it.
Related
I want to get data with request.data.columns in frontend.I can do it with ViewSet with list method but how to do it with generics.APIView.
Below is my viewsets and generics code:
class TestList(viewsets.ViewSet):
queryset = Test.objects.all()
def list(self,request):
serializer = TestSerializer(self.queryset, many = True)
return Response({'columns': serializer.data})
class TestList(generics.RetriveAPIView):
queryset = Test.objects.all()
serializer_class = TestSerializer
class TestList(APIView):
queryset = Test.objects.all()
def list(self,request):
serializer = TestSerializer(self.queryset, many = True)
return Response({'columns': serializer.data})
change your urls.py like this.
path(r"url", TestList.as_view({"get": "list"}))
Correct code:
class TestList(APIView):
queryset = Test.objects.all()
def list(self,request):
queryset = self.get_queryset()
serializer = TestSerializer(queryset, many = True)
return Response({'columns': serializer.data})
Details about why i had to add queryset = self.get_queryset() instead of directly access self.queryset.From official drf documentation:
queryset - The queryset that should be used for returning objects from this view. Typically, you must either set this attribute, or override the get_queryset() method. If you are overriding a view method, it is important that you call get_queryset() instead of accessing this property directly, as queryset will get evaluated once, and those results will be cached for all subsequent requests.
I have two models (Like and News). I am using django-rest-framework to make a web api out of it.
class Like(models.Model):
user = models.ForeignKey(User)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
class News(models.Model):
user = models.ForeignKey(User)
title = models.CharField(max_length=150)
...
likes = GenericRelation(Like)
A Like object has its own user field to store who liked the News object. Now to check if a specific user exists in any of the likes of a News object, I am getting request.user from a SerializerMethodField.
class NewsSerializer(serializers.HyperlinkedModelSerializer):
user = UserSerializer()
likes_count = serializers.IntegerField(source='likes.count', read_only=True)
user_in_likes = serializers.SerializerMethodField()
class Meta:
model = News
fields = ('user', 'title', 'body', 'article_image', 'pub_date', 'likes_count', 'user_in_likes')
def get_user_in_likes(self, obj):
user = self.context['request'].user
what = obj.likes.filter(user=user).exists()
return what
When I go the /news/ url, I get the json objects including the user_in_likes to true/false for each news object.
However, I have another serialzer for different model which imports NewsSerializer class and other similar serializers:
class ActivityObjectRelatedField(serializers.RelatedField):
def to_representation(self, value):
if isinstance(value, User):
serializer = UserSerializer(value)
elif isinstance(value, Job):
serializer = JobSerializer(value)
elif isinstance(value, News):
serializer = NewsSerializer(value)
elif isinstance(value, Tender):
serializer = TenderSerializer(value)
else:
raise Exception('Unexpected type of tagged object')
return serializer.data
class ActivitySerializer(serializers.HyperlinkedModelSerializer):
actor = ActivityObjectRelatedField(read_only=True)
target = ActivityObjectRelatedField(read_only=True)
class Meta:
model = Activity
fields = ('url', 'actor', 'verb', 'target', 'pub_date')
Now if I visit /activities/, to get the activities objects I am getting an error:
KeyError at /activities/
'request'
And it points to the line of SerializerMethod of NewsSerialize class where self.context['request'].user is used.
Exception Location: /vagrant/myproject/news/serializers.py in get_user_in_likes, line 25
Again if I visit /news/ url, everything is fine and I get news objects. What am I missing here? Why is request not being recognized in the ActivitiesSerializer class? Please help me solve this problem. Thank you.
You are getting this error because you are not passing request in the context when instantiating the NewsSerializer class or other similar serializers in the to_representation() method.
You are manually instantiating the particular serializer class in to_representation() method. So, after instantiation, that particular serializer does not have access to ActivitySerializer's context thereby leading to the error.
You can pass the ActivitySerializer's context during instantiation of the relevant serializer in the to_representation() method.
class ActivityObjectRelatedField(serializers.RelatedField):
def to_representation(self, value):
if isinstance(value, User):
serializer = UserSerializer(value, context=self.context) # pass context
elif isinstance(value, Job):
serializer = JobSerializer(value, context=self.context) # pass context
elif isinstance(value, News):
serializer = NewsSerializer(value, context=self.context) # pass context
elif isinstance(value, Tender):
serializer = TenderSerializer(value, context=self.context) # pass context
else:
raise Exception('Unexpected type of tagged object')
return serializer.data
It seems like you don't populate the context dictionary of NewsSerializer with your request in the /activities/ view.
You probably use a class based view provided by Django REST Framework which populates this dictionary for you (see the get_serializer_context() method) and passes it to the Serializer instance. That's why it's automatically available to your serializer in your /news/ view.
In your /activities/ view, though, the context is passed to ActivitySerializer and isn't (automatically) propagated further from there. That's why there's no request key in your context dictionary of NewsSerializer. You would need to pass your request object to the NewsSerializer instance. To pass extra context to a Serializer you can add a context parameter containing a dictionary when instantiating (see the DRF documentation).
I have a Django model, Note, which has a class-based view. It is supposed to return a JSON object upon the appropriate query.
Before returning the object, however, I would like to check that the user field in the note object matches the user currently logged in. (Users should not be able to access Note objects that are not their own.) To do this, I tried rewriting the get() method, calling on self.retrieve() to inspect the object before returning it:
class NoteDetail(generics.RetrieveUpdateDestroyAPIView):
model = Note
serializer_class = NoteSerializer
permission_classes = (permissions.IsAuthenticated,)
renderer_classes = (JSONRenderer,)
def get(self, request, *args, **kwargs):
current_user = User.objects.get(pk=self.request.user.id)
note = self.retrieve(request, *args, **kwargs)
if note.author is current_user:
return note
else:
raise PermissionDenied('Note does not belong to authenticated user.')(author=current_user)
However, this returns a ContentNotRenderedError when run: The response content must be rendered before it can be accessed.
Is there a way for me to check the object before returning it? Must I find a workaround?
One potential workaround is to redefine get_queryset(), rather than get() or get_object(). get_queryset() is the function that returns all objects of the relevant model; get_object() narrows down among these given the argument pk.
By overriding get_queryset(), you restrict the potential objects that get_object() can pick. Thus the set is already filtered at the time get() is called.
def get_queryset(self):
current_user = User.objects.get(pk=self.request.user.id)
# Must filter by author to prevent making everyone's notes public
queryset = Note.objects.filter(author=current_user)
return queryset
The problem is as follows: I want to handle a POST request with JSON body. The body consists of an array of JSON Objects, without further nesting, i.e. simple HashMaps. All of these objects represent JSON-serialized domain classes from an Android Application, which will have their counterpart in my Grails app. I am thinking of parsing the JSON body, iterating through every element and saving each node as its corresponding domain class instance.
a) How should I save the instance? I am quite new to Grails/Groovy so please excuse any huge mistakes. Code so far is
public static Object JSONArray2Instances(String json, Class type) {
def slurper = new JsonSlurper()
def result = slurper.parseText(json)
//we only want to parse JSON Arrays
if (!(result instanceof JSONArray))
return null
result.each {
def instance = it.asType(type)
// now I need to save to domain class!
}
}
b) where do I place the corresponding code? Currently it is in /grails-app/src/groovy. Where do the tests go? (Since it is not a 'real' Grails component)
c) Is an intermediate command object more appropriate?
Your code should go in to the controller which is handling the request. Please take a look at
gson-grails plugin which has examples of how to serialize and deserialze objects and map them to domain objects. Please take a look at the grails basics where they talk about the conventions used in the grails application and the layout. There are good examples at grails site. Hope this helps
I solved my problem as follows, based on help provided by the comment from allthenutsandbolts. : (Grails-Gson plugin was not needed)
Let N2696AdminAction be the name of a Domain Class
in my controller:
class N2696AdminActionController extends RestfulController{
static responseFormats = ['json', 'xml']
def JSONHandlerService
N2696AdminActionController() {
super(N2696AdminAction)
}
#Override
#Transactional
def save(){
if (request!=null)
JSONHandlerService.instancesfromJSON(request.JSON)
}
}
then I delegate persisting to my service as follows
class JSONHandlerService {
def instancesfromJSON(Object request){
//we only want to parse JSON Arrays
if (!(request instanceof JSONArray))
return null
request.each {
def domainClass = Class.forName("${it.type}",
true, Thread.currentThread().getContextClassLoader())
def newDomainObject = domainClass.newInstance(it)
newDomainObject.save(failOnError:true, flush:true, insert: true)
}
}
}
type is a Json attribute which holds the full (package inclusive) name for my class. This way, I can save to multiple Domain Classes with the same POST request.
I'm learning Backbone.js and Flask (and Flask-sqlalchemy). I chose Flask because I read that it plays well with Backbone implementing RESTful interfaces. I'm currently following a course that uses (more or less) this model:
class Tasks(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(80), unique=True)
completed = db.Column(db.Boolean, unique=False, default=False)
def __init__(self, title, completed):
self.title = title
self.completed = completed
def json_dump(self):
return dict(title=self.title, completed=self.completed)
def __repr__(self):
return '<Task %r>' % self.title
I had to add a json_dump method in order to send JSON to the browser. Otherwise, I would get errors like object is not JSON serializable, so my first question is:
Is there a better way to do serialization in Flask? It seems that some objects are serializable but others aren't, but in general, it's not as easy as I expected.
After a while, I ended up with the following views to take care of each type of request:
#app.route('/tasks')
def tasks():
tasks = Tasks.query.all()
serialized = json.dumps([c.json_dump() for c in tasks])
return serialized
#app.route('/tasks/<id>', methods=['GET'])
def get_task(id):
tasks = Tasks.query.get(int(id))
serialized = json.dumps(tasks.json_dump())
return serialized
#app.route('/tasks/<id>', methods=['PUT'])
def put_task(id):
task = Tasks.query.get(int(id))
task.title = request.json['title']
task.completed = request.json['completed']
db.session.add(task)
db.session.commit()
serialized = json.dumps(task.json_dump())
return serialized
#app.route('/tasks/<id>', methods=['DELETE'])
def delete_task(id):
task = Tasks.query.get(int(id))
db.session.delete(task)
db.session.commit()
serialized = json.dumps(task.json_dump())
return serialized
#app.route('/tasks', methods=['POST'])
def post_task():
task = Tasks(request.json['title'], request.json['completed'])
db.session.add(task)
db.session.commit()
serialized = json.dumps(task.json_dump())
return serialized
In my opinion, it seems a bit verbose. Again, what is the proper way to implement them? I have seen some extensions that offer RESTful interfaces in Flask but those look quite complex to me.
Thanks
I would use a module to do this, honestly. We've used Flask-Restless for some APIs, you might take a look at that:
https://flask-restless.readthedocs.org/en/latest/
However, if you want build your own, you can use SQLAlchemy's introspection to output your objects as key/value pairs.
http://docs.sqlalchemy.org/en/rel_0_7/core/schema.html#metadata-reflection
Something like this, although I always have to triple-check I got the syntax right, so take this as a guide more than working code.
#app.route('/tasks')
def tasks():
tasks = Tasks.query.all()
output = []
for task in tasks:
row = {}
for field in Tasks.__table__.c:
row[str(field)] = getattr(task, field, None)
output.append(row)
return jsonify(data=output)
I found this question which might help you more. I'm familiar with SQLAlchemy 0.7 and it looks like 0.8 added some nicer introspection techniques:
SQLAlchemy introspection
Flask provides jsonify function to do this. Check out its working here.
Your json_dump method is right though code can be made concise. See this code snippet
#app.route('/tasks')
def tasks():
tasks = Tasks.query.all()
return jsonify(data=[c.json_dump() for c in tasks])