Reputation: 35
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
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