How to post array data in DRF - django

When I post to array json like {"productId":[1, 2, 3]}.
I got errors
Cannot assign "[<Product: Short Sleeve>, <Product: Short Sleeve>, <Product: Short Sleeve>]": "FittingCartItem.productId" must be a "Product" instance.
I already try add many=True argument in get_serializer function.
I don't know how solve this problem...
serializers.py
class ItemListSerializer(serializers.ModelSerializer):
product = ProductSerializer(source='productId', read_only=True)
memberId = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), write_only=True, required=False)
productId = serializers.PrimaryKeyRelatedField(queryset=Product.objects.all(), write_only=True, many=True)
is_live = serializers.BooleanField(default=True)
class Meta:
model = FittingCartItem
fields = '__all__'
views.py
class ItemListView(generics.ListCreateAPIView):
serializer_class = ItemListSerializer
queryest= FittingCartItem.objects.all()
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
serializer.save()
headers = self.get_success_headers(serializer.data, many=True)
return Response(serializer.data, status=status.HTTP_201_CREATED,
headers=headers)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
models.py
class FittingCartItem(models.Model):
memberId = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='fittingcarts')
productId = models.ForeignKey(Product, on_delete=models.CASCADE)
created_time = models.DateTimeField(auto_now_add=True)
is_live = models.BooleanField(default=True)

In yout serializer, you want your product to be serialized by ProductSerializer, which causes your serializer to expect "Product" instances instead of productIds in your JSON input.
What you need is to tell yout serializer to expect products as productIds, which is possible by using PrimaryKeyRelatedField.
You need to delete:
product = ProductSerializer(source='productId', read_only=True)
This will solve your problem because DRF automatically acts ForeignKeys in models as PrimaryKeyRrlatedFields. But if productId was not defined as ForeignKey, but instead ManyToMany relation etc. You need to add it as below:
productId = PrimaryKeyRelatedField(many=True, read_only=True)

Related

Querying and Filtering related models in DRF

I have Contact model to list the followers of an User object, I try to filter the contacts of a User but I still could not manage get a correct queryset. My Contact model is simple with two ForeignKey:
class Contact(models.Model):
user_from = models.ForeignKey(User,related_name='rel_from_set', on_delete=models.CASCADE,)
user_to = models.ForeignKey(User,related_name='rel_to_set', on_delete=models.CASCADE,)
def __str__(self):
return '{} follow {}'.format(self.user_from, self.user_to)
I have created serializers for User and Contact:
##Contact Serializer
class ContactsSerializer(serializers.ModelSerializer):
user_from = serializers.StringRelatedField(read_only=True)
user_to = serializers.StringRelatedField(read_only=True)
class Meta:
model = Contact
fields = ["user_from", "user_to"]
##UserSerializer
class UserInformationSerializer(serializers.ModelSerializer):
followers = ContactsSerializer(many=True, read_only=True)
class Meta:
model = User
fields = ['first_name', 'last_name', 'followers']
​
And try to make a query through views:
class FollowerListView(APIView):
queryset = Contact.objects.all()
serializer_class = ContactsSerializer
lookup_field = "username"
def get(self, request, format=None, slug=None):
kwarg_username = self.kwargs.get("slug")
user = User.objects.filter(is_active=1).filter(username=kwarg_username)
print(user.username)
contacts = Contact.objects.filter(user_to=user.id)
serializer = ContactsSerializer(contacts)
return Response(serializer.data)
Now I get error message:
AttributeError at /api/member/ytsejam/followers/
'QuerySet' object has no attribute 'username'
print(user.username)
If i try print(user) I can see the user an Object.
Can you guide me how to correct?
Thanks
filter will always return a queryset. If you expect to retrieve one single item, use get.
So that it looks like that:
def get(self, request, format=None, slug=None):
kwarg_username = self.kwargs.get("slug")
user = User.objects.filter(is_active=1).get(username=kwarg_username)
print(user.username)
contacts = Contact.objects.filter(user_to=user.id)
serializer = ContactsSerializer(contacts)
return Response(serializer.data)
You could, of course, do this on one take:
User.objects.get(is_active=1, username=kwarg_username)
But beware, if there are two rows in your model that would satisfy this call, Django will throw an error. Best make sure that the username has a unique constraint.

django.db.utils.IntegrityError: in POST request

I am creating a post function to save data in a model, Here's the code:
Views.py
class take_quizapi(CreateAPIView):
def post(self, request,pk, *args, **kwargs):
supplier = request.user.supplier
data = request.data.copy()
data["supplier_id"] = supplier.user_id
data["score"] = 0
data["quiz"] = pk
print("data is", data)
serializer = takenquizSerializer(data=data)
if serializer.is_valid():
serializer.save()
print("Serializer data", serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
when I call the API from postman it says:
django.db.utils.IntegrityError: null value in column "supplier_id" violates not-null constraint
But I am clearly providing the data["supplier_id"] = supplier.user_id.
What is it that I am doing wrong here ?
Serializers.py
class takenquizSerializer(serializers.ModelSerializer):
supplier = ReadOnlyField(source='supplier.supplier_fname')
class Meta:
model = TakenQuiz
fields = "__all__"
Models.py
class TakenQuiz(models.Model):
supplier = models.ForeignKey(Supplier, on_delete=models.CASCADE, related_name='taken_quizzes')
quiz = models.ForeignKey(Quiz, on_delete=models.CASCADE, related_name='taken_quizzes')
score = models.FloatField()
date = models.DateTimeField(auto_now_add=True)
least_bid = models.IntegerField(default=0)
confirmed = models.CharField(max_length=100, default='Not Confirmed')
supplier is a read only field. So there's no point in adding it to the data dict, since it will never be used.
You shouldn't be modifying the post data anyway - there's a reason that is immutable. Instead pass it in when saving:
serializer = takenquizSerializer(data=request.data)
if serializer.is_valid():
serializer.save(supplier_id=supplier.user_id, score=0, quiz=pk)

Adding a request.user in a ManyToMany field

I am trying to add a request.user to a manytomany field to create an item. But I receive this error:
TypeError at /api/v1/movies/
'User' object is not iterable
Models.py
class Movie(models.Model):
class Movie(models.Model):
title = models.CharField(max_length=100)
genre = models.CharField(max_length=100)
year = models.IntegerField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
owners = models.ManyToManyField('auth.User', related_name='movies')
views.py
# Create a new movie
def post(self, request):
serializer = MovieSerializer(data=request.data)
if serializer.is_valid():
serializer.save(owners=request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Per comment and updated question, the desired behavior is multiple users. Assuming that your MovieSerializer is a subclass of ModelSerializer and that you have used the default (PrimaryKeyRelatedField) binding for the owners serializer field, you should be able to add the requestor in the initial data:
def post(self, request):
serializer = MovieSerializer(data=dict(request.data, owners=[request.user.id]))
if serializer.is_valid():
serializer.save()
...
NOTE: since owners is a ManyToMany relation, you need to stick the user in an array, and for serialization this needs to be the pk of the user, not the object.

DRF serialize and save model with user FK

models.py
class UserContentItem(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=200)
created_date = models.DateTimeField(default=timezone.now)
views.py
class UserContentItemView(APIView):
permission_classes = (permissions.IsAuthenticatedOrReadOnly, )
def post(self, request, format=None):
data = request.data
data['owner'] = request.user.id
serializer = UserContentItemSerializer(data=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)
serializers.py
class UserContentItemSerializer(serializers.ModelSerializer):
class Meta:
model = UserContentItem
fields = ('id', 'owner', 'title', 'created_date')
I am building an API with Django Rest Framework and simple jwt. I want to allow authenticated users to POST a new UserContentItem that has a FK dependency on the User but the User is not part of the POST payload. The only way I've been able to figure out how to do this is as above, adding the request.user.id to the request data before passing it to the serializer. Is there a better way to serialize the UserContentItem and achieve the same goal?
I think you can try like this using CurrentUserDefault:
class UserContentItemSerializer(serializers.ModelSerializer):
owner = serializers.PrimaryKeyRelatedField(
read_only=True,
default=serializers.CurrentUserDefault()
)
class Meta:
model = UserContentItem
fields = ('id', 'owner', 'title', 'created_date')
And in view, pass request as context to serializer:
serializer = UserContentItemSerializer(data=data,context={'request':request})
Also you don't need to pass user id with data.
Your approach is good even you can follow these
serializer.save(owner=self.request.user)

ModelViewSet - Selectively hide fields?

I have an Instructor model, which has a many to many field to a Client model. (Instructor.clients)
The model:
class InstructorProfile(models.Model):
'''Instructor specific profile attributes
'''
# Fields
office_number = models.CharField(max_length=30, blank=True, null=True)
location = models.CharField(max_length=30)
last_updated = models.DateTimeField(auto_now=True, editable=False)
# Relationship Fields
user = models.OneToOneField(settings.AUTH_USER_MODEL,
related_name="instructor_profile",
on_delete=models.CASCADE)
clients = models.ManyToManyField('ClientProfile', blank=True)
My serializer is currently:
class InstructorProfileSerializer(serializers.ModelSerializer):
class Meta:
model = models.InstructorProfile
fields = '__all__'
And viewset:
class InstructorProfileViewSet(viewsets.ModelViewSet):
"""ViewSet for the InstructorProfile class"""
queryset = models.InstructorProfile.objects.all()
serializer_class = serializers.InstructorProfileSerializer
permission_classes = [permissions.IsAuthenticated]
I'd like to prevent access to the clients field to everyone except the user which Instructor belongs to (available in the Instructor.user model field).
How can I achieve this?
Add this to your InstructorProfileViewSet:
...
def get_queryset(self):
if hasattr(self.request.user, 'instructor_profile'):
return models.InstructorProfile.objects.filter(user=self.request.user)
else:
return models.InstructorProfile.objects.none()
... if I guessed your InstructorProfile model correctly.
One way to do this is to change the list method to set the client=None where needed. This way you would preserve the response structure. It would be something like this:
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
for i in serializer.data:
if i['user'] != request.user.pk:
i['client'] = None
return Response(serializer.data)