Django rest framework - Raising exception / Handling empty results while filtering - django-rest-framework

I have a user profile class and am checking if a user exists and if not want to create that user.
Am using the filter class for userprofile so that the client can call :
http://localhost:8000/users/?email=a#b.com
and if the result is empty will create a user with the email address.
Is there a way to intercept the query result and raise an exception when its empty and handle that to create the user.
If there is a better way would like to be corrected as well.
class UserQueryFilter(django_filters.rest_framework.FilterSet):
email = django_filters.CharFilter(name="user__email")
username = django_filters.CharFilter(name="user__username")
class Meta:
model = UserProfile
fields = ['email', 'username']
class UserViewSet(viewsets.ReadOnlyModelViewSet):
queryset = UserProfile.objects.all()
serializer_class = UserSerializer
filter_class = UserQueryFilter
Any help is appreciated.
Thanks
Anand

Django Rest Framework provide a functionality that is disabled by default. Maybe it could give you another approach to resolve your problem: PUT as create
In other hand, if you really need to create the user through a GET request with a querystring, you can use a MethodFilter from django-filters, for example:
class UserFilters(FilterSet):
user = MethodFilter(action='filter_user')
class Meta:
model = User
fields = ['user']
def filter_user(self, queryset, value):
if not value:
# Here Raise Exception
else:
# Check if the user exists, if not create it
users = queryset.filter(Q(username=value) | Q(email=value))
if not users.exists:
user = User.objects.create(...)
return queryset.filter(pk=user.id)
else:
return users
Hope this can help you. I'm not pretty sure about it works in that exact way but it's the idea.
Personally, I recommend you that try to execute that tasks through a more appropriate request like POST or PUT and manage in the corresponding method.

Related

Serialize available choices and mark selected

I am new to REST and django-rest-framework. I want to get list of available ManyToMany choices along with some way to know which ones are currently selected.
I have model like this:
class PGroup(models.Model):
.
permissions = models.ManyToManyField(
Permission, related_name="group_permissions", help_text=_('Select permissions for this group.')
)
Serializers.
class PermissionSerializer(serializers.ModelSerializer):
class Meta:
model = Permission
fields = ['pk', 'name',]
class PGroupSerializer(serializers.ModelSerializer):
permissions = PermissionSerializer(many=True)
class Meta:
model = PGroup
fields = [....'permissions']
Looking at Browseable API, with this setup I get 'permissions: []'(empty list) for generics.createAPIView and get the associated 'permissions[....]'(non-empty list) for generics.RetrieveUpdateAPIView.
I want a list of available permissions on both API views and also want to know which permissions are already selected for Update API view.
Can anyone please help.
Thanks
There are 2 ways to get the list of choices.
Using the SerializerMethodField,
from rest_framework import serializers
from .models import Permission
class PGroupSerializer(serializers.ModelSerializer):
permissions = PermissionSerializer(many=True)
all_available_permissions = serializers.SerializerMethodField()
def get_all_available_permissions(self, obj):
return Permission.objects.all()
class Meta:
model = PGroup
fields = ['permissions', "all_available_permissions"]
or using source, we can define a custom method on the model and point the serializer to use it using the source argument.
### models.py
class PGroup(models.Model):
.
permissions = models.ManyToManyField(
Permission, related_name="group_permissions", help_text=_('Select permissions for this group.')
)
def all_permissions(self):
return Permission.objects.all()
### serializers.py
class PGroupSerializer(serializers.ModelSerializer):
permissions = PermissionSerializer(many=True)
all_available_permissions = PermissionSerializer(many=True, read_only=True, source="all_permissions")
class Meta:
model = PGroup
fields = ['permissions', "all_available_permissions"]
2nd option is much better, IMO.
Note: you may not always want to send a full list of choices as that could get really slow overtime when u have hundreds or thousands of objects.

How to override get_serializer_class in APIView to change the serializer_class attribute in a class based view according to a specific condition.?

What I am trying to do is set the serializer_class attribute through get_serializer_class method like this
if user.role==1 :
return adminLoanView
elif user.role==2 :
return agentLoanView
else:
return customerLoanView
And in def put() function use these serializers based on the logged in user to perform different function that is admin and agent can only edit specific fields. And customer can not edit anything.
I am still a beginner in DRF so let me know if there is any other better way to do this.
If you're using Django's own User model which has .is_superuser and .is_staff fields, you can override the the get_serializer_class() like so:
def get_serializer_class(self):
user = self.request.user
if user.is_superuser:
return SerializerClassWithHigherPermissions
elif user.is_staff:
return SerializerClassWithIntermediatePermissions
else:
return SerializerClassWithCustomerPermissions
If you have a custom User model with a OnetoOneField on the User model, you could access that instance through the user instance in the view, and then check for permissions conditions.

Update Foreign-key Related Instance on Post Request

I am trying to implement a system where a user can subscribe to a company. For that I have to implement the following flow:
User registers
User klicks on a button to subscribe to a company
User has to enter a code (every company has a secret code. By possession the user proves that he is somewhat related to the company)
User is subscribed
For this, I have to implement an API endpoint that receives the code by the user (the user is authenticated at this point).
It is shameful, but I am lost: I am thinking of implementing a view like this.
class RegisterUserToCustomer(APIView):
permission_classes = (permissions.IsAuthenticated,)
def post(self, request, format=None):
serializer = CustomerSerializer(data=request.data)
if serializer.is_valid():
serializer.save(user=self.request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.data, status=status.HTTP_400_BAD_REQUEST)
Obviously, I have to:
Receive the code from the user via POST method
Search the database and figure out, if the code corresponds to any company
Edit the ForeignKey field on the user and link it to the company
But where would I implement this logic. With my limited experience I see three possibilities:
Call serializer.save() and write a custom create() method (but I don't want to create anything so this seems like bad practice
Implement this logic in the view, but I want to access the code from the validated_data (this seems like a problem?)
Can I write a custom save() method for the serializer? Are there any examples for a custom save() method since the original drf save() method contains a lot of validation logic.
Obviously I don't expect you guys to write the whole code for me, but maybe someone has somewhat of a blueprint of how and where to implement this?
Steps to do this:
Create a field company_code in Customer table and make that
field as Foreign key.
Create a Serializer with that foreign key field.
Create a viewset or generic views to expect that field from Post method as Id of the Company. Use save() method to store foreign key.
This scenarios is only for Subscribe one organisation, if you're able to subscribe multiple organisation make that field as ManytoMany field.
Refer the code sample,
Models.py
class Customer(models.Model):
# customer related field
name = models.CharField(max_length=20)
organization = models.ForeignKey(Organization, on_delete=models.CASCADE)
Serializer.py
class CustomerSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('name', 'organization', )
Views.py
class CustomerViewSet(viewsets.ModelViewSet):
serializer_class = CustomerSerializer
queryset = Customer.objects.all()
Request data(POST Json)
{
"name": "test",
"organization": "id-of company"
}

DRF 3.0: UniqueTogetherValidator with a read-only field

In process of upgrading to Django REST Framework 3.0 from 2.4.4 and I want to have a read-only user field, but this is failing because 'user' is being required by the UniqueTogetherValidator (I think)
I have model (excuse typos, this is simplified and the code works fine IRL):
class ExampleModel(models.Model):
some_attr = models.PositiveSmallIntegerField()
other_attr = models.PositiveSmallIntegerField()
user = models.ForeignKey(User)
class Meta:
unique_together = ('some_attr', 'other_attr', 'user')
Viewset:
class ExampleViewSet(viewsets.ModelViewSet):
queryset = ExampleModel.objects.all()
serializer_class = ExampleSerializer
def perform_create(self, serializer):
serializer.save(user=self.request.user)
def perform_update(self, serializer):
serializer.save(user=self.request.user)
Serializer:
class ExampleSerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=True)
class Meta:
model = ExampleModel
Now, I keep getting errors saying: {"user":["This field is required."]}, which was not the case before. In a slightly different example with the same basic problem, I get the assertion error May not set both 'read_only' and 'required' even though I am not setting user as required.
I receive the same error regardless if I add required=False for the user attribute in the serializer, or if I add user to the the excluded fields in the serializer's meta.
When I use the handy new serializer printing, I see:
class Meta:
validators = [UniqueTogetherValidator(queryset=ExampleModel.objects.all(), fields=('user', 'some_attr', 'other_attr'))]
which gets automatically added based on the model's unique_together. If I explicitly overwrite this and do not include 'user' in the fields for UniqueTogetherValidator then everything works as before.
Is this an intended consequence of the 3.0 update? Seems to me that adding request.user in the perform_create / perform_update is very standard DRF procedure as demonstrated in the tutorial. I realize not having the new validation just means failing at the DB level instead, and the new validation probably gives better error messages, but
Is there a solution other than to override the validation for every serializer where this is an issue?
Thanks in advance for any help!
This is a known issue that we are in the process of addressing within Django REST Framework. As of right now, there is a note in the documentation about UniqueTogtherValidator that says
Note: The UniqueTogetherValidation class always imposes an implicit constraint that all the fields it applies to are always treated as required. Fields with default values are an exception to this as they always supply a value even when omitted from user input.
This explains why you are seeing an error because the field is required, even though you are explicitly settings read_only=True. You may want to look into the CurrentUserDefault class, which may suit your needs while avoiding the issue with the UniqueTogetherValidator.
class ExampleSerializer(serializers.ModelSerializer):
user = UserSerializer(
read_only=True
default=serializers.CurrentUserDefault()
)
class Meta:
model = ExampleModel
This should do the same thing as your perform_create and perform_update hooks.

Django REST framework restrict posting and browsable Api-fields

I use the Django Rest framework together with an JavaScript app. I have some difficulties to get the posting of new data items right with the generic ModelViewSet
Most importantly I want to restrict what a poster can submit
(they should only be allowed to post items that have the user_id of this user (the authenticated user of the session).
I don't know when/where I should check for this? Is this a validation problem?
How I understand the permission classes is that they restrict the method (Post/Get) or check for user groups.
Also my user field in the item model is a foreign key to the user model
so the browsable api suggest in the Html-form a dropdown with the information about other users. (their email adresses and some other fields).
My data items look like this
[{
"id": 792,
"name": "test",
"category": 1,
"value": 5,
"user": "33"
}]
Here is my Serializer and the Viewset:
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ('id',
'name',
'category',
'value',
'user',
)
class ItemViewSet(viewsets.ModelViewSet):
serializer_class = ItemSerializer
def get_queryset(self):
return Item.objects.filter(user=self.request.user)
I can't believe this issue with the DRF Create/Update (Post/Put) form isn't more widely discussed.
It's a huge data privacy issue - e.g. One can restrict the List API view to only show items owned by a User via overriding the get_queryset method inside as below:
# views.py
class ItemViewSet(viewsets.ModelViewSet):
def get_queryset(self):
return Item.objects.filter(user=self.request.user)
But as OP notes, when accessing the API Create/Post or Update/Put form for the ItemViewSet, there is seemingly no easy way to restrict the user options to the user itself.
I had a similar issue myself building a survey platform, where I want to restrict choice of survey/question/options etc. to those owned by the user, and prevent users from inadvertently seeing each other's data.
Jocelyn's answer works for the OP's particular situation where we already know that the Item.user must equal request.user, so we override this on the perform_create method.
But Jocelyn's solution is insufficient for situations where you do not know in advance what the relationship between model instances will be (e.g. in my case where a new question objected could be added to any one of a user's surveys).
The solution I came up with was the nuclear option: do away with the Viewset altogether for Create and Update functionality, and use a set of custom views.APIView classes instead, as below (adapted for the case of the OP, only showing Create).
class ItemCreateView(views.APIView):
def post(self, request, format=None):
post_user_id = int(request.data['user'].split('/')[-2])
request_user_id = request.user.id
serializer = ItemSerializer(data=request.data, context={'request': request})
if post_user_id == request_user_id:
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)
else:
return Response('Not Allowed: Owner is not User', status=status.HTTP_401_UNAUTHORIZED)
Please note, I'm using a HyperlinkedModelSerializer rather than a plain ModelSerializer, hence the need for .split('/')[-2] to grab the post_user_id
Handling the user field
First set the user field to be readonly:
# serializers.py
class ItemSerializer(serializers.ModelSerializer):
user = serializers.ReadOnlyField()
class Meta:
model = Item
fields = ('id',
'name',
'category',
'value',
'user',
)
Then auto-set the user id on creation:
# views.py
class ItemViewSet(viewsets.ModelViewSet):
serializer_class = ItemSerializer
def get_queryset(self):
return Item.objects.filter(user=self.request.user)
def perform_create(self, serializer):
serializer.save(user=self.request.user.customer)
Handling permissions
Just use standard permissions mechanism to define a custom one :
# permissions.py
from rest_framework import permissions
class IsOwner(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
return (request.user.is_authenticated() and
(obj.user == request.user.customer))
...and use it in your viewset :
# views.py
from permissions import IsOwner
class ItemViewSet(viewsets.ModelViewSet):
permission_classes = [IsOwner]
...

Resources