lookup field should not required while update but required while create - python

I am using model viewset in django rest framework.
where lookup field is company
class UserViewSet(viewsets.ModelViewSet):
"""
A viewset for viewing and editing user instances.
"""
serializer_class = UserSerializer
queryset = User.objects.all()
lookup_field = 'company'
Company is required when create user.
POST method - /localhost/user/
but when i trying to update
PUT method - /localhost/user/1/
here 1 is company_id
it is raising error
{
"company": [
"This field is required."
]
}
serializers.py
class UserSerializer(DynamicFieldsModelSerializer):
class Meta:
model = User
fields = "__all__"
If i make company allow_null =True
then it will also not required for create metod.
How can i make company required for create.
and
not required for update

PUT method is update,it's need you upload all your model fields data,
PATCH method is partial_update,it's only need you upload partial model fields data.
View the source code you will see:
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
self.before_update(instance)
serializer = self.get_serializer(instance, data=request.data, partial=partial)
....
partial=partial is what matters,or you need PATCH method - /localhost/user/1/

Related

how to pass current authenticated user to django rest framework serializer?

I have a model like this:
class Professional(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
dummy_text = models.CharField(max_length=300)
a serializer like this:
class ProfessionalSerializer(serializers.ModelSerializer):
class Meta:
model = Professional
fields = '__all__'
and a view like this one:
class CreateProfessional(generics.CreateAPIView):
serializer_class = ProfessionalSerializer
The thing is, I need to pass the current authenticated user for a given request as the user for my serializer, I'm getting an error because obviously the user field is required as stated in my model, but I can't find an elegant way to do so, how could I go about it?
Set the user as a read_only_fields in the serializer meta. This will prevent accepting the user data from the payload.
class ProfessionalSerializer(serializers.ModelSerializer):
class Meta:
model = Professional
fields = '__all__'
read_only_fields = ["user"]
Then, override the perform_create(...) method of the view class
class CreateProfessional(generics.CreateAPIView):
serializer_class = ProfessionalSerializer
def perform_create(self, serializer):
serializer.save(user=self.request.user)
You can make validate method with like validate_user and do following code inside.
self.context['view'].request.user()
class CreateProfessional(generics.CreateAPIView):
serializer_class = ProfessionalSerializer
def create(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data, context={'request': request})
serializer.is_valid(raise_exception=True)
serializer.save()
class ProfessionalSerializer(serializers.ModelSerializer):
class Meta:
model = Professional
fields = '__all__'
def create(self, validated_data):
user = self.context['request'].user
like this you can get your current user
I found a very nice way to do it, I dont wan't to return custom responses, I will let that to the framework, what I did was the following in my view:
def get_serializer(self, *args, **kwargs):
# redefine method to parameterize the serializer
# while leaving the response handling to the
# framework
serializer_class = self.get_serializer_class()
kwargs['context'] = self.get_serializer_context()
kwargs['context']['user'] = self.request.user
return serializer_class(*args, **kwargs)
and in my serializer:
class EntitySerializer(serializers.ModelSerializer):
class Meta:
model = Entity
fields = '__all__'
extra_kwargs = {'user': {'required': False}}
def create(self, validated_data):
user = self.context['request'].user
entity = Entity.objects.create(user=user, **validated_data)
return entity
You can use ModelViewSet instead of single API Views. ModelViewSet handles all the crud operations automatically.
Let's say you have the model
class Professional(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
dummy_text = models.CharField(max_length=300)
and the serializer
class ProfessionalSerializer(serializers.ModelSerializer):
class Meta:
model = Professional
fields = '__all__'
So you can add a ModelViewSet
class ProfessionalViewSet(ModelViewSet):
queryset = Professional.objects.all()
permission_classes = [IsAuthenticated]
serializer_class = ProfessionalSerializer
this will handle all the crud operations for your model. Following example class will show you the methods that a viewset has.
class UserViewSet(viewsets.ViewSet):
"""
Example empty viewset demonstrating the standard
actions that will be handled by a router class.
If you're using format suffixes, make sure to also include
the `format=None` keyword argument for each action.
"""
def list(self, request):
pass
def create(self, request):
pass
def retrieve(self, request, pk=None):
pass
def update(self, request, pk=None):
pass
def partial_update(self, request, pk=None):
pass
def destroy(self, request, pk=None):
pass
these will handle all the actions. checkout the documentation for more
you'll also have to add the router to handle all the urls automatically which is also covered in the documentation (above link). Following is an example(urls.py).
from myapp.views import UserViewSet
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'users', UserViewSet, basename='user')
urlpatterns = router.urls

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)

How to make a field in Serializer both readable and writeable

I am writing a Serializer for my model which will be accessed using GET, POST and PATCH endpoints. I have a property in my model which I am using as a source for a field. However, using source= in serializer field is making it ready-only.
If I remove the source="get_field1" from the
field1 = serializers.NullBooleanField(source="get_field1")
then I am able to update the data.
But I HAVE to use the source to get the right value of the field.
class MyModel(models.Model):
field1 = NullBooleanField(default=None)
#property
get_field1(self):
data = True
# ...some logic
return data
Now I have a serializer that I am using
class MyModelSerializer(serializers.ModelSerializer):
field1 = serializers.NullBooleanField(source="get_field1")
class Meta:
model = MyModel
fields = ('field1')
Now in my API endpoint, I do this
serializer = MyModelSerializer(my_model, data=request.data, partial=True)
if serializer.is_valid():
serializer.save() # <- throws error "can't set attribute"
Also, I would like to mention that the field in the serializer is referred by the property name and not by the field name.
Example: If I do
>> serializer.validated_data
>> 'OrderedDict(['get_field1'], True) # <- shouldn't this by field1 and not get_field1
Answer by #JPG is good, but I feel it's a hacky way.
I would override the to_representation method of the Serializer to achieve your purpose.
Here's what you can do
class MyModelSerializer(serializers.ModelSerializer):
field1 = serializers.NullBooleanField() # get rid of source
def to_representation(self, instance):
data = super(MyModel, self).to_representation(instance)
data.update({
'field1': instance.get_field1
})
return data
class Meta:
model = MyModel
fields = ('field1')
This way you are implicitly providing the source and your field becomes writeable. So every time you GET, POST, or PATCH you'll get the right value.
This can be done by overriding the __init__() method of the serializer. Apart from that, we've to pass some context data to the serializer to distinguish between the GET, POST and PATCH requests.
class MyModelSerializer(serializers.ModelSerializer):
field1 = serializers.NullBooleanField() # remove "source" argument from here
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.context['request'].method == 'GET':
self.fields['field1'] =serializers.NullBooleanField(source= "get_field1")
class Meta:
model = MyModel
fields = ('field1',)
and while serializing the data, don't forget to pass the request as context, as
serializer = MyModelSerializer(my_model, data=request.data, partial=True, context={"request": request})

In the Django REST framework, how to allow partial updates when using a ModeViewSet?

I'd like to create a REST API for an object which can be partially updated. On http://www.django-rest-framework.org/api-guide/serializers/#partial-updates and example is given in which partial=True is passed when instantiating the serializer:
# Update `comment` with partial data
serializer = CommentSerializer(comment, data={'content': u'foo bar'}, partial=True)
In my case, however, the model (which is called SessionType) has the following viewset:
class SessionTypeViewSet(viewsets.ModelViewSet):
queryset = SessionType.objects.all()
serializer_class = SessionTypeSerializer
where the serializer is defined as
class SessionTypeSerializer(serializers.ModelSerializer):
class Meta:
model = SessionType
fields = ('title',)
How can I adapt the serializer in this use case so that partial is always True?
You don't need to adapt the serializer in any way. With that viewset, any call to the "detail" endpoint using the PATCH method will do a partial update.
The Django Rest Framework ModelViewSet base class includes the following mixin. Here you can see how partial=True is passed when calling partial_update, which is routed to the PATCH method by default:
class UpdateModelMixin(object):
"""
Update a model instance.
"""
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)
if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to a queryset, we need to
# refresh the instance from the database.
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)
def perform_update(self, serializer):
serializer.save()
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
The partial update is implicit in the ModelViewset acoording with the documentation the only thing you need to do is call the "SessionTypeViewSet" endpoint with the method PATCH

django rest framework add field when not in list view

I'm using the Django Rest Framework and I'd like to be able to add extra detail to the serializer when a single object is returned, which would be left out of the list view.
In the code below I add the celery_state field to the TestModelSerializer, but I'd only like this field to be added when its returning a single object, not when it's returning the list of TestModel data.
I've looked at the list_serializer_class option but it seems to just use the original model serializer so it will always still include the field even if I try to exclude from there.
What are my options?
class TestModelSerializer(serializers.HyperlinkedModelSerializer):
celery_state = serializers.CharField(source='celery_state', read_only=True)
class Meta:
model = TestModel
class TestModelViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows TestModels to be viewed or edited.
"""
authentication_classes = (SessionAuthentication, BasicAuthentication)
permission_classes = (IsAuthenticatedOrReadOnly,)
queryset = TestModel.objects.all()
serializer_class = TestModelSerializer
Since the serializer class (used by the viewsets) passes many argument, you can use that to control the fields output:
class TestModelSerializer(serializers.HyperlinkedModelSerializer):
# ...
def __init__(self, *args, **kwargs):
super(TestModelSerializer, self).__init__(*args, **kwargs)
if kwargs.get('many', False):
self.fields.pop('celery_state')
Inspired by #mariodev answer:
The other possibility is to override many_init static method in serializer. Acording comments in thie code (https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/serializers.py#L128 ) it is suggested variant.
from rest_framework import serializers
class ExtendedSerializer(serializers.Serializer):
...
#classmethod
def many_init(cls, *args, **kwargs):
kwargs['child'] = cls()
kwargs['child'].fields.pop('extractedFiled')
return serializers.ListSerializer(*args, **kwargs)
You can have an extra serializer called ExtendedTestModelSerializer which would contain the extra fields that you want.
After that, you can use the get_serializer_class method to decide which serializer is used based on request.action -
class TestModelViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows TestModels to be viewed or edited.
"""
authentication_classes = (SessionAuthentication, BasicAuthentication)
permission_classes = (IsAuthenticatedOrReadOnly,)
queryset = TestModel.objects.all()
# serializer_class = TestModelSerializer
get_serializer_class(self):
if self.request.action == 'list':
return TestModelSerializer
return ExtendedTestModelSerializer

Categories