I have two APIView classes and one ModelSerializer class. The APIView classes are using the serializer class. Is there any way to know which APIView class is calling the serializer class?
I need seperate representation_view for these 2 APIView.
APIView Classes
class OwnerAllListAPIView(APIView):
def get(self, request, *args, **kwargs):
user = self.request.user
all_list = ListName.objects.filter(owner=user).all()
list_serializer = core_slr.ListNameSerializer(all_list, many=True)
return response('Owner list', list_serializer.data, status.HTTP_200_OK)
class ListNameDetailAPIView(APIView):
def get(self, request, *args, **kwargs):
list_name = ListName.objects.filter(id=self.kwargs.get('list_name_id')).first()
list_serializer = core_slr.ListNameSerializer(list_name)
return response('list name detail view', list_serializer.data, status.HTTP_200_OK)
Serializer Class
class ListNameSerializer(serializers.ModelSerializer):
class Meta:
model = ListName
fields = [
'id', 'owner', 'name'
]
def to_representation(self, instance):
ret = super(ListNameSerializer, self).to_representation(instance)
ret['owner'] = f"{instance.owner.first_name} {instance.owner.last_name}"
ret['total_question'] = QuestionBank.objects.filter(list_name=instance).count()
return ret
In the to_representation View, I just wanna to know which API is currently calling the serializer.
You can easily do that by accessing the context of the Serializer, like the following:
...
def to_representation(self, instance):
ret = super(ListNameSerializer, self).to_representation(instance)
ret['owner'] = f"{instance.owner.first_name} {instance.owner.last_name}"
ret['total_question'] = QuestionBank.objects.filter(list_name=instance).count()
if self.context['view'].action == 'retrieve':
pass # Do something
elif self.context['view'].action == 'list':
pass # Do the Other Thing
return ret
Related
This is my code.
class MyViewSet(ModelViewSet):
serializer_class = MySerializer
queryset = MyClass.objects.all()
def get_serializer_class(self):
if request.user.is_superuser:
return self.serializer_class
else:
return OtherSerializer
def perform_create(self, serializer):
if request.user.is_superuser:
if serializer.is_valid():
serializer.save(organization=self.request.user.organization)
else:
employee = Employee.objects.get(user=self.request.user)
serializer.save(employee=employee, organization=self.request.user.organization)
This is my Serializer:
class MySerializer(serializers.ModelSerializer):
class Meta:
model = models.MyClass
def validate(self, data):
employee = data.get('employee')
members = Team.objects.get(id=team.id.members.all())
if employee not in members:
raise serializers.ValidationError('Invalid')
return data
The issue is, My custom validate function is not being called when I call it inside perform_create() in my ViewSet.
What might be the issue?
validate member function should be defined in the scope of the serializer class not inside class Meta. So you need to left-indent the function validate:
class MySerializer(serializers.ModelSerializer):
class Meta:
model = models.MyClass
def validate(self, data):
employee = data.get('employee')
members = Team.objects.get(id=team.id.members.all())
if employee not in members:
raise serializers.ValidationError('Invalid')
return data
I am passing a parameter to a serilaizer like this:
serializer = AttractionTicketSerializer(attraction, context={'api_consumer':request.auth.application})
I have a view which inherits from ListModelMixin, I need to pass this context param to the serilizer as well.
here is a summarized view:
class AttractionView(mixins.ListModelMixin, generics.GenericAPIView):
authentication_classes = AUTHENTICATION_CLASSES
permission_classes = [IsAuthenticatedOrTokenHasReadWriteScope]
queryset = Attraction.objects.all()
serializer_class = AttractionSerializer
def get(self, request: Request, *args, **kwargs):
attractions: Dict[str, Any] = self.list(request, *args, **kwargs)
return attractions
Is there a way to do it?
Thanx in advance
You can override the get_serializer_context() function to add additional context to be passed to the serializer.
class AttractionView(mixins.ListModelMixin, generics.GenericAPIView):
<...already_defined_attributes...>
def get_serializer_context(self):
# you can access self.request here
# if you need to get some data from your request
context = super().get_serializer_context()
context.update({
'new_key': <new_value>
})
return context
You can also override the list function to achieve it.
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
context = self.get_serializer_context()
context.update({
'new_key': 'new_value'
})
if page is not None:
serializer = self.get_serializer(page, context=context, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, context=context, many=True)
return Response(serializer.data)
Based on your specific requirement you can choose any of the above approaches.
I created an extra function in my View that receives a list with hyperlinked references to some ResourceGroup objects, but I don't know how to convert them to database instances
class ResourceViewSet(viewsets.ModelViewSet):
queryset = Resource.objects.all()
serializer_class = ResourceSerializer
#action(methods=['put'], detail=True)
def groups_append(self, request, pk=None):
instance = self.get_object()
groups = request.data.get("groups")
for resource_group in groups:
instance.groups.add(WHAT_HERE(resource_group))
instance.save()
return Response(self.get_serializer(instance, many=False).data)
This is the request:
PUT http://.../api/resources/1/groups_append/
with body:
{"groups": ["http://.../api/resource_groups/1/", ...]}
ResourceSerializer:
class ResourceSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Resource
fields = ('resource_id', 'object_id', 'type', 'system', 'path', 'groups', 'job_set')
def update(self, instance, validated_data):
instance.object_id = validated_data.get('object_id', instance.object_id)
instance.type = validated_data.get('type', instance.type)
instance.system = validated_data.get('system', instance.system)
instance.path = validated_data.get('path', instance.path)
instance.save()
return instance
ResourceGroupSerializer:
class ResourceGroupSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ResourceGroup
fields = ('resource_group_id', 'label', 'resource_set')
def update(self, instance, validated_data):
instance.label = validated_data.get('label', instance.label)
instance.save()
return instance
use HyperlinkedRelatedField for groups in ResourceSerializer or just create a new serializer for this action(the main idea is to get the data using a serializers not just directly from the request body) like this:
class ResourceSerializer(serializers.HyperlinkedModelSerializer):
groups = serializers.HyperlinkedRelatedField(
many=True,
read_only=True,
view_name='groups-detail' ## name of the groups detail url
)
class Meta:
model = Resource
....
then edit your action as below:
#action(methods=['put'], detail=True)
def groups_append(self, request, pk=None):
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
### then get the groups from the validated data
groups = serializer.validated_data.get('groups', [])
....
....
References:
1- hyperlinkedrelatedfield
I am trying to return custom json with get_queryset but always get 404 error in response.
class TestViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows groups to be viewed or edited.
"""
queryset = Test.objects.all()
serializer_class = TestSerializer
def get_queryset(self):
if self.request.method == "GET":
content = {'user_count': '2'}
return HttpResponse(json.dumps(content), content_type='application/json')
If I delete everything starting from def I'll got correct response with standard json data. What I am doing wrong?
If you don't need a ModelViewSet and just want custom JSON on a GET request
You can also use an APIView, which doesn't require a model
class MyOwnView(APIView):
def get(self, request):
return Response({'some': 'data'})
and
urlpatterns = [
url(r'^my-own-view/$', MyOwnView.as_view()),
]
With a ModelViewSet
You've put the custom JSON into get_queryset, that's wrong. If you want to use a ModelViewSet, this by itself should be enough:
class TestViewSet(viewsets.ModelViewSet):
queryset = Test.objects.all()
serializer_class = TestSerializer
This ModelViewSet comes with default implementations for .list(), .retrieve(), .create(), .update(), and .destroy(). Which are available for you to override (customize) as needed
Returning custom JSON from .retrieve() and/or .list() in ModelViewSet
E.g. to override .retrieve() to return custom view when retrieving a single object. We can have a look at the default implementation which looks like this:
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)
So as an example to return custom JSON:
class TestViewSet(viewsets.ModelViewSet):
queryset = Test.objects.all()
serializer_class = TestSerializer
def retrieve(self, request, *args, **kwargs):
return Response({'something': 'my custom JSON'})
def list(self, request, *args, **kwargs):
return Response({'something': 'my custom JSON'})
There are 2 ways to custom the response in Class-based views with ModelViewSet
Solution 1: custom in views.py
class StoryViewSet(viewsets.ModelViewSet):
permission_classes = (permissions.AllowAny,)
queryset = Story.objects.all()
serializer_class = StorySerializer
def retrieve(self, request, *args, **kwargs):
# ret = super(StoryViewSet, self).retrieve(request)
return Response({'key': 'single value'})
def list(self, request, *args, **kwargs):
# ret = super(StoryViewSet, self).list(request)
return Response({'key': 'list value'})
Solution 2: custom in serializers.py (I recommend this solution)
class StorySerializer(serializers.ModelSerializer):
class Meta:
model = Story
fields = "__all__"
def to_representation(self, instance):
ret = super(StorySerializer, self).to_representation(instance)
# check the request is list view or detail view
is_list_view = isinstance(self.instance, list)
extra_ret = {'key': 'list value'} if is_list_view else {'key': 'single value'}
ret.update(extra_ret)
return ret
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()