How to set read_only dynamically in django rest framework? - django

I am trying to check if the user id not equal to 1 then he should not be able to update few fields. I tried something similar to the following code but it did not work because of the following issues
self.user.id don't actually return the user I need to get the authenticated user in different why?
the def function maybe should have a different name like update?
also the general way maybe wrong?
class ForAdmins(serializers.ModelSerializer)):
class Meta:
model = User
fields = '__all__'
class ForUsers(serializers.ModelSerializer)):
class Meta:
read_only_fields = ['email','is_role_veryfied','is_email_veryfied']
model = User
fields = '__all__'
class UsersSerializer(QueryFieldsMixin, serializers.ModelSerializer):
def customize_read_only(self, instance, validated_data):
if (self.user.id==1):
return ForAdmins
else:
return ForUsers
class Meta:
# read_only_fields = ['username']
model = User
fields = '__all__'

You can make the decision which serializer you want to pass from your views
or
you can do it inside modelSerializer update method.
for getting user from Serializer class Try:
request = self.context.get('request', None)
if request:
user = request.user
for getting user from View class Try:
user = self.request.user

Related

How to retrive data from OneToOne Relational Model in DRF using API view

I have imported User model and customized it a/c to my need and make OneToOne Relation with UserProfileModel Model. While retrieving data I got this error.
"The serializer field might be named incorrectly and not match any attribute or key on the AnonymousUser instance.
Original exception text was: 'AnonymousUser' object has no attribute 'gender'."
My Model is :
class UserProfileModel(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True, related_name='userprofilemodel')
gender = models.CharField(max_length=20)
locality = models.CharField(max_length=70)
city = models.CharField(max_length=70)
address = models.TextField(max_length=300)
pin = models.IntegerField()
state = models.CharField(max_length=70)
profile_image = models.FileField(upload_to='user_profile_image', blank=True)
My Serializer looks like:
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model= User
fields = ['id', 'name' , 'email','mobile',]
class UserProfileModelSerializer(serializers.ModelSerializer):
user = serializers.StringRelatedField(many=True, read_only=True)
class Meta:
model= UserProfileModel
fields = ['user','gender' , 'locality','city','address','pin','state','profile_image', ]
My view looks like:
class UserProfileDataView(APIView):
def get(self, request, format=None):
# user = UserProfileModel.objects.all()
serializer = UserProfileModelSerializer(request.user)
return Response(serializer.data, status=status.HTTP_200_OK)
I want to retrieve profile data of the logged user using UserProfileModel Model
Your first issue in that you are passing a User instance to the UserProfileModelSerializer, which is expecting a UserProfileModel instance. To fix this you need to change:
serializer = UserProfileModelSerializer(request.user)
to
serializer = UserProfileModelSerializer(request.user.userprofilemodel)
where userprofilemodel is the related_name you have set on the user field in your UserProfileModel.
Second issue is, as Mohamed Beltagy said, you're allowing anyone to access the view, including unauthenticated users. Django rest framework has a built in mixin that you can use to restrict access to authenticated users only (https://www.django-rest-framework.org/api-guide/permissions/#isauthenticated).
from rest_framework.permissions import IsAuthenticated
class UserProfileDataView(APIView):
permission_classes = [IsAuthenticated]
the problem here is you are passing an anonymous user which has no profile ( you permit non-authenticated users access this view)
def get(self, request, format=None):
# user = UserProfileModel.objects.all()
if request.user.is_authenticated:
serializer = UserProfileModelSerializer(request.user)
return Response(serializer.data, status=status.HTTP_200_OK)
else:
return Response(status=status.HTTP_401_UNAUTHORIZED)

DRF SERILAZATION

I serialize the field named "product" with ProductSerializer() inside OrderItemSerializer().
That's what I want.
class OrderItemSerializer(serializers.ModelSerializer):
product = ProductSerializer()
class Meta:
model = models.OrderItem
fields = ('id','order', 'product', 'quantity')
The output is;
But when I try to request with POST Method needs to send Product as a dictionary, just giving the id value is not enough.
How can I POST by sending only the id value?
I haven't written anything about the operation yet. Default ModelViewSet
class OrderItemViewSet(ModelViewSet):
queryset = OrderItem.objects.all()
serializer_class = serializers.OrderItemSerializer
permission_classes = (IsOwnerOrNot, IsAuthenticated)
def get_queryset(self):
user = self.request.user
return self.filter_queryset(queryset=self.queryset.filter(order__user=self.request.user))
If you're supporting writable nested representations you'll need to write .create() or .update() methods that handle saving multiple objects.
class UserSerializer(serializers.ModelSerializer):
profile = ProfileSerializer()
class Meta:
model = User
fields = ['username', 'email', 'profile']
def create(self, validated_data):
profile_data = validated_data.pop('profile')
user = User.objects.create(**validated_data)
Profile.objects.create(user=user, **profile_data)
return user

how to post foreign key with name of user other than id in django rest framework

hello everything is working fine, but if i want to post any dat in rest framework i need to pass the id of user , which is impractical how can i make it to accept the name of the user and post the the request, find my code below
serializers.py
class Authserializers(serializers.ModelSerializer):
class Meta:
model = Unique
fields = '__all__'
views.py
#api_view(['POST'])
def auth_post_data(request):
serializer = Authserializers(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
when i type the name of the user as string it does not accept the input, plz help me in this situation
You need to write a custom method for .create() to support a writeable nested serializer.
Then you can pass a request like this:
{
"user": {
"username": "SomeName"
},
"other_field": "value"
}
EDIT
Here's a rough example. Be advised that it has not validation to make sure the username is valid etc. And since it don't know how your models look like I can't make it specific to yours. So you'll have to modify it. Also, you should probably don't want to have __all__ on fields on a serializer with User as model.
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
class Authserializers(serializers.ModelSerializer):
# Create field user, a nested serializer.
user = UserSerializer()
def create(self, validated_data):
# Extract field 'user' from POST data.
user_data = validated_data.pop('user')
# Find a user with that username
user = User.objects.get(username=user_data['username'])
# Create instance of you model "Unique" here
return Unique.objects.create(
user=user,
**validated_data # will set other fields that were passed.
)
class Meta:
model = Unique
fields = '__all__'

Django Rest Framework unique together validation with field absent from request

I'm implementing some voting functionality in an application, where a logged-in user specifies a post that they would like to vote for using a payload like this:
{
"post": 1,
"value": 1
}
As you can tell, the a user field is absent - this is because it gets set in my viewset's perform_create method. I've done this to ensure the vote's user gets set server side. This is what the viewset looks like:
class CreateVoteView(generics.CreateAPIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = VoteSerializer
def perform_create(self, serializer):
serializer.save(user=self.request.user)
Here is what the model looks like:
class Vote(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='votes', null=False)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='votes', null=False)
class Values(models.IntegerChoices):
UP = 1, _('Up')
DOWN = -1, _('Down')
value = models.IntegerField(choices=Values.choices, null=False)
class Meta:
unique_together = ('post', 'user')
and finally, the serializer:
class VoteSerializer(serializers.ModelSerializer):
class Meta:
model = Vote
fields = ['post', 'value']
From what I understand, in order for DRF to enforce a unique together validation, both fields (in my case, user and post) must be included in the serializer's fields. As I've mentioned, I'd like to avoid this. Is there any other way of implementing this type of validation logic?
EDIT:
To clarify: the records do not save - I receive this error:
django.db.utils.IntegrityError: (1062, "Duplicate entry '1-3' for key 'api_vote.api_vote_post_id_user_id_73614533_uniq'")
However, my goal is to return a Bad Request instead of an Internal Server Error much like I would when traditionally using a DRF serializer and excluding required fields from a payload.
To output a custom error message due to the IntegrityError, you can override the create method in your serializer:
from django.db import IntegrityError
class VoteSerializer(serializers.ModelSerializer):
class Meta:
model = Vote
fields = ['post', 'value']
def create(self, validated_data):
try:
validated_data['user'] = self.context['request'].user
return super().create(validated_data)
except IntegrityError:
error_msg = {'error': 'IntegrityError message'}
raise serializers.ValidationError(error_msg)
You can try this on your views
try:
MoviesWatchList.objects.create(user=request.user, content=movie)
return response.Response({'message': f'{movie} added in watchlist.'}, status=status.HTTP_201_CREATED)
except:
return response.Response({'message': f'{movie} already added to watchlist.'}, status=status.HTTP_304_NOT_MODIFIED)

Django Rest Framework, hyperlinking a nested relationship

I've got two models: User and Ticket. Ticket has one User, User has many Tickets
I've accomplished that when i go to url /users/1/tickets, i'm getting the list of user's tickets.
I want to use hyperlinked relations, and here is what i see in my User model representation:
"tickets": [
"http://127.0.0.1:8000/tickets/5/",
"http://127.0.0.1:8000/tickets/6/"
]
But I want it to be like
"tickets": "http://127.0.0.1:8000/users/1/tickets"
Is there a way to do that with DRF?
The url:
url(r'^users/(?P<user_pk>\d+)/tickets/$',
views.TicketsByUserList.as_view(),
name='myuser-tickets'),
The view:
class TicketsByUserList(generics.ListAPIView):
model = Ticket
serializer_class = TicketSerializer
def get_queryset(self):
user_pk = self.kwargs.get('user_pk', None)
if user_pk is not None:
return Ticket.objects.filter(user=user_pk)
return []
User serializer (i tried to play with tickets field definition, changing type, view_name, but with no effect):
class UserSerializer(serializers.HyperlinkedModelSerializer):
tickets = serializers.HyperlinkedRelatedField(many=True, view_name='ticket-detail')
class Meta:
model = MyUser
fields = ('id', 'nickname', 'email', 'tickets')
Ticket serializer:
class TicketSerializer(serializers.HyperlinkedModelSerializer):
user = serializers.HyperlinkedRelatedField(view_name='myuser-detail')
liked = serializers.Field(source='liked')
class Meta:
model = Ticket
fields = ('id', 'user', 'word', 'transcription', 'translation', 'liked', 'created', 'updated')
You can use a SerializerMethodField to customize it. Something like this:
class UserSerializer(serializers.HyperlinkedModelSerializer):
tickets = serializers.SerializerMethodField('get_tickets')
def get_tickets(self, obj):
return "http://127.0.0.1:8000/users/%d/tickets" % obj.id
class Meta:
model = MyUser
fields = ('id', 'nickname', 'email', 'tickets')
I hard-wired the URL in there for brevity, but you can do a reverse lookup just as well. This basically just tells it to call the get_tickets method instead of the default behavior in the superclass.
For the record, here is an example of the full solution based on Joe Holloway's answer:
from rest_framework.reverse import reverse
class WorkProjectSerializer(serializers.CustomSerializer):
issues = drf_serializers.SerializerMethodField()
def get_issues(self, obj):
request = self.context.get('request')
return request.build_absolute_uri(reverse('project-issue-list', kwargs={'project_id': obj.id}))
class Meta:
model = WorkProject
fields = '__all__'