Saving a Serializer in Django - python

When I'm trying to save a model object (its name here is 'RSS) in the view's post() it doesn't get saved, how can I save the model instance 'rss' from the view's post()?
In the Serializers class:
class RSSSerializer(serializers.ModelSerializer):
class Meta:
model = RSS
fields = ('feed_url', 'website_url', 'description', 'title')
def create(self, validated_data):
rss = RSS(**validated_data)
rss.created_at = datetime.now()
rss.last_scan_time = '2001-01-01 00:00:00'
rss.id = None
return rss
In the View class:
class RSSList(APIView):
def post(self, request):
serializer = RSSSerializer(data=request.data)
if serializer.is_valid():
print("saving rss post")
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Your current create method instantiates a new RSS object but doesn't save it. Try this:
def create(self, validated_data):
rss = RSS.objects.create(**validated_data)
rss.created_at = datetime.now()
...
return rss.save()
More info: Django rest framework: override create() in ModelSerializer passing an extra parameter
http://www.django-rest-framework.org/api-guide/serializers/#saving-instances
If your object instances correspond to Django models you'll also want to ensure that these methods save the object to the database.

Related

Provide request data from view into serializer ModelViewSet Django

I try to make a custom list function inside ProductViewSet because I need to download an extra field - the highest product price in database. How can I provide the request argument from def list into serializer? I mean right now I get error 'NoneType' object has no attribute 'user' in this line: if request.user.is_authenticated.
So how can I fix it that he can read self.context.get('request') correctly?
class ProductViewSet(viewsets.ModelViewSet):
...
def list(self, request):
queryset = Product.objects.all()
serializer = ProductSerializer(queryset, many=True)
return Response(serializer.data)
class ProductSerializer(serializers.ModelSerializer):
...
class Meta:
model = Product
...
def get_is_followed(self, obj):
request = self.context.get('request')
if request.user.is_authenticated:
return Product.objects.filter(id=obj.id, followers=request.user.id).exists()
I want to work it like a default ModelViewSet list but with an extra field.
You have used ModelViewSet which already has a serializer_class attribute. You can simply provide the serializer_class and the serializer context is taken care by DRF itself. So, instead of writing
serializer = ProductSerializer(queryset, many=True) you should write in this way:
class ProductViewSet(viewsets.ModelViewSet):
serializer_class = ProductSerializer
queryset = Product.objects.all()
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
If your concern is only the request object as a context to the serializer then there is no need to override the list method in the ProductViewSet. By default three contexts (request, format and view) are passed to the serializer but if you need extra contexts then you can always override def get_serializer_context(self) method in the view.
This is the default signature of the get_serializer_context:
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'request': self.request,
'format': self.format_kwarg,
'view': self
}

How to allow the update of only 1 field?

So Im doing an app where the user can upload photos, each photo has a user, image, description, and other fields.
When the user creates a new photo, the image is required, but when the user wants to update it, you only can change the description. I'm stuck with this, first tried with update (put) but it's clearly not the method to use, now I'm trying with partial_update but I can't get it right.
This is what I have right now, it gives me the error: TypeError: Object of type Photo is not JSON serializable.
Maybe it's a completely wrong way of doing it.
View:
class PhotoViewSet(mixins.RetrieveModelMixin,
mixins.CreateModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
viewsets.GenericViewSet):
""" PhotoViewSet
Handle upload (create) and delete of photos.
"""
queryset = Photo.objects.all()
serializer_class = PhotoModelSerializer
def perfom_create(self, serializer):
""" Upload a new photo. """
serializer.save()
def perform_destroy(self, instance):
""" Delete a photo. """
return super().perform_destroy(instance)
def retrieve(self, request, *args, **kwargs):
""" Retrieve photo information. """
response = super(PhotoViewSet, self).retrieve(request, *args, **kwargs)
data = {
'photo': response.data,
}
response.data = data
return response
def partial_update(self, request, pk=None):
serializer = UpdateDescriptionSerializer(data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
data = serializer.save()
return Response(data=data, status=status.HTTP_202_ACCEPTED)
serializer:
class UpdateDescriptionSerializer(serializers.ModelSerializer):
""" Update description serializer. """
description = serializers.CharField(max_length=255)
class Meta:
""" Meta class. """
model = Photo
fields = ('description',)
You should pass the instance that you wish to update. Furthermore you should not return the outcome of the .save() method, but the data of the serializer, so:
def partial_update(self, request, pk=None):
instance = self.get_object()
serializer = UpdateDescriptionSerializer(
instance,
data=request.data,
partial=True
)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(data=serializer.data, status=status.HTTP_202_ACCEPTED)
If you want to return all the details of the updated item, you can work with another serializer that will create the data for the response, so:
def partial_update(self, request, pk=None):
instance = self.get_object()
serializer = UpdateDescriptionSerializer(
instance,
data=request.data,
partial=True
)
serializer.is_valid(raise_exception=True)
serializer.save()
serializer2 = PhotoModelSerializer(instance)
return Response(data=serializer2.data, status=status.HTTP_202_ACCEPTED)
You should use 2 different serializers, of which 1 of them is dedicated to update your object e.g. PhotoSerializer and UpdatePhotoSerializer.
What I would suggest is to override the get_serializer_class() method as follows:
def get_serializer_class(self):
if self.request.method == "PATCH": # add PUT too if you wish
return UpdatePhotoSerializer
return PhotoSerializer
Another tip, using generic views is much cleaner in your views as per documentation.

how to update model object's , only one field data when doing serializer.save() in django rest framework?

so heres my serializer class:
class HeroSerializer(serializers.ModelSerializer):
class Meta:
model=Hero
fields=['id','name','secret_identity']
and my view :
#api_view(['PUT', ])
def api_hero_update(request, name):
try:
character = Hero.objects.get(name=name)
except:
return Response(status=status.HTTP_404_NOT_FOUND)
serializer = serializers.HeroSerializer(character, data=request.data)
message={}
if serializer.is_valid():
serializer.save()
print(serializer.data)
message["success"]="Update Successful"
return Response(data=message)
return Response(serializer.errors,status=status.HTTP_400_BAD_REQUEST)
Let a model object has fields name and secret_identity. and i want to update its name only but the line
serializer = serializers.HeroSerializer(character, data=request.data)
doesnt allow me to update only , one field . how to update only one field?
you have to add partial=True attribute to serializer object. i.e. use this
serializer = serializers.HeroSerializer(character, data=request.data, partial=True)
By overriding the serializers update method:
class HeroSerializer(serializers.ModelSerializer):
class Meta:
model=Hero
fields=['id','name','secret_identity']
def update(self, instance, validated_data):
instance.secret_identity = validated_data.get('your field name', 'defaultvalue')
instance.save()
return instance
Don't forget to save your model inside the update method, or the changes wont be persistent in the database. If you need more information, this is described in the DRF docs.

How to override POST method for bulk adding - Django Rest Framework

I'm writing a REST API using Django Rest Framework and I would like that one of my routes accepts bulk adding on POST method, to create multiple objects. Others methods (GET, PUT, PATCH, DELETE) will still remain the same, accepting only one at a time.
What I have so far is below and it's currently working just fine for posting one at a time.
In my urls.py:
path('book', books.BookViewSet.as_view()),
books.py:
class BookViewSet(viewsets.ModelViewSet):
serializer_class = BookSerializer
queryset = Book.objects.all()
permission_classes = (IsAuthenticated, )
serializer.py:
class BookSerializer(serializers.ModelSerializer):
def create(self, validated_data):
# I assume this is the method to be overridden to get this
class Meta:
model = Book
fields = ('id', 'name', 'author_id', 'page_number', 'active')
Serializer create method, unfortunatelly creates data object by object.You can override create method of ModelViewSet and after validation use bulk_create method.
def create(self, request, *args, **kwargs):
many = True if isinstance(request.data, list) else False
serializer = BookSerializer(data=request.data, many=many)
serializer.is_valid(raise_exception=True)
author = request.user # you can change here
book_list = [Book(**data, author=author) for data in serializer.validated_data]
Book.objects.bulk_create(book_list)
return Response({}, status=status.HTTP_201_CREATED)

DRF using serializers with a dynamic primary key

Our API has a model defined:
class Job(models.Model):
file = models.FileField('File')
xml = models.FileField('XML')
There is a basic serializer:
class XmlSerializer(serializers.ModelSerializer)
file = serializers.FileField(read_only=True)
xml = serializers.FileField(required=True)
class Meta:
model = Job
fields = '__all__'
We don't want to change the file but we do want to change the xml field. The xml is uploaded by a system that doesn't know the primary key. Of course we need this to update the model.
I have the following code at the moment:
class ProcessXml(mixins.CreateModelMixin, generics.GenericAPIView):
serializer_class = XmlSerializer
def post(self, request, format=None):
pk = 200
serializer = XmlSerializer(request.data)
return Response({})
pk = 200 serves as an example instead of the code we use to parse the xml. I know this doesn't work but it's to show my intention (more or less).
I have tried to use
id = serializers.SerializerMethodField()
def get_id(self, obj):
return 200
without success.
How do I get this primary key into the the serializer?
I was making it much too difficult. The solution was pretty easy.
class ProcessXml(mixins.CreateModelMixin, generics.GenericAPIView):
serializer_class = XmlSerializer
def post(self, request, format=None):
id = magic_xml_parser_function()
job = get_object_or_404(Job, pk=id)
serializer = XmlSerializer(job, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
magic_xml_parser_function() contains the id we found in the xml. This solved it for us.

Categories