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.
Related
I have the following model + serializer where I send a post request to create a new model instance. The user sending the post request is related to a Company which I want to pass as related model instance to the serializer.
But how to actually define this instance and attach it to the serializer instance within the post view?
# views.py
class OfferList(APIView):
"""
List all Offers or create a new Offer related to the authenticated user
"""
def get(self, request):
offers = Offer.objects.filter(company__userprofile__user=request.user)
serializer = OfferSerializer(offers, many=True)
return Response(serializer.data)
def post(self, request):
serializer = OfferSerializer(data=request.data)
# Add related company instance
company = Company.objects.get(userprofile__user=request.user)
serializer['company'] = company # this doesn't work
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)
# offer/models.py
class Offer(models.Model):
"""
A table to store Offer instances
"""
# Relations
company = models.ForeignKey(Company, on_delete=models.CASCADE)
..
# serializers.py
class OfferSerializer(serializers.ModelSerializer):
class Meta:
model = Offer
fields = '__all__'
user/models.py
class UserProfile(models.Model):
"""
Extends Base User via 1-1 for profile information
"""
# Relations
user = models.OneToOneField(User, on_delete=models.CASCADE)
company = models.ForeignKey(Company, on_delete=models.CASCADE, null=True)
One simple way is to pass your company instance through your serializer. So maybe changing your post method to something like:
from rest_framework.generics import get_object_or_404
def post(self, request):
serializer = OfferSerializer(data=request.data)
# Add related company instance
if serializer.is_valid():
company = get_object_or_404(Company, userprofile__user=request.user) # raising 404 exception if related company does not exist,
# and you're sure that there is one and only one company for this user not more!
serializer.save(company=company)
return Response(serializer.data,
status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
and then change your serializer fields to exclude company field from them (because you are already sending this data through your serializer):
class OfferSerializer(serializers.ModelSerializer):
class Meta:
model = Offer
fields = ["some_field", "other_field"] # Do not include company in your fields.
# also note that since I didn't know your Offer's fields I used ["some_field", "other_field"] for fields
hope this solves your problem.
Hmm, I think the use of the context for serializers will be a nice way to solve your case.
# serializers.py
class OfferSerializer(serializers.ModelSerializer):
class Meta:
model = Offer
# now that we do not want company from the request body
exclude = ["company"]
def create(self, validated_data):
# add company to the validated data from the context
# we can feed the context from the APIView
validated_data["company"] = self.context.get("company", None)
...
return super().create(validated_data)
# views.py
class OfferList(APIView):
def post(self, request):
# imo, we do not have to query for UserProfile
# if the company is assigned to the UserProfile instance for the requestor
# then, one2one relation can give us this
context = {"company": request.user.userprofile.company}
serializer = OfferSerializer(data=request.data, context=context)
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)
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)
So I have a view that accepts serialized Album object and assign the owner to current user
class AlbumListViewer(APIView):
def post(self, request, format = None):
request.data['user_id'] = request.user.id
serializer = AlbumSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(status.HTTP_201_CREATED)
return Response(status = status.HTTP_400_BAD_REQUEST)
The error I get is
null value in column "user_id" violates not-null constraint
My Serializer looks like this
class AlbumSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Album
fields = ('id','album_name', 'date_created','user')
And finally my Model looks like this
class Album(models.Model):
user = models.ForeignKey(User)
album_name = models.CharField(max_length=30)
date_created = models.DateTimeField(editable=False, auto_now_add=True)
I have tried assigning a User ID in the JSON data but it is not being recognized by the serializer, anyway to assign this serialized object and give it a owner before saving?
Your have some problems in your AlbumListViewer. Do not try to add to the validatated_data anything. Instead pass on partial=True to the serializer, and after validation, use the save() method to save the missing values.
class AlbumListViewer(APIView):
def post(self, request, format = None):
serializer = AlbumSerializer(data=request.data, partial=True)
if serializer.is_valid():
user = request.user
serializer.save(user=user)
return Response(status=status.HTTP_201_CREATED)
return Response(status = status.HTTP_400_BAD_REQUEST)
Short description: I have a problem with ModelSerializer. It is not filling id field of object on save.
Useful details.
My model:
class Platform(models.Model):
identifier = models.CharField(max_length=32, unique=True)
description = models.TextField()
My serializer:
class PlatformSerializer(serializers.ModelSerializer):
identifier = serializers.CharField(required=True, max_length=32)
description = serializers.CharField(max_length=100000, required=False)
class Meta:
model=Platform
fields=('id', 'identifier', 'description',)
read_only_fields=('id',)
And APIView:
class PlatformView(APIView):
def post(self, request, id_platform=None, format=None):
serializer = PlatformSerializer(data=request.DATA)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(form_error(u"Ошибка при создании КА: ", serializer.errors), status=status.HTTP_400_BAD_REQUEST)
The line serializer.save() returns Platform object with id==None. Is it a bug or am I missing something?
I think this serializer class should work:
class PlatformSerializer(serializers.ModelSerializer):
class Meta:
model=Platform
ModelSerializer:
By default, all the model fields on the class will be mapped to corresponding serializer fields.
Ouch. I'm sorry for this question.
Somebody created pk field for this table as integer with default value from sequence (without owning this sequence).
Django performs pk selection as SELECT CURRVAL(pg_get_serial_sequence('tablename','id')). But pg_get_serial_sequence returns Null.
I don't know why django-rest-framework doesn't performselect ... returning id;`. I'll try to fix the database.
I am using DRF to expose some API endpoints.
# models.py
class Project(models.Model):
...
assigned_to = models.ManyToManyField(
User, default=None, blank=True, null=True
)
# serializers.py
class ProjectSerializer(serializers.ModelSerializer):
assigned_to = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), required=False, many=True)
class Meta:
model = Project
fields = ('id', 'title', 'created_by', 'assigned_to')
# view.py
class ProjectList(generics.ListCreateAPIView):
mode = Project
serializer_class = ProjectSerializer
filter_fields = ('title',)
def post(self, request, format=None):
# get a list of user.id of assigned_to users
assigned_to = [x.get('id') for x in request.DATA.get('assigned_to')]
# create a new project serilaizer
serializer = ProjectSerializer(data={
"title": request.DATA.get('title'),
"created_by": request.user.pk,
"assigned_to": assigned_to,
})
if serializer.is_valid():
serializer.save()
else:
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
return Response(serializer.data, status=status.HTTP_201_CREATED)
This all works fine, and I can POST a list of ids for the assigned to field. However, to make this function I had to use PrimaryKeyRelatedField instead of RelatedField. This means that when I do a GET then I only receive the primary keys of the user in the assigned_to field. Is there some way to maintain the current behavior for POST but return the serialized User details for the assigned_to field?
I recently solved this with a subclassed PrimaryKeyRelatedField() which uses the id for input to set the value, but returns a nested value using serializers. Now this may not be 100% what was requested here. The POST, PUT, and PATCH responses will also include the nested representation whereas the question does specify that POST behave exactly as it does with a PrimaryKeyRelatedField.
https://gist.github.com/jmichalicek/f841110a9aa6dbb6f781
class PrimaryKeyInObjectOutRelatedField(PrimaryKeyRelatedField):
"""
Django Rest Framework RelatedField which takes the primary key as input to allow setting relations,
but takes an optional `output_serializer_class` parameter, which if specified, will be used to
serialize the data in responses.
Usage:
class MyModelSerializer(serializers.ModelSerializer):
related_model = PrimaryKeyInObjectOutRelatedField(
queryset=MyOtherModel.objects.all(), output_serializer_class=MyOtherModelSerializer)
class Meta:
model = MyModel
fields = ('related_model', 'id', 'foo', 'bar')
"""
def __init__(self, **kwargs):
self._output_serializer_class = kwargs.pop('output_serializer_class', None)
super(PrimaryKeyInObjectOutRelatedField, self).__init__(**kwargs)
def use_pk_only_optimization(self):
return not bool(self._output_serializer_class)
def to_representation(self, obj):
if self._output_serializer_class:
data = self._output_serializer_class(obj).data
else:
data = super(PrimaryKeyInObjectOutRelatedField, self).to_representation(obj)
return data
You'll need to use a different serializer for POST and GET in that case.
Take a look into overriding the get_serializer_class() method on the view, and switching the serializer that's returned depending on self.request.method.