Calling 'is_valid()' removes data from the serializer - python

When I am calling is_valid on my serializer, some of the data passed to the serializer is not getting saved. The files field is available in the serializer.initial_data, but does not get saved in serializer.validated_data. Any ideas?
Serializer:
class SomeSerializer(serializers.Serializer):
email = serializers.EmailField()
files = serializers.ListField(
child=serializers.FileField()
)
And the following view:
class SomeView(mixins.CreateModelMixin, generics.GenericAPIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = SomeSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
email = serializer.validated_data["email"]
files = serializer.validated_data.get("files")
#Do something here
return response

Related

Overriding create() for class based generic views

How can I perform some logic prior to saving a record within my generic view? I believe the actual saving logic occurs within super().create().
The request within create() looks like this
<QueryDict: {'csrfmiddlewaretoken': ['5WvMZnoBMCUjmlMBaacLnx6Pxt3jUDvHWHvo90ORumYrClkebcx7NJZpmWASRIyG'], 'user': ['1'], 'address': ['3E8ociqZa9mZUSwGdSmAEMAoAxBK3FNDcd']}>
view
class WalletListCreateAPIView(generics.ListCreateAPIView):
queryset = Wallet.objects.all()
serializer_class = WalletSerializer
def create(self, request, *args, **kwargs):
# Some logic here prior to saving
return super().create(request, *args, **kwargs)
For instance, I would like to create the value for balance instead of relying on the value from the request
class Wallet(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
address = models.CharField(max_length=34)
balance = models.DecimalField(default=0, max_digits=16, decimal_places=8)
slug = models.SlugField(max_length=34, blank=True, null=True)
This is the flow when you are saving your request data into model
Also please check the syntax
create -> perform_create -> serializer's create back to perform create then back to create
class WalletListCreateAPIView(generics.ListCreateAPIView):
queryset = Wallet.objects.all()
serializer_class = WalletSerializer
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
wallet = self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(
self.get_response_data(user),
status=status.HTTP_201_CREATED,
headers=headers,
)
def perform_create(self, serializer):
wallet = serializer.save(user=self.request.user) # if you want to change how you want to save from serializer to your model then you should override create method of serializer as I have shown below
wallet.balance = 30
wallet.save()
return wallet
# serializers.py
class WalletSerializer(serializers.ModelSerializer):
class Meta:
model = Wallet
fields = "__all__"
def create(self, validated_data):
# here in validated data you will receive your request data after validation If you want to discard any request value you can do here
balance = validated_data.pop("balance", None)
wallet = Wallet.objects.create(**validated_data)
return wallet
You can override model save method
class Wallet(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
address = models.CharField(max_length=34)
balance = models.DecimalField(default=0, max_digits=16, decimal_places=8)
slug = models.SlugField(max_length=34, blank=True, null=True)
def save(self, *args, **kwargs):
if not self.pk:
#You can write own logic here
self.balance = 10
super(Wallet, self).save(*args, **kwargs)
I understand your problem.
you want to override create method in generics.ListCreateAPIView.
so you have to define some method, here you want to create new record so you want to define post method and in post method you can override create method.
class WalletListCreateAPIView(generics.ListCreateAPIView):
queryset = Wallet.objects.all()
serializer_class = WalletSerializer
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
NOTE:
- as per your Wallet model address field is not blank, so you should have pass address. then run it.

How to override the update action in django rest framework ModelViewSet?

These are the demo models
class Author(models.Model):
name = models.CharField(max_lenght=5)
class Post(models.Model):
author = models.ForeignKey(Author, on_delete=models.CASCADE)
title = models.CharField(max_lenght=50)
body = models.TextField()
And the respective views are
class AuthorViewSet(viewsets.ModelViewSet):
queryset = Author.objects.all()
serializer_class = AuthorSerializer
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostStatSerializer
I am trying to perform an update/put action on PostViewSet and which is succesfull, but I am expecting different output. After successful update of Post record, I want to send its Author record as output with AuthorSerializer. How to override this and add this functionality?
You can override update method for this:
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostStatSerializer
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
# this will return autor's data as a response
return Response(AuthorSerializer(instance.parent).data)
I figured out some less code fix for my issue.
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostStatSerializer
def update(self, request, *args, **kwargs):
super().update(request, *args, **kwargs)
instance = self.get_object()
return Response(AuthorSerializer(instance.author).data)

Filtering by Foreign Key in ViewSet, django-rest-framework

I want my api to return certain objects from a database based on the foreign key retrieved from the url path. If my url looks like api/get-club-players/1 I want every player object with matching club id (in this case club.id == 1). I'm pasting my code down below:
models.py
class Club(models.Model):
name = models.CharField(max_length=25)
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True)
def __str__(self):
return self.name
class Player(models.Model):
name = models.CharField(max_length=30)
club = models.ForeignKey(Club, on_delete=models.SET_NULL, blank=True, null=True)
def __str__(self):
return self.name
serialziers.py
class ClubSerializer(serializers.ModelSerializer):
class Meta:
model = Club
fields = 'id', 'owner', 'name'
class PlayerSerializer(serializers.ModelSerializer):
class Meta:
model = Player
fields = 'id', 'name', 'offense', 'defence', 'club', 'position'
views.py, This is the part where I get the most trouble with:
class ClubViewSet(viewsets.ModelViewSet):
queryset = Club.objects.all()
serializer_class = ClubSerializer
class PlayerViewSet(viewsets.ModelViewSet):
queryset = Player.objects.all()
serializer_class = PlayerSerializer
class GetClubPlayersViewSet(viewsets.ViewSet):
def list(self, request):
queryset = Player.objects.all()
serializer = PlayerSerializer(queryset, many=True)
def retrieve(self,request, clubId):
players = Player.objects.filter(club=clubId, many=True)
if not players:
return JsonResponse({'error': "No query found!"})
else:
serializer = PlayerSerializer(players)
return Response(serializer.data)
urls.py
from rest_framework import routers
from django.urls import path, include
from .views import (GameViewSet, PlayerViewSet, ClubViewSet,
GetClubPlayersViewSet, create_club, set_roster)
router = routers.DefaultRouter()
router.register(r'clubs', ClubViewSet, basename="clubs")
router.register(r'players', PlayerViewSet, basename="players")
router.register(r'get-club-players', GetClubPlayersViewSet, basename="club-players")
urlpatterns = [
path('', include(router.urls)),
]
EDIT:
Now views.py looks like that:
class GetClubPlayersViewSet(viewsets.ViewSet):
queryset = Player.objects.all()
def list(self, request):
serializer = PlayerSerializer(self.queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, *args, **kwargs):
clubId = kwargs['get-club-players']
players = Player.objects.filter(club=clubId, many=True)
if not players:
return JsonResponse({'error': "No query found!"})
else:
serializer = PlayerSerializer(players)
return Response(serializer.data)
http://127.0.0.1:8000/api/get-club-players/ returns all of the player objects, but when I ad a clubId into url I get this error:
EDIT 2:
class GetClubPlayersViewSet(viewsets.ViewSet):
queryset = Player.objects.all()
def retrieve(self, request, *args, **kwargs):
queryParams = self.request.GET.get('abc')
if queryParams is None:
queryset = Player.objects.none()
else:
queryset = Player.objects.filter(club = queryParams)
serializer = PlayerSerializer(queryset)
return Response(serializer.data)
def list(self, request):
serializer = PlayerSerializer(self.queryset, many=True)
return Response(serializer.data)
You can get url parameters using kwargs attribute. You will need to modify the signature of your retrieve method for it.
def retrieve(self, request, *args, **kwargs):
clubId = kwargs['get-club-players']
players = Player.objects.filter(club=clubId, many=True)
....
EDIT
For the queryset error, it is due to DRF requiring either the queryset class attribute or implementation of get_queryset() function. In your case, you can get around it like this:
class GetClubPlayersViewSet(viewsets.ViewSet):
queryset = Player.objects.all()
def list(self, request):
serializer = PlayerSerializer(self.queryset, many=True)
So you can define your queryset like -
def get_queryset(self):
queryParams == self.request.GET.get('abc') # get queryparameter from url
if queryParams is None:
#queryset = anyModel.objects.all()
queryset = anyModel.objects.none()
else:
queryset = anyModel.objects.filter(anyProperty = queryParams)
return queryset
and your url will be like -
api/get-club-players/?abc=1
this abc can be id or any other property from the model.
Use this get_queryset logic in your retrieve method.
rest_framework.viewsets.ViewSet has an attribute named lookup_field which you can override. By default the value of lookup_field is id.
When adding the viewset in router, the lookup_field is added as the argument name in the url (e.g. /api/get-club-players/:id/).
You can either override the lookup_field of GetClubPlayersViewSet or access the correct kwargs key by changing
clubId = kwargs['get-club-players'] to clubId = kwargs['id']
Or a bit of both:
class GetClubPlayersViewSet(viewsets.ViewSet):
lookup_field = "clubId"
queryset = Player.objects.all()
# ....
def retrieve(self, request, *args, **kwargs):
clubId = kwargs[self.lookup_field]
players = Player.objects.filter(club=clubId, many=True)
if not players:
return JsonResponse({'error': "No query found!"})
else:
serializer = PlayerSerializer(players)
return Response(serializer.data)

How to parse and serialize an Abstract User in another model?

I've been trying to create a APIView for a post request that saves an image and retrieves the user value in the request to fill one of the fields of the model. However, I've been stuck as all I've been getting here was PlotImage has no user.
How do I pass the current user model in the request to a serializer ?
Models.py
class AppUser(AbstractUser):
email = models.EmailField(...)
....
class ImageModel(models.Model):
image = models.ImageField(...)
user = models.ForeignKey(AppUser,on_delete=models.CASCADE)
mix_id = models.IntegerField()
plot_id = models.IntegerField()
Serializer.py
def PlotImagesInputSerializer(serializers.ModelSerializer):
class Meta:
model = ImageModel
fields = ('plot_id','image','mix_id')
view.py
class MixImagesCreateView(APIView):
parser_classes = [MultiPartParser, FormParser]
permission_classes = (permissions.IsAuthenticated,)
def post(self, request, format=None):
serializer = PlotImagesInputSerializer(data=request.FILES, partial=True)
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)
You need to save the requested user in ImageModel inside your post method in view.py
Try as follows:
serializer.save(user=self.request.user)
Full code
class MixImagesCreateView(APIView):
parser_classes = [MultiPartParser, FormParser]
permission_classes = (permissions.IsAuthenticated,)
def post(self, request, format=None):
serializer = PlotImagesInputSerializer(data=request.FILES, partial=True)
if serializer.is_valid():
serializer.save(user=self.request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

How to get user by Token in django Rest

Using:
class PostSerializer(serializers.ModelSerializer):
author = ExtUserSerializer(required=False, allow_null=True)
class Meta:
model = Post
field = ('title','description','rating','author')
def create(self, validated_data):
return Post.objects.create(**validated_data)
and
#api_view(['GET'])
def post_list(request, format=None):
if request.method == 'GET':
posts = Post.objects.all()
serializer = PostSerializer(posts, many=True)
return Response(serializer.data)
How to modify Serializer and view to fill field author automatically

Categories