How do I override viewsets.ModelViewSet in Django REST Framework? - python

So my model is simple as
class Face(models.Model):
uid = models.CharField(max_length=510, primary_key=True)
photo = models.ImageField(upload_to='face_photos')
serializer
class FaceSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Face
fields = ['uid', 'photo']
extra_kwargs = {'uid': {'required': True}, 'photo': {'required': True}}
and view should be something like
class FaceViewSet(viewsets.ModelViewSet):
queryset = Face.objects.all()
serializer_class = FaceSerializer
permission_classes = [permissions.AllowAny]
And it works. However:
I don't want list, update, delete options. Only POST and GET.
I want to add my logic on post, so if uid exists then update, else create... as well other processing.
I want custom response after the POST.
How do I achieve this all not loosing all the goodies that viewsets.ModelViewSet provides, like validations, auto generated HTML fields in Rest API web view, etc?

This worked for me:
class FaceViewSet(mixins.CreateModelMixin, mixins.RetrieveModelMixin, GenericViewSet):
"""
API endpoint for adding and processing new client (by uid) face
"""
queryset = Face.objects.all()
serializer_class = FaceSerializer
permission_classes = [permissions.AllowAny]
def create(self, request):
if "uid" in request.POST:
try:
instance = Face.objects.get(pk=request.POST['uid'])
serializer = FaceSerializer(
instance=instance,
data=request.data
)
except Face.DoesNotExist:
serializer = FaceSerializer(data=request.data)
else:
serializer = FaceSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = FaceSerializer(instance=instance)
return Response(serializer.data)

for number one you can use this:
http_method_names = ['get','post']
buth get method also cintains list method

Related

How can I pass an id as an argument to delete or patch an user?

I'm trying to set endpoints to PATCH or DELETE users according to a permission but my current code only allows me to apply those changes to the account I'm logged to. Is there a way for me to pass an id or an email as an argument in the body of a request to modify other accounts without using the ID in the URL? Thank you.
serializers.py
class UserModifySerializer(serializers.ModelSerializer):
class Meta:
model = User
exclude = ['id', 'user_role']
class UserSerializer(serializers.ModelSerializer):
organization = OrganizationSerializer(read_only=True)
class Meta:
model = User
fields = ['id', 'email', 'first_name', 'last_name', 'organization', 'user_role', 'language']
models.py
class UserViewSet(mixins.UpdateModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet):
swagger_schema = None
serializer_class = UserSerializer
#action(detail=False, methods=['patch'], permission_classes = [IsAuthenticated], url_path='modify-user')
def update_user(self, request):
user = User.objects.get(id=self.request.user.id)
if user.user_role in [Roles.ORGANIZATION_USER, Roles.ORGANIZATION_ADMIN, Roles.WEBSITE_ADMIN]:
serializer = UserModifySerializer(user, data=request.data, partial=True)
if serializer.is_valid():
serializer.save(**serializer.validated_data)
return Response(status=status.HTTP_200_OK)
return Response(status=status.HTTP_401_UNAUTHORIZED)
#action(detail=False, methods=['delete'], permission_classes = [IsWebsiteAdmin, IsOrganizationAdmin])
def delete_member(self, request):
user = User.objects.get(id=self.request.user.id)
if user.user_role in [Roles.ORGANIZATION_ADMIN, Roles.WEBSITE_ADMIN]:
members = User.objects.filter(organization=self.request.user.organization)\
.filter(id=id).delete()
return Response(status=status.HTTP_200_OK)
return Response(status=status.HTTP_401_UNAUTHORIZED)
You can put the necessary data in a json format inside request's body and then access it in the view like so:
data = json.loads(request.body)
How you put the data depends on how you call the view.

Get Authenticated user on many=True serializer Viewset

I'm writing a rest api using Django Rest Framework, I have an endpoint to create objects on POST method and this method is overridden in order to allow bulk adding. However, the object is an "intermediate table" between Pacient and Symptoms and in order to create it I need to provide the pacient object or id and the same for the symptom. I get the Symptom id in the request, so that's not an issue, however the pacient is the authenticated user (who's making the request). Now, how do I edit the create method in the serializer in order to do that?
Here's my view:
class PacienteSintomaViewSet(viewsets.ModelViewSet):
serializer_class = SintomaPacienteSerializer
queryset = SintomaPaciente.objects.all()
permission_classes = (IsAuthenticated, )
http_method_names = ['post', 'get']
def create(self, request, *args, **kwargs):
many = True if isinstance(request.data, list) else False
serializer = SintomaPacienteSerializer(data=request.data, many=many)
if serializer.is_valid():
sintomas_paciente_lista = [SintomaPaciente(**data) for data in serializer.validated_data]
print(serializer.validated_data)
SintomaPaciente.objects.bulk_create(sintomas_paciente_lista)
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors,status=status.HTTP_400_BAD_REQUEST)
And this is my serializer:
class SintomaPacienteSerializer(serializers.ModelSerializer):
def create(self, validated_data):
sintoma_paciente = SintomaPaciente.objects.create(
sintoma_id=self.validated_data['sintoma_id'],
paciente_id=THIS NEEDS TO BE FILLED,
data=self.validated_data['data'],
intensidade=self.validated_data['intensidade'],
)
return sintoma_paciente
class Meta:
model = SintomaPaciente
fields = ('id', 'sintoma_id', 'paciente_id', 'intensidade', 'data',
'primeiro_dia', 'ativo')
There is two way.
First one, you can pass your user to serializer inside context, and use it in serializer:
in your view:
def create(self, request, *args, **kwargs):
many = True if isinstance(request.data, list) else False
serializer = SintomaPacienteSerializer(data=request.data, many=many,context={'user':request.user})
in your serializer you can access this user with self.context['user']
Second way, you don't need to pass user to serializer again. Also If you already override the create method in your View, you don't need to override create method in serializer. I think it is wrong logically. Anyway, you can use your user when create object in view:
def create(self, request, *args, **kwargs):
many = True if isinstance(request.data, list) else False
serializer = SintomaPacienteSerializer(data=request.data, many=many)
if serializer.is_valid():
sintomas_paciente_lista = [SintomaPaciente(**data,paciente_id=request.user.id) for data in serializer.validated_data]
print(serializer.validated_data)
....

How better way to create a function to pass a JSON by serializer in django rest?

I'm studying django rest framework and would like the create a function. In this function, I need to pass a list in JSON and update by serializer.
For help I wrote a code example below.
Serialzer example:
class GarageViewSet(viewsets.ModelViewSet):
queryset = Garage.objects.all()
serializer_class = GarageSerializer
model = Garage
class CarViewSet(RestrictedQuerysetMixin, viewsets.ModelViewSet):
queryset = Car.objects.all()
serializer_class = CarSerializer
model = Car
Well. I need to update a car list through the garage serializer. I'm thinking anything like this:
(example view)
class GarageViewSet(viewsets.ModelViewSet):
queryset = Garage.objects.all()
serializer_class = GarageSerializer
model = Garage
#action(detail=True, methods=['put'])
def update_car(self, request):
...
serializer = CarSerializer(queryset, many=True)
...
return Response(serializer.data)
Attempt 1:
Searching and reading the doc, I tried this way:
#action(methods=['put'], detail=False)
def update_car(self, request, *args, **kwargs):
if request.method == 'PUT':
data = JSONParser().parse(request)
serializer = CarSerializer(data=data, many=True)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data)
return JsonResponse(serializer.errors, status=400)
But I received this error:
non_field_errors:
["Expected a list of items but got type "dict"."]
Attempt 2:
With a #fxgx I tried too:
def update_car(self, request):
serializer = CarSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
validated_data = dict(list(serializer.validated_data.items()))
queryset = Car.objects.update(**validated_data)
return Response(CarSerializer(queryset, many=True).data)
But I received this error:
{
"detail": "Not found."
}
DRF serializers do not support bulk updates, you must pass an object instance to serializer to update it. What you can do is serializing data using serializer, updating objects with validated data and then serializing objects again to get response data:
class GarageViewSet(viewsets.ModelViewSet):
queryset = Garage.objects.all()
serializer_class = GarageSerializer
model = Garage
#action(detail=False, methods=['put'])
def update_car(self, request):
...
# Use partial=True for partial updates.
serializer = CarSerializer(data=request.data)
# Validate data.
serializer.is_valid(raise_exception=True)
# Get validated data in dictionary format.
validated_data = dict(list(serializer.validated_data.items()))
# Update objects
quertset.update(**validated_data)
...
return Response(CarSerializer(queryset, many=True).data)

django filter on APIView

I have a APIView class for showing all the rents and posting and delete etc. Now i want search feature so i tried to use DjangoFilterBackend but it is not working. I see in documentation, it has been used with ListAPIView but how can i use it in APIView.
class Rent(APIView):
"""
List all the rents if token is not provided else a token specific rent
"""
serializer_class = RentSerializer
filter_backends = (DjangoFilterBackend,)
filter_fields = ('city', 'place', 'property_category',)
search_fields = ('=city', '=place')
def get(self, request, token=None, format=None):
reply={}
try:
rents = Rental.objects.all()
if token:
rent = Rental.objects.get(token=token)
reply['data'] = self.serializer_class(rent).data
else:
reply['data'] = self.serializer_class(rents, many=True).data
except Rental.DoesNotExist:
return error.RequestedResourceNotFound().as_response()
except:
return error.UnknownError().as_response()
else:
return Response(reply, status.HTTP_200_OK)
when i search the rent with the following parameters in the url, i get all the rents, instead i should get only those rents that lies in city Kathmandu and place koteshwor
http://localhost:8000/api/v1/rents?city=Kathmandu&place=Koteshwor
To use the functionality of DjangoFilterBackend, you could incorporate the filter_queryset method from GenericViewSet, which is the DRF class that inherits from APIView and leads to all specific 'generic' view classes in DRF. It looks like this:
def filter_queryset(self, queryset):
"""
Given a queryset, filter it with whichever filter backend is in use.
You are unlikely to want to override this method, although you may need
to call it either from a list view, or from a custom `get_object`
method if you want to apply the configured filtering backend to the
default queryset.
"""
for backend in list(self.filter_backends):
queryset = backend().filter_queryset(self.request, queryset, self)
return queryset
https://github.com/encode/django-rest-framework/blob/master/rest_framework/generics.py
Here If you are using APIView, There is nothing to do with filters.So you have to do like
get_data = request.query_params #or request.GET check both
Then
Rental.objects.filter(city=get_data['city'], place=get_data['place'])
In case someone is wondering how can we integrate django_filters filter_class with api_views:
#api_view(['GET'])
#permission_classes([permissions.IsAuthenticated])
def filter_data(request, format=None):
qs = models.YourModal.objects.all()
filtered_data = filters.YourFilter(request.GET, queryset=qs)
filtered_qs = filtered_data.qs
....
return response.Ok(yourData)
Adding to #ChidG's answer. All you need to do is override the DjangoFilterBackend's filter_queryset method, which is the entry point for the filter, and pass it the instance of your APIView. The important point to note here is you must declare filter_fields or filter_class on the view in order to get the filter to work. Otherwise it just return your queryset unfiltered.
If you're more curious about how this works, the class is located at django_filters.rest_framework.backends.py
In this example, the url would look something like {base_url}/foo?is_active=true
from django_filters.rest_framework import DjangoFilterBackend
class FooFilter(DjangoFilterBackend):
def filter_queryset(self, request, queryset, view):
filter_class = self.get_filter_class(view, queryset)
if filter_class:
return filter_class(request.query_params, queryset=queryset, request=request).qs
return queryset
class Foo(APIView):
permission_classes = (AllowAny,)
filter_fields = ('name', 'is_active')
def get(self, request, format=None):
queryset = Foo.objects.all()
ff = FooFilter()
filtered_queryset = ff.filter_queryset(request, queryset, self)
if filtered_queryset.exists():
serializer = FooSerializer(queryset, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
else:
return Response([], status=status.HTTP_200_OK)

Django REST Framework : "This field is required." with required=False and unique_together

I want to save a simple model with Django REST Framework. The only requirement is that UserVote.created_by is set automatically within the perform_create() method. This fails with this exception:
{
"created_by": [
"This field is required."
]
}
I guess it is because of the unique_together index.
models.py:
class UserVote(models.Model):
created_by = models.ForeignKey(User, related_name='uservotes')
rating = models.ForeignKey(Rating)
class Meta:
unique_together = ('created_by', 'rating')
serializers.py
class UserVoteSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=True)
created_by = UserSerializer(read_only=True)
class Meta:
model = UserVote
fields = ('id', 'rating', 'created_by')
views.py
class UserVoteViewSet(viewsets.ModelViewSet):
queryset = UserVote.objects.all()
serializer_class = UserVoteSerializer
permission_classes = (IsCreatedByOrReadOnly, )
def perform_create(self, serializer):
serializer.save(created_by=self.request.user)
How can I save my model in DRF without having the user to supply created_by and instead set this field automatically in code?
Thanks in advance!
I had a similar problem and I solved it by explicitly creating and passing a new instance to the serializer. In the UserVoteViewSet you have to substitute perform_create with create:
def create(self, request, *args, **kwargs):
uv = UserVote(created_by=self.request.user)
serializer = self.serializer_class(uv, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
I was able to solve this with one-liner in views.py
def create(self, request, *args, **kwargs):
request.data.update({'created_by': request.user.id})
return super(UserVoteViewSet, self).create(request, *args, **kwargs)
Since this view expects user to be authenticated, don't forget to extend permission_classes for rest_framework.permissions.IsAuthenticated
The other weird way you can do is use signals like this
#receiver(pre_save, sender=UserVote)
def intercept_UserVote(sender, instance, *args, **kwargs):
import inspect
for frame_record in inspect.stack():
if frame_record[3]=='get_response':
request = frame_record[0].f_locals['request']
break
else:
request = None
instance.pre_save(request)
Then basically you can define pre_save in your model
def pre_save(self, request):
# do some other stuff
# Although it shouldn't happen but handle the case if request is None
self.created_by = request.user
The advantage of this system is you can use same bit of code for every model. If you need to change anything just change in pre_save(). You can add more stuff as well
Add the following to the ViewSet:
def perform_create(self, serializer):
serializer.save(user=self.request.user)
And the following on the Serializer:
class Meta:
extra_kwargs = {
'user': {
'required': False,
},
}
Below code worked for me.
Even I was facing same error after many experiments found something, so added all fields in serializer.py in class meta, as shown below -
class Emp_UniSerializer( serializers.ModelSerializer ):
class Meta:
model = table
fields = '__all__' # To fetch For All Fields
extra_kwargs = {'std_code': {'required': False},'uni_code': {'required': False},'last_name': {'required': False},'first_name': {'required': False}}
Here, we can update any field which are in "extra_kwargs", it wont show error ["This field is required."]

Categories

Resources