How to hide field from Json response - python

I would like to hide from public api simple Serializer field (created without any model). How I can solve this?
same_result = serializers.SerializerMethodField()

You can use write_only argument to achieve this goal :
class YourSerializer(serializers.Serializer):
same_result = serializers.SerializerMethodField(write_only=True)

you can override the to to_representation method
def to_representation(self, instance):
data = super().to_representation(instance)
data.pop('same_result')
return data

I think you could either use the permissions configuration to identify either a public or private user based on membership of a group, then you could switch in the View, something like:
class UserViewSet(viewsets.ViewSet):
def list(self, request):
queryset = User.objects.all()
if request.user.groups.filter(name="private").exists():
serializer = PrivateUserSerializer(queryset, many=True)
else:
serializer = PublicUserSerializer(queryset, many=True)
return Response(serializer.data)
Then you define the fields on your serializer as you wish.

Related

Provide request data from view into serializer ModelViewSet Django

I try to make a custom list function inside ProductViewSet because I need to download an extra field - the highest product price in database. How can I provide the request argument from def list into serializer? I mean right now I get error 'NoneType' object has no attribute 'user' in this line: if request.user.is_authenticated.
So how can I fix it that he can read self.context.get('request') correctly?
class ProductViewSet(viewsets.ModelViewSet):
...
def list(self, request):
queryset = Product.objects.all()
serializer = ProductSerializer(queryset, many=True)
return Response(serializer.data)
class ProductSerializer(serializers.ModelSerializer):
...
class Meta:
model = Product
...
def get_is_followed(self, obj):
request = self.context.get('request')
if request.user.is_authenticated:
return Product.objects.filter(id=obj.id, followers=request.user.id).exists()
I want to work it like a default ModelViewSet list but with an extra field.
You have used ModelViewSet which already has a serializer_class attribute. You can simply provide the serializer_class and the serializer context is taken care by DRF itself. So, instead of writing
serializer = ProductSerializer(queryset, many=True) you should write in this way:
class ProductViewSet(viewsets.ModelViewSet):
serializer_class = ProductSerializer
queryset = Product.objects.all()
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
If your concern is only the request object as a context to the serializer then there is no need to override the list method in the ProductViewSet. By default three contexts (request, format and view) are passed to the serializer but if you need extra contexts then you can always override def get_serializer_context(self) method in the view.
This is the default signature of the get_serializer_context:
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'request': self.request,
'format': self.format_kwarg,
'view': self
}

How to filter through the list/retrieve function on Model ViewSet

Anyone know How can I implement a search filter on list/retrieve function to a viewset?
I'm trying to use DRF Searh-filter on ViewSet but it's not working (it doesn't return the object filtered).
What I want to return is something like --> /store/1/locker/1/controller?controller={name_controller}
view.py
class ControllerViewSet(viewsets.ModelViewSet):
serializer_class = ControllerSerializer
queryset = Controller.objects.all()
filter_backends = [filters.SearchFilter]
search_fields = ['controller']
def list(self, request, store_pk=None, locker_pk=None):
queryset = Controller.objects.filter(locker__store=store_pk, locker=locker_pk)
serializer = ControllerSerializer(queryset, many=True, context={'request': request})
return Response(serializer.data)
def retrieve(self, request, pk=None, store_pk=None, locker_pk=None):
queryset = Controller.objects.filter(pk=pk, locker=locker_pk, locker__store=store_pk)
locker = get_object_or_404(queryset, pk=pk)
serializer = ControllerSerializer(locker, context={'request': request})
return Response(serializer.data)
You set a filter backend, but then you overrode the code that would have called it (list(), retrieve()).
Seems like the only reason you overrode those methods is to filter the queryset by store and locker pks. This can be done in one place to affect all requests and without breaking existing code.
def get_queryset(self):
locker_pk = self.kwargs["locker_pk"] # named parameters in url appear in self.kwargs
store_pk = self.kwargs["store_pk"]
return super().get_queryset().filter(locker=locker_pk, locker__store=store_pk)
And that's it. DRF will call this method to get queryset, filter it as configured, and serialize the data with your serializer automatically. No need to manually implement what's already provided.
Note: ensure your pks are valid with regex. You don't want your app to crash if someone requests /store/x/locker/y/controller, right? The regex should be \d+ for any integer.

How to make a field in Serializer both readable and writeable

I am writing a Serializer for my model which will be accessed using GET, POST and PATCH endpoints. I have a property in my model which I am using as a source for a field. However, using source= in serializer field is making it ready-only.
If I remove the source="get_field1" from the
field1 = serializers.NullBooleanField(source="get_field1")
then I am able to update the data.
But I HAVE to use the source to get the right value of the field.
class MyModel(models.Model):
field1 = NullBooleanField(default=None)
#property
get_field1(self):
data = True
# ...some logic
return data
Now I have a serializer that I am using
class MyModelSerializer(serializers.ModelSerializer):
field1 = serializers.NullBooleanField(source="get_field1")
class Meta:
model = MyModel
fields = ('field1')
Now in my API endpoint, I do this
serializer = MyModelSerializer(my_model, data=request.data, partial=True)
if serializer.is_valid():
serializer.save() # <- throws error "can't set attribute"
Also, I would like to mention that the field in the serializer is referred by the property name and not by the field name.
Example: If I do
>> serializer.validated_data
>> 'OrderedDict(['get_field1'], True) # <- shouldn't this by field1 and not get_field1
Answer by #JPG is good, but I feel it's a hacky way.
I would override the to_representation method of the Serializer to achieve your purpose.
Here's what you can do
class MyModelSerializer(serializers.ModelSerializer):
field1 = serializers.NullBooleanField() # get rid of source
def to_representation(self, instance):
data = super(MyModel, self).to_representation(instance)
data.update({
'field1': instance.get_field1
})
return data
class Meta:
model = MyModel
fields = ('field1')
This way you are implicitly providing the source and your field becomes writeable. So every time you GET, POST, or PATCH you'll get the right value.
This can be done by overriding the __init__() method of the serializer. Apart from that, we've to pass some context data to the serializer to distinguish between the GET, POST and PATCH requests.
class MyModelSerializer(serializers.ModelSerializer):
field1 = serializers.NullBooleanField() # remove "source" argument from here
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.context['request'].method == 'GET':
self.fields['field1'] =serializers.NullBooleanField(source= "get_field1")
class Meta:
model = MyModel
fields = ('field1',)
and while serializing the data, don't forget to pass the request as context, as
serializer = MyModelSerializer(my_model, data=request.data, partial=True, context={"request": request})

django rest framework add field when not in list view

I'm using the Django Rest Framework and I'd like to be able to add extra detail to the serializer when a single object is returned, which would be left out of the list view.
In the code below I add the celery_state field to the TestModelSerializer, but I'd only like this field to be added when its returning a single object, not when it's returning the list of TestModel data.
I've looked at the list_serializer_class option but it seems to just use the original model serializer so it will always still include the field even if I try to exclude from there.
What are my options?
class TestModelSerializer(serializers.HyperlinkedModelSerializer):
celery_state = serializers.CharField(source='celery_state', read_only=True)
class Meta:
model = TestModel
class TestModelViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows TestModels to be viewed or edited.
"""
authentication_classes = (SessionAuthentication, BasicAuthentication)
permission_classes = (IsAuthenticatedOrReadOnly,)
queryset = TestModel.objects.all()
serializer_class = TestModelSerializer
Since the serializer class (used by the viewsets) passes many argument, you can use that to control the fields output:
class TestModelSerializer(serializers.HyperlinkedModelSerializer):
# ...
def __init__(self, *args, **kwargs):
super(TestModelSerializer, self).__init__(*args, **kwargs)
if kwargs.get('many', False):
self.fields.pop('celery_state')
Inspired by #mariodev answer:
The other possibility is to override many_init static method in serializer. Acording comments in thie code (https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/serializers.py#L128 ) it is suggested variant.
from rest_framework import serializers
class ExtendedSerializer(serializers.Serializer):
...
#classmethod
def many_init(cls, *args, **kwargs):
kwargs['child'] = cls()
kwargs['child'].fields.pop('extractedFiled')
return serializers.ListSerializer(*args, **kwargs)
You can have an extra serializer called ExtendedTestModelSerializer which would contain the extra fields that you want.
After that, you can use the get_serializer_class method to decide which serializer is used based on request.action -
class TestModelViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows TestModels to be viewed or edited.
"""
authentication_classes = (SessionAuthentication, BasicAuthentication)
permission_classes = (IsAuthenticatedOrReadOnly,)
queryset = TestModel.objects.all()
# serializer_class = TestModelSerializer
get_serializer_class(self):
if self.request.action == 'list':
return TestModelSerializer
return ExtendedTestModelSerializer

Return the current user with Django Rest Framework

I am currently developing an API using Django.
However, I would like to create a view that returns the current User with the following endpoint: /users/current/.
To do so, I created a list view and filtered the queryset on the user that made the request. That works, but the result is a list, not a single object. Combined with pagination, the result looks way too complicated and inconsistent compared to other endpoints.
I also tried to create a detail view and filtering the queryset, but DRF complains that I provided no pk or slug.
With something like this you're probably best off breaking out of the generic views and writing the view yourself.
#api_view(['GET'])
def current_user(request):
serializer = UserSerializer(request.user)
return Response(serializer.data)
You could also do the same thing using a class based view like so...
class CurrentUserView(APIView):
def get(self, request):
serializer = UserSerializer(request.user)
return Response(serializer.data)
Of course, there's also no requirement that you use a serializer, you could equally well just pull out the fields you need from the user instance.
#api_view(['GET'])
def current_user(request):
user = request.user
return Response({
'username': user.username,
'email': user.email,
...
})
The best way is to use the power of viewsets.ModelViewSet like so:
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
def get_object(self):
pk = self.kwargs.get('pk')
if pk == "current":
return self.request.user
return super().get_object()
viewsets.ModelViewSet is a combination of mixins.CreateModelMixin + mixins.RetrieveModelMixin + mixins.UpdateModelMixin + mixins.DestroyModelMixin + mixins.ListModelMixin + viewsets.GenericViewSet. If you need just list all or get particular user including currently authenticated you need just replace it like this
class UserViewSet(mixins.RetrieveModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet):
# ...
Instead of using full power of ModelViewSet you can use mixins. There is RetrieveModelMixin used to retrieve single object just like it is mentioned here - http://www.django-rest-framework.org/api-guide/viewsets/#example_3
class UserViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = UserSerializer
def get_object(self):
return self.request.user
If you need also update your model, just add UpdateModelMixin.
If you must use the generic view set for some reason, you could do something like this,
class UserViewSet(viewsets.ModelViewSet):
serializer_class = UserSerializer
def get_object(self):
return self.request.user
def list(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
retrieve method is called when the client requests a single instance using an identifier like a primary key /users/10 would trigger the retrieve method normally. Retrieve itself calls get_object. If you want the view to always return the current used then you could modify get_object and force list method to return a single item instead of a list by calling and returning self.retrieve inside it.
I used a ModelViewSet like this:
class UserViewSet(viewsets.ModelViewSet):
serializer_class = UserSerializer
def dispatch(self, request, *args, **kwargs):
if kwargs.get('pk') == 'current' and request.user:
kwargs['pk'] = request.user.pk
return super().dispatch(request, *args, **kwargs)
Use this way to get logged in user data in django rest framework
class LoggedInUserView(APIView):
def get(self, request):
serializer = UserSerializer(self.request.user)
return Response(serializer.data)
Add the api in urls.py file.
path('logged_in_user', LoggedInUserView.as_view())

Categories