Add user specific fields to Django REST Framework serializer - python

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()

Related

Django rest framework: exclude results when nested serializer is empty

I have a nested serializer that works, but I would like to exclude instances where the nested serializer is empty. The filtering I'm using on the nested serializer works, but currently this code returns all Sites, most of which have empty site_observations arrays when filters are applied. I would like to return only Sites that contain site_observations. I have tried a SerializerMethodField for site_observations but have the same issue. Using DRF 3.12
Relevant models are Site, and Observation which has FK to site, with related field=site_observations
serializers.py
class FilteredObsListSerializer(serializers.ListSerializer):
def to_representation(self, data):
projName = self.context["projName"]
# this is my filter which works
data = filter_site_observations(data, self.context["request"],
projName)
return super(FilteredObsListSerializer, self).to_representation(data)
class ObsFilterSerializer(serializers.ModelSerializer):
class Meta:
list_serializer_class = FilteredObsListSerializer
model = Observation
fields = "__all__"
class SiteSerializer(GeoFeatureModelSerializer):
site_observations = ObsFilterSerializer(many=True)
class Meta:
model = Site
geo_field = "geometry"
fields = ("id", "name", "geometry", "site_observations")
views.py
class SiteList(generics.ListAPIView):
queryset = Site.objects.all().order_by("pk")
serializer_class = SiteSerializer
# this is for filtering Observations on segment of an url:
def get_serializer_context(self):
context = super(SiteList, self).get_serializer_context()
context.update({"projName": self.kwargs["name"]})
return context
How can I exclude Sites where site_observations is an empty list? Thanks.
One approach is to tell your view to only work with certain objects that meet some criteria:
class SiteList(generics.ListAPIView):
queryset = Site.objects.filter(
site_observations__isnull=False,
).distinct().order_by('pk')
This will tell SiteList to only work with Site objects that have site_observation relations existing. You need the distinct call here as described by this.
Thanks for the suggestion, which led to an answer. Not running filter_site_observations in the serializer worked. I reworked the filter to run in the view and got the results I needed. (I had tried this before using a SerializerMethodField but couldn't filter it properly; the nested serializer seems a better approach.) Thanks for the suggestions! Here's the filter:
def filter_site_observations(queryset, request, projName):
'''
filters a collection on project, observer status
'''
if request.user.is_authenticated:
authprojects = [
item.project.name
for item in ObserverStatus.objects.filter(observer=request.user.id)
]
if projName in authprojects:
return queryset.filter(site_observations__project__name=projName)
else:
return queryset.filter(
site_observations__project__name=projName).filter(
site_observations__private=False)
else:
return queryset.filter(
site_observations__project__name=projName).filter(
site_observations__private=False)
and the views.py:
class SiteList(generics.ListAPIView):
serializer_class = SiteSerializer
def get_serializer_class(self, *args, **kwargs):
if self.request.method in ("POST", "PUT", "PATCH"):
return SitePostSerializer
return self.serializer_class
def get_queryset(self):
projName = self.kwargs["name"]
queryset = Site.objects.all().order_by('pk')
queryset = filter_site_observations(queryset, self.request, projName)
queryset = queryset.filter(
site_observations__isnull=False, ).distinct()
queryset = self.get_serializer_class().setup_eager_loading(queryset)
return queryset
UPDATE AUG 28
Actually, to get the filtering I needed, I had to run pretty much the same filter in both the Observation serializer AND in the SiteList views.py. This was true regardless of whether I used SerializerMethodField or a simple nested serializer for the child data. Otherwise, I would get either: 1) ALL Sites, including ones that didn't have any Observations, or 2) Sites that had some non-private Observations, but also displayed all the private ones.
filters.py
from users.models import ObserverStatus
def filter_site_observations(queryset, request, projName):
'''
filters a collection on project, observer status; used in views.py.
Both of these filters seem to be required to get proper filtering.
'''
queryset = queryset.filter(site_observations__project__name=projName)
if request.user.is_authenticated:
authprojects = [
item.project.name
for item in ObserverStatus.objects.filter(observer=request.user.id)
]
if projName in authprojects:
return queryset
else:
return queryset.filter(site_observations__private=False)
else:
return queryset.filter(site_observations__private=False)
def filter_observations(queryset, request, projName):
'''
filters observations in ObsSerializer on observer status
'''
if request.user.is_authenticated:
authprojects = [
item.project.name
for item in ObserverStatus.objects.filter(observer=request.user.id)
]
if projName in authprojects:
return queryset
else:
return queryset.filter(private=False)
else:
return queryset.filter(private=False)
So I'm filtering twice and not sure why this needs to happen.

how can insert multiple record using

I'm working on a small project Django Rest Framework, I already create add contact function as you can see in my create function. now I'm working on bulk import, but when I submit my data as a list not as a dict I get an error message :
{"non_field_errors":["Invalid data. Expected a dictionary, but got list."]}
this is my code to add a contact,
class ContactView(ListModelMixin, viewsets.GenericViewSet):
queryset = Contact.objects.all()
serializer_class = ContactSerializer
def create(self, request):
serializeObject = ContactSerializer(data = request.data)
if serializeObject.is_valid():
serializeObject.save()
contactObject = Contact.objects.all()
contactSerializer = ContactSerializer(contactObject, many=True)
return Response(contactSerializer.data, status = status.HTTP_201_CREATED)
return Response(serializeObject.errors, status.HTTP_400_BAD_REQUEST)
Now i would like to create another function, for bulk create, since i have a list
This is my header data structure :
[{"Greeting":"amine","first_name":"alain","last_name":"amine","title":"ricardo","language":"ab#xyz.com","email":43822510594,"phone_1":43822510594,"phone_2":"not yet","mobile":43822510594,"fax":"not yet","internal_id":"xname"},{"Greeting":"bill","first_name":"microsoft","last_name":"bill","title":"microsoft","language":"bill#microsoft.com","email":652565455,"phone_1":652565455,"phone_2":"new york","mobile":652565455,"fax":"new york","internal_id":"microsoft"},{"Greeting":"john","first_name":"Yoyo","last_name":"Ruth","title":"xnameagain","language":"rh#xyz.com","email":5465559852,"phone_1":5465559852,"phone_2":"Vancouver","mobile":5465559852,"fax":"Vancouver","internal_id":"yname"}]
This is my serializer:
class ContactSerializer(serializers.ModelSerializer):
class Meta:
model = Contact
fields = "__all__"
I found the Solution on https://www.django-rest-framework.org/api-guide/serializers/#dealing-with-multiple-objects
all what i have to do is to add many=True to create multiple object
serializeObject = ContactSerializer(data = request.data, many=True)
Create method should look like this:
class ContactView(ListModelMixin, viewsets.GenericViewSet):
queryset = Contact.objects.all()
serializer_class = ContactSerializer
def create(self, request):
valid_objects = []
for data in request.data:
serializeObject = ContactSerializer(data=data)
if serializeObject.is_valid():
valid_objects.append(serializeObject)
else:
return Response(serializeObject.errors, status.HTTP_400_BAD_REQUEST)
for obj in valid_objects:
obj.save()
contactObject = Contact.objects.all()
contactSerializer = ContactSerializer(contactObject, many=True)
return Response(contactSerializer.data, status = status.HTTP_201_CREATED)
Advise
They may not be the best practices but it works.

update_or_create in my django rest framework api work wrong

My problem in GIF
Instead of updating the user's rating DRF creating new.
Maybe i made a mistake in serializer?
I wrote documentation but i dont kwon where i wrong.
My code:
views.py:
class CreateReviewView(APIView):
def post(self, request):
review = CreateReviewSerializer(data= request.data)
if review.is_valid():
review.save()
return Response(status=201)
class CreateRatingView(APIView):
def get_user(self, request):
user= request.user
if user =="AnonymousUser":
return "noname in CreateRaringView"
return user
def post(self, request):
serializer = CreateRatingSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
serializer.save(user=self.get_user(request))
return Response(status=201)
else:
return Response(status=400)
serializers.py:
class Meta:
model = Rating
fields = ('star','movie')
def new(self,validated_data):
rating = Rating.objects.update_or_create(
user= validated_data.get('user',None),
movie= validated_data.get('movie',None),
defaults={'start': validated_data.get("star")}
)
return rating
models.py:
class Rating(models.Model):
"""Рейтинг"""
user = models.ForeignKey(User,on_delete=models.CASCADE,verbose_name="Пользователь",related_name='user')
star = models.ForeignKey(RatingStar, on_delete=models.CASCADE, verbose_name="Звезда",related_name="star")
movie = models.ForeignKey(Movie, on_delete=models.CASCADE, verbose_name="Фильм",related_name="movie")
def __str__(self):
return f"{self.star} - {self.movie}"
class Meta:
#unique_together = ['user','movie','star']
verbose_name = "Рейтинг"
verbose_name_plural = "Рейтинги"
According to documentation, Calling .save() will either create a new instance, or update an existing instance, depending on if an existing instance was passed when instantiating the serializer class:
# .save() will create a new instance.
serializer = CommentSerializer(data=data)
# .save() will update the existing `comment` instance.
serializer = CommentSerializer(comment, data=data)
In your case you are only passing new data and missing existing instance.
def post(self, request):
serializer = CreateRatingSerializer(data=request.data)
I renamed def new(self,validated_data): to def create(self,validated_data): in serializers.py and all started working. >.<

Django Rest Framework object is not iterable?

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

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()

Categories