Kacper Marchwant
Kacper Marchwant

Reputation: 35

Post Request to Django Rest APIView lacking user (token authentication)

I'm creating API for Twitter like app in Django and since I implemented token authentication (rest-auth) I have problem with creating new tweets as it throws:

{
    "author": [
        "This field is required."
    ]
}

I've tried CreateAPIView:

class TweetCreateAPIView(CreateAPIView):
    serializer_class = TweetModelSerializer
    permission_classes = (IsAuthenticated,)

    # I've also tried to add the author field mannualy
    def perform_create(self, serializer):
        serializer.save(author=self.request.user)

but it didn't work so I created custom post method:

class TweetCreateAPIView(APIView):
    permission_classes = (IsAuthenticated,)

    def post(self, request, format=None):

        serializer = TweetModelSerializer(data=request.data)

        if serializer.is_valid():
            serializer.save(author=request.user)

            return Response(serializer.data, status=status.HTTP_201_CREATED)

        return Response(serializer.errors,  status=status.HTTP_400_BAD_REQUEST)

but it still can't identify the user creating the tweet

Model:

class Tweet(models.Model):

    retweeted_by = models.ManyToManyField(
        TwitterUser, blank=True, related_name='retweets')

    commented_tweet = models.ManyToManyField(
        'self', related_name='comments', blank=True, symmetrical=False)

    author = models.ForeignKey(
        TwitterUser, on_delete=models.CASCADE, related_name='tweets')

    content = models.CharField(max_length=280)
    created_at = models.DateTimeField(auto_now_add=True)
    liked_by = models.ManyToManyField(
        TwitterUser, blank=True, related_name='likes')

    objects = TweetManager()

    def __str__(self):
        return self.content

    def get_comments(self):
        comments = Tweet.objects.filter(commented_tweet__pk=self.pk)
        return comments

    def get_retweets(self):
        retweets = TwitterUser.retweets(tweet__id=self.id)

    def get_absolute_url(self):
        return reverse('tweets:pk', kwargs={'pk': self.pk})

Serializer:

class TweetModelSerializer(serializers.ModelSerializer):

    likes_count = serializers.SerializerMethodField()
    already_liked = serializers.SerializerMethodField()

    def get_likes_count(self, tweet):
        return tweet.liked_by.all().count()

    def get_already_liked(self, tweet):
        user = None
        request = self.context.get("request")

        if request and hasattr(request, "user"):
            user = request.user

        if user is not None:
            if user in tweet.liked_by.all():
                return True
            else:
                return False
        else:
            pass

    class Meta:
        model = Tweet
        fields = [
            'id',
            'author',
            'commented_tweet',
            'content',
            'retweeted_by',
            'likes_count',
            'already_liked',
            'created_at',
        ]

Settings:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    # 3rd party
    'rest_framework',
    'rest_framework.authtoken',
    'rest_auth',
    'django.contrib.sites',
    'channels',
    'allauth',
    'allauth.account',
    'rest_auth.registration',
    'reset_migrations',
    'corsheaders',

]

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
    ],
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.TokenAuthentication',
    ),
    'DEFAULT_PAGINATION_CLASS':     'twitter.paginations.StandardResultsSetPagination',
    'TEST_REQUEST_DEFAULT_FORMAT': 'json',
}

Authentication itself works fine, including registration, so I don't think that it's a problem with tokens, yet I have no other idea where the bug is when even CreateAPIView doesn't work.

Upvotes: 2

Views: 1057

Answers (1)

Vaibhav Vishal
Vaibhav Vishal

Reputation: 7138

In your serializer you have author in the list of fields. Since it's a ModelSerializer, drf is picking up details for that field from the model, and in the model author is null = False by default, hence drf is making it a required field, since it can't be null. But since you want the author to be automatically the user who is making the request, you don't need that field to be editable. Hence make it a read only field in your serializer like this:

class TweetModelSerializer(serializers.ModelSerializer):
    ...
    class Meta:
        model = Tweet
        fields = ('id', 'author', 'commented_tweet', 'content', 'retweeted_by',
                  'likes_count', 'already_liked', 'created_at', )
        read_only_fields = ('author', )  # it's read only now, so drf go looking for it in POST data

Now you just need to override the perform_create method, no need to change the post method.

If you need even more customizations like superuser can edit authors but no one else, you can override the __init__ method your serializer and make fields read_only based on request.user which gets a bit complicated.

Also, you should consider using tuple instead of list for fields and read_only_fields to get minor performance boost.

Upvotes: 1

Related Questions