How to make a POST request and use the ID of the author?
I have this model Game and I have a working GET method and would like to create new Game objects in my database by using a POST request.
I have Game like this:
class Game(models.Model):
owner = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL)
name = models.CharField(max_length=255)
...
I have a view like this:
class GameDetail(APIView):
def get_object(self, game_slug):
try:
return Game.objects.filter(slug=game_slug)
except Game.DoesNotExist:
raise Http404
def get(self, request, game_slug, format=None):
game = self.get_object(game_slug).first()
serializer = GameSerializer(game)
return Response(serializer.data)
def post(self, request, format=None):
game_data = request.data
game_data['owner'] = { 'username': 'admin', 'email': 'admin@example.com'} // just to test
serializer = GameSerializer(data=game_data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
If I send the server a POST request with the following body:
{
"name": "Posted game",
... // ommiting owner field
}
It looks like the server thinks I want to create a new user, but I just want the game to be associated with the userId
{
"owner": {
"username": [
"user with this username already exists."
]
}
}
If instead I set the game_data['owner'] to the userID directly it complains that it's expecting a dictionary.
How can I make it so that when I GET a game I see the owner (username and email) and when I post it can just add the logged in user?
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ("username", "email")
class GameSerializer(serializers.ModelSerializer):
owner = UserSerializer()
class Meta:
model = Game
fields = (
"name",
"owner",
...
)
Sometimes the best solution is to create 2 serializers and use the right one for the right endpoint:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ("username", "email")
class ReadGameSerializer(serializers.ModelSerializer):
owner = UserSerializer()
class Meta:
model = Game
fields = (
"name",
"owner",
...
)
class WriteGameSerializer(serializers.ModelSerializer):
class Meta:
model = Game
fields = (
"name",
"owner",
...
)
And then update your view:
class GameDetail(APIView):
def get_object(self, game_slug):
try:
return Game.objects.filter(slug=game_slug)
except Game.DoesNotExist:
raise Http404
def get(self, request, game_slug, format=None):
game = self.get_object(game_slug).first()
serializer = ReadGameSerializer(game)
return Response(serializer.data)
def post(self, request, format=None):
game_data = request.data
game_data['owner'] = request.user # if the user is authenticated
serializer = WriteGameSerializer(data=game_data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Another solution would be to make the nested serializer writable, but out of experience this solution is more difficult to maintain.
There area many ways to associate, If you want to associate the logged in user with Game, than pass request in serializer
...
def post(self, request, format=None):
serializer = GameSerializer(data=request.data, context={"request": requeset})
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Modify your serializer,
serializers.CharField(default=serializers.CurrentUserDefault()) this will automatically get your user from request and will associate with owner field in Game Model
Override the to_representation method to manipulate the response data
class GameSerializer(serializers.ModelSerializer):
owner = serializers.CharField(default=serializers.CurrentUserDefault())
class Meta:
model = Game
fields = (
"name",
"owner",
...
)
def to_representation(self, instance):
representation = super().to_representation(instance)
representation['owner'] = {"username":instance.owner.username, "email": instance.owner.email}
return representation