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)
Related
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)
In the serializer used to create a model I want to rename my model field to (field_name)_id so it's clearer for API consumers that this field is an ID field. The model also has a unique_together constraint on some fields. However when validation runs in the serializer, it fails with a KeyError that the field does not exist:
...rest_framework/utils/serializer_helpers.py", line 148, in __getitem__
return self.fields[key]
KeyError: 'question'
Is there a simple way to get this to work? Minimal example code below.
Model
class MyModel(Model):
question = ForeignKey('uppley.Question', null=False, on_delete=PROTECT)
user = ForeignKey('catalystlab.User', null=False, on_delete=PROTECT)
class Meta:
unique_together = ('question', 'user',)
Serializer
class MyCreateSerializer(ModelSerializer):
question_id = PrimaryKeyRelatedField(
write_only=True,
source='question',
queryset=Question.objects.all(),
)
user_id = PrimaryKeyRelatedField(
write_only=True,
source='user',
queryset=User.objects.all(),
)
class Meta:
model = MyModel
fields = ('question_id', 'user_id',)
test.py - test for demonstration purposes
question = QuestionFactory()
user = UserFactory()
data = {
'question_id': question.id,
'user_id': user.id,
}
serializer = MyCreateSerializer(data=data, write_only=True)
is_valid = serializer.is_valid(raise_exception=True) #KeyError exception raised here.
Previously with DRF 3.10.3 this all worked fine, however with 3.11.0 this now throws a KeyError as mentioned above.
What I have tried
Removing the source field on PrimaryKeyRelatedField for user_id and question_id in the Serializer actually results in bypassing the unique_together validation in DRF and the KeyError is avoided. However the validated data is not mapped back to the original field names (user and question). In this case we have to manually change the keys back to their original names before we can create an instance of the Model from the validated data.
Is there a better way to do this?
You can make a custom serializer like :-
class MyCreateSerializer(serializers.Serializer):
question_id = serializers.PrimaryKeyRelatedField(
write_only=True,
queryset=Question.objects.all(),
)
user_id = PrimaryKeyRelatedField(
write_only=True,
queryset=User.objects.all(),
)
and make custom create function in it for creating object. like :-
def create(self, validated_data):
try:
question = validated_data.get('question_id')
user = validated_data.get('user_id')
instance = MyModel.objects.create(question=question, user=user)
except TypeError:
raise TypeError("Something went wrong while creating objects")
return instance
I am trying to update or create the following model:
class Profile(models.Model):
user = models.OneToOneField(AUTH_USER_MODEL, on_delete=models.CASCADE)
canMakeEvent = models.BooleanField(default=False)
with the serializer:
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = '__all__'
and view:
def post(self, request):
answer, created = Profile.objects.update_or_create(
user=request.user, canMakeEvent = request.data['canMakeEvent'])
return Response()
I understand the response isn't correct but the code to update_or_create is what I'm worried about primarily. The console outputs the following:
UNIQUE constraint failed: event_profile.user_id
You need to add defaults argument to perform search only by user_id not by combination of (user_id, canMakeEdit):
answer, created = Profile.objects.update_or_create(
user=request.user, defaults={'canMakeEvent': request.data['canMakeEvent']})
This will allow to update existing profile of selected user if it exists or create new otherwise.
I am using the django rest framework and I have a very simple model of Posts for a particular user which I have serialised in the following manner.
Serializers.py
class PostSerializer(serializers.HyperlinkedModelSerializer):
image = serializers.ImageField(max_length=None, use_url=True)
question = serializers.CharField(required=False)
ayes = serializers.CharField(required=False)
nays = serializers.CharField(required=False)
neutrals = serializers.CharField(required=False)
class Meta:
model = Posts
fields = ('user','question', 'image','ayes', 'nays', 'neutrals')
My models.py is as follows
class Posts(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
question = models.TextField(max_length=500, blank=True)
image = models.ImageField('optionalImage', upload_to='images/posts/', default='/images/posts/blank.png')
ayes = models.TextField(max_length=200, default=0)
nays = models.TextField(max_length=200, default=0)
neutrals = models.TextField(max_length=200, default=0)
When I tried posting to this I kept getting NOT NULL Integrity constraint error of user_id. Hence I added context={'request': request}) to the serializer which ends up giving me the following error:
Could not resolve URL for hyperlinked relationship using view name "user-detail". You may have failed to include the related model in your API, or incorrectly configured the lookup_field attribute on this field.
My views.py is as follows:
views.py
#permission_classes((IsAuthenticated, ))
class PostsView(generics.ListCreateAPIView):
queryset = Posts.objects.all()
serializer_class = PostSerializer
def get(self, request, format=None):
snippets = Posts.objects.filter(pk=request.user.id)
serializer = PostSerializer(snippets, many=True,context={'request': request})
return Response(serializer.data)
def post(self, request, format=None):
posts = PostSerializer(data=request.data,context={'request': request})
if posts.is_valid():
posts.save()
return Response("YOLO", status=status.HTTP_201_CREATED)
else:
return Response(posts.errors, status=status.HTTP_400_BAD_REQUEST)
All other fields of mine have posted correctly when I set default=0 in my model for user. I am unable to submit the foreign key user which needs to be saved on every post. What am I doing wrong here? Am I following the correct method?
Since you don't want to send your user, you should remove it from the serializer's field.
Next, you want to set the post's user to the current user. To achieve that, you want to pass the request.user to the serializer's data by changing the save method to:
posts.save(user=request.user)
It's explained in the documentation and in the tutorial
I am using DRF to expose some API endpoints.
# models.py
class Project(models.Model):
...
assigned_to = models.ManyToManyField(
User, default=None, blank=True, null=True
)
# serializers.py
class ProjectSerializer(serializers.ModelSerializer):
assigned_to = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), required=False, many=True)
class Meta:
model = Project
fields = ('id', 'title', 'created_by', 'assigned_to')
# view.py
class ProjectList(generics.ListCreateAPIView):
mode = Project
serializer_class = ProjectSerializer
filter_fields = ('title',)
def post(self, request, format=None):
# get a list of user.id of assigned_to users
assigned_to = [x.get('id') for x in request.DATA.get('assigned_to')]
# create a new project serilaizer
serializer = ProjectSerializer(data={
"title": request.DATA.get('title'),
"created_by": request.user.pk,
"assigned_to": assigned_to,
})
if serializer.is_valid():
serializer.save()
else:
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
return Response(serializer.data, status=status.HTTP_201_CREATED)
This all works fine, and I can POST a list of ids for the assigned to field. However, to make this function I had to use PrimaryKeyRelatedField instead of RelatedField. This means that when I do a GET then I only receive the primary keys of the user in the assigned_to field. Is there some way to maintain the current behavior for POST but return the serialized User details for the assigned_to field?
I recently solved this with a subclassed PrimaryKeyRelatedField() which uses the id for input to set the value, but returns a nested value using serializers. Now this may not be 100% what was requested here. The POST, PUT, and PATCH responses will also include the nested representation whereas the question does specify that POST behave exactly as it does with a PrimaryKeyRelatedField.
https://gist.github.com/jmichalicek/f841110a9aa6dbb6f781
class PrimaryKeyInObjectOutRelatedField(PrimaryKeyRelatedField):
"""
Django Rest Framework RelatedField which takes the primary key as input to allow setting relations,
but takes an optional `output_serializer_class` parameter, which if specified, will be used to
serialize the data in responses.
Usage:
class MyModelSerializer(serializers.ModelSerializer):
related_model = PrimaryKeyInObjectOutRelatedField(
queryset=MyOtherModel.objects.all(), output_serializer_class=MyOtherModelSerializer)
class Meta:
model = MyModel
fields = ('related_model', 'id', 'foo', 'bar')
"""
def __init__(self, **kwargs):
self._output_serializer_class = kwargs.pop('output_serializer_class', None)
super(PrimaryKeyInObjectOutRelatedField, self).__init__(**kwargs)
def use_pk_only_optimization(self):
return not bool(self._output_serializer_class)
def to_representation(self, obj):
if self._output_serializer_class:
data = self._output_serializer_class(obj).data
else:
data = super(PrimaryKeyInObjectOutRelatedField, self).to_representation(obj)
return data
You'll need to use a different serializer for POST and GET in that case.
Take a look into overriding the get_serializer_class() method on the view, and switching the serializer that's returned depending on self.request.method.