Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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!

like image 685
Matthias Scholz Avatar asked Jan 07 '16 17:01

Matthias Scholz


3 Answers

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)
like image 88
SimoV8 Avatar answered Oct 24 '22 04:10

SimoV8


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

like image 43
SukiCZ Avatar answered Oct 24 '22 04:10

SukiCZ


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

like image 2
Mirage Avatar answered Oct 24 '22 05:10

Mirage