Django Rest Framework object is not iterable? - python

I have serialised one of my models that has a foreign key in. I get 'Parent' object is not iterable
models.py
class Parent(models.Model):
# Parent data
class Child(models.Model):
parent = ForeignKey(Parent)
serializer.py
class ChildSerializers(serializers.ModelSerializer):
parent = serializers.RelatedField(many=True)
class Meta:
model = ReportField
fields = (
'id',
'parent'
)
api.py
class ChildList(APIView):
def get(self, request, format=None):
child = Child.objects.all()
serialized_child = ChildSerializers(child, many=True)
return Response(serialized_child.data)
Im guessing i have to pass the parent list to the child list but not sure of the best way to do it
attempt api.py
class ChildList(APIView):
def get(self, request, format=None):
child = Child.objects.all()
parent = Parent.objects.all()
serialized_child = ChildSerializers(child, many=True)
serialized_parent = ChildSerializers(parent, many=True)
return Response(serialized_child.data, serialized_parent.data)

Why using many=True. Parent is just a single field, no need to use explicit serializer field. Just get rid of these many=True
-answered by mariodev in comment.

You can do something like this using python collections as an intermediate
#serializers.py
class TimelineSerializer(serializers.Serializer):
childs= childSerializer(many=True)
parents = parentSerializer(many=True)
#apiviews.py
from collections import namedtuple
Timeline = namedtuple('Timeline', ('childs', 'parents'))
def list(self, request):
timeline = Timeline(
childs=Child.objects.all(),
parents=Parent.objects.all(),
)
serializer = TimelineSerializer(timeline)
return Response(serializer.data)

If your using a ModalSerializer then you have two foreign keys in a modal, then use "to_representation" function like this
def to_representation(self, instance):
rep = super().to_representation(instance)
rep['pcatalog_name'] = CatalogSerializer(instance.pcatalog_name).data
rep['pcategory_name'] = CategorySerializer(instance.pcategory_name).data
return rep

Replace ForeignKey by ManyToManyField to clarify the serializer field with many = True

Related

'QuerySet' object has no attribute 'simplified'

I am trying to do a fairly simple GET request that leads to a query with DRF:
def get(self, request, character):
char_entry = Dictionary.objects.filter(Q(simplified=character) | Q(traditional=character))
serializer = DictionarySerializer(char_entry)
return Response({"character": serializer.data})
My DictionarySerializer looks like this:
from rest_framework import serializers
from .models import Dictionary
class DictionarySerializer(serializers.ModelSerializer):
class Meta:
model = Dictionary
fields = ["id", "simplified", "pinyin_numbers", "pinyin_marks", "translation", "level", "traditional", ]
And my Dictionary model looks like this:
class Dictionary(models.Model):
traditional = models.CharField(max_length=50)
simplified = models.CharField(max_length=50)
pinyin_numbers = models.CharField(max_length=50)
pinyin_marks = models.CharField(max_length=50)
translation = models.TextField()
level = models.IntegerField()
class Meta:
db_table = 'dictionary'
indexes = [
models.Index(fields=['simplified', ]),
models.Index(fields=['traditional', ]),
]
As far as I can tell, this should serialize all the fields from the Dictionary table, including simplified.
Why can't Django find the attribute? What am I missing?
A QuerySet is a collection of items. It can thus contain zero, one or more items. YOu need to serialize with the many=True parameter:
def get(self, request, character):
char_entry = Dictionary.objects.filter(
Q(simplified=character) | Q(traditional=character)
)
serializer = DictionarySerializer(char_entry, many=True)
return Response({'character': serializer.data})
or if we know there is only one item, we should retrieve that single item:
from django.shortcuts import get_object_or_404
def get(self, request, character):
char_entry = get_object_or_404(
Dictionary,
Q(simplified=character) | Q(traditional=character)
)
serializer = DictionarySerializer(char_entry, many=True)
return Response({'character': serializer.data})

How to pack model queryset in serializer into one field DRF?

For example i have a few models:
class Parent(Model):
api_key = CharField(max_length=250)
class Child(Model):
parent = ForeignKey(Parent, related_name='children')
status = CharField(max_length=250)
I wrote view based on ListAPIView, and serializer:
class ChildSerializer(ModelSerializer):
class Meta:
fields = ['id']
I need to take all Children with parent by find Parent by api_key and return this as:
{
children:[
{'id':1},
{'id':2}
]}
But i take this:
[
{'id':1},
{'id':2}
]
Well, you can define a ParentSerializer which will also return the child objects data with it:
class ParentSerializer(ModelSerializer):
child = ChildSerializer(many=True)
class Meta:
fields = ['api_key', 'child']
Then you need to find the parent by api_key and pass the Parent object to this serializer. Like this:
class ParentView(RetrieveAPIView):
queryset = Parent.objects.all()
lookup_field = 'api_key'
serializer_class = ParentSerializer
lookup_url_kwarg = 'api_key'
Finally set the url to:
path('/parent/<str:api_key>/', ParentView.as_view(), name='parent-detail')
Update
If you need to get the data from request.GET(url querystring), then it much simpler. Try like this:
class ParentView(ListAPIView):
queryset = Parent.objects.all()
serializer_class = ParentSerializer
def get_queryset(self, *args, **kwargs):
queryset = super().get_queryset(*args, **kwargs)
api_key = request.GET.get('api_key')
if api_key:
return queryset.filter(api_key=api_key)
return queryset

How to set the DestroyAPIView method not real delete my instance?

I have a model, like bellow:
class BModel(models.Model):
name = models.CharField(max_length=11)
status = models.CharField(default="is_active") # if delete: deleted
a = models.ForeignKey(AModel)
def delete(self, using=None, keep_parents=False):
pass # there I want to set the BModel's status to `deleted`
Serializer:
class BModelSerializer(ModelSerializer):
class Meta:
model = BModel
fields = "__all__"
Its Views is bellow:
class BModelListAPIView(ListAPIView):
serializer_class = BModelSerializer
permission_classes = []
queryset = BModel.objects.all()
class BModelDestroyAPIView(DestroyAPIView):
serializer_class = BModelSerializer
permission_classes = []
queryset = BModel.objects.all()
My requirement is when use Django-Rest-Framework delete my BModel, I want to set the BModel's status field to deleted, not real delete the instance. How to access it?
when I tried to write a delete method to my Model, there comes the default delete method:
def delete(self, using=None, keep_parents=False):
I have some questions about this:
Is this is for the BModel's instance?
Whether should through the delete method to access my requirement?
DestroyAPIView has a perform_destroy method. You can override that and add your logic of deletion. For eg:
class BModelDestroyAPIView(DestroyAPIView):
serializer_class = BModelSerializer
permission_classes = []
queryset = BModel.objects.all()
def perform_destroy(self, instance):
instance.delete_flag = True
instance.save()
The delete method of BModel will override the default delete method. Which will also affect the Django Admin. You can also add a custom delete method to Manager of that Model. Refer

Pass extra arguments to Serializer Class in Django Rest Framework

I want to pass some arguments to DRF Serializer class from Viewset, so for I have tried this:
class OneZeroSerializer(rest_serializer.ModelSerializer):
def __init__(self, *args, **kwargs):
print args # show values that passed
location = rest_serializer.SerializerMethodField('get_alternate_name')
def get_alternate_name(self, obj):
return ''
class Meta:
model = OneZero
fields = ('id', 'location')
Views
class OneZeroViewSet(viewsets.ModelViewSet):
serializer_class = OneZeroSerializer(realpart=1)
#serializer_class = OneZeroSerializer
queryset = OneZero.objects.all()
Basically I want to pass some value based on querystring from views to Serializer class and then these will be allocate to fields.
These fields are not include in Model in fact dynamically created fields.
Same case in this question stackoverflow, but I cannot understand the answer.
Can anyone help me in this case or suggest me better options.
It's very easy with "context" arg for "ModelSerializer" constructor.
For example:
in view:
my_objects = MyModelSerializer(
input_collection,
many=True,
context={'user_id': request.user.id}
).data
in serializers:
class MyModelSerializer(serializers.ModelSerializer):
...
is_my_object = serializers.SerializerMethodField('_is_my_find')
...
def _is_my_find(self, obj):
user_id = self.context.get("user_id")
if user_id:
return user_id in obj.my_objects.values_list("user_id", flat=True)
return False
...
so you can use "self.context" for getting extra params.
Reference
You could in the YourView override get_serializer_context method like that:
class YourView(GenericAPIView):
def get_serializer_context(self):
context = super().get_serializer_context()
context["customer_id"] = self.kwargs['customer_id']
context["query_params"] = self.request.query_params
return context
or like that:
class YourView(GenericAPIView):
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.context["customer_id"] = request.user.id
serializer.context["query_params"] = request.query_params
serializer.is_valid(raise_exception=True)
...
and anywhere in your serializer you can get it. For example in a custom method:
class YourSerializer(ModelSerializer):
def get_alternate_name(self, obj):
customer_id = self.context["customer_id"]
query_params = self.context["query_params"]
...
To fulfill the answer of redcyb - consider using in your view the get_serializer_context method from GenericAPIView, like this:
def get_serializer_context(self):
return {'user': self.request.user.email}
A old code I wrote, that might be helpful- done to filter nested serializer:
class MySerializer(serializers.ModelSerializer):
field3 = serializers.SerializerMethodField('get_filtered_data')
def get_filtered_data(self, obj):
param_value = self.context['request'].QUERY_PARAMS.get('Param_name', None)
if param_value is not None:
try:
data = Other_model.objects.get(pk_field=obj, filter_field=param_value)
except:
return None
serializer = OtherSerializer(data)
return serializer.data
else:
print "Error stuff"
class Meta:
model = Model_name
fields = ('filed1', 'field2', 'field3')
How to override get_serializer_class:
class ViewName(generics.ListAPIView):
def get_serializer_class(self):
param_value = self.context['request'].QUERY_PARAMS.get('Param_name', None)
if param_value is not None:
return Serializer1
else:
return Serializer2
def get_queryset(self):
.....
Hope this helps people looking for this.
List of element if your query is a list of elements:
my_data = DataSerializers(queryset_to_investigate,
many=True, context={'value_to_pass': value_passed}
in case off single data query:
my_data = DataSerializers(queryset_to_investigate,
context={'value_to_pass': value_passed}
Then in the serializers:
class MySerializer(serializers.ModelSerializer):
class Meta:
fields = '__all__'
model = 'Name_of_your_model'
def on_representation(self, value):
serialized_data = super(MySerializer, self).to_representation(value)
value_as_passed = self.context['value_to_pass']
# ..... do all you need ......
return serialized_data
As you can see printing the self inside on_representation you can see: query_set: <object (x)>, context={'value_to_pass': value_passed}
This is a simpler way, and you can do this in any function of serializers having self in the parameter list.
These answers are far to complicated; If you have any sort of authentication then add this property to your serializer and call it to access the user sending the request.
class BaseSerializer(serializers.ModelSerializer):
#property
def sent_from_user(self):
return self.context['request'].user
Getting the context kwargs passed to a serializer like;
...
self.fields['category'] = HouseCategorySerializer(read_only=True, context={"all_fields": False})
...
In your serializer, that is HouseCategorySerializer do this in one of your functions
def get_houses(self, instance):
print(self._context.get('all_fields'))
Using self._context.get('keyword') solved my mess quickly, instead of using self.get_extra_context()

Add user specific fields to Django REST Framework serializer

I want to add a field to a serializer that contains information specific to the user making the current request (I don't want to create a separate endpoint for this). Here is the way I did it:
The viewset:
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
filter_class = ArticleFilterSet
def prefetch_likes(self, ids):
self.current_user_likes = dict([(like.article_id, like.pk) for like in Like.objects.filter(user=self.request.user, article_id__in=ids)])
def get_object(self, queryset=None):
article = super(ArticleViewSet, self).get_object(queryset)
self.prefetch_likes([article.pk])
return article
def paginate_queryset(self, queryset, page_size=None):
page = super(ArticleViewSet, self).paginate_queryset(queryset, page_size)
if page is None:
return None
ids = [article.pk for article in page.object_list]
self.prefetch_likes(ids)
return page
The serializer:
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
def to_native(self, obj):
ret = super(ArticleSerializer, self).to_native(obj)
if obj:
view = self.context['view']
ret['has_liked'] = False
if hasattr(view, 'current_user_liked'):
ret['has_liked'] = obj.pk in view.current_user_liked
return ret
Is there a better place to inject the prefetching of liked articles, or a nicer way to do this in general?
you can do it with SerializerMethodField
Example :
class PostSerializer(serializers.ModelSerializer):
fav = serializers.SerializerMethodField('likedByUser')
def likedByUser(self, obj):
request = self.context.get('request', None)
if request is not None:
try:
liked=Favorite.objects.filter(user=request.user, post=obj.id).count()
return liked == 1
except Favorite.DoesNotExist:
return False
return "error"
class Meta:
model = Post
then you should call serializer from view like this:
class PostView(APIVIEW):
def get(self,request):
serializers = PostSerializer(PostObjects,context={'request':request})
I'd be inclined to try and put as much of this as possible on the Like model object and then bung the rest in a custom serializer field.
In serializer fields you can access the request via the context parameter that they inherit from their parent serializer.
So you might do something like this:
class LikedByUserField(Field):
def to_native(self, article):
request = self.context.get('request', None)
return Like.user_likes_article(request.user, article)
The user_likes_article class method could then encapsulate your prefetching (and caching) logic.
I hope that helps.
According to the Django Documentation - SerializerMethodField, I had to change the code of rapid2share slightly.
class ResourceSerializer(serializers.ModelSerializer):
liked_by_user = serializers.SerializerMethodField()
def get_liked_by_user(self, obj : Resource):
request = self.context.get('request')
return request is not None and obj.likes.filter(user=request.user).exists()

Categories