Update data using RetrieveUpdateAPIView - Getting validated data from a serializer - django

I would like to update certain properties of a user (say first_name and last_name)
my json object through a PUT request would look like this
{
"user" : {
"first_name": "Jack",
"last_name": "shnider",
"password":"admin123"
"email" : "foo#google.com"
},
"employee_zip" : 12345
}
This is what my view looks like (I would like to update the existing fields to these new fields).
These are the serializer
class Serializer_UpdateUser(ModelSerializer):
class Meta:
model = User
fields = ('first_name','last_name','password')
class Serializer_UpdateEmployer(ModelSerializer):
user = Serializer_UpdateUser()
class Meta:
model = modelEmployer
fields = [
'user',
'employer_zip',
]
This is the view :
class UpdateProfile_RetrieveUpdateAPIView(RetrieveUpdateAPIView):
queryset = modelEmployer.objects.all()
serializer_class = Serializer_UpdateEmployer
lookup_field = 'user__email'
permission_classes = [permissions.AllowAny]
def update(self, request, *args, **kwargs):
instance = self.get_object() #------>I have the object that I would like to update
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True) #--->Success
Now I would like to get a validated fields (The json only contains the fields that have been updated). I know if I do something like this
serializer.save
I would get back a modelEmployer but instead I get back this error
AssertionError at /api/employer/update_profile/employerA#gmail.com/ The `.update()` method does not support writable nested fields by default. Write an explicit `.update()` method for serializer `Employer.api.serializers.Serializer_ListEmployer`, or set `read_only=True` on nested serializer fields. Request Method:
I have two questions
1-Why is save failing ?
2-How can I get the validated data from the above serializer ?

The save is failing because django-rest-framework doesn't deal with nested serializers by default.
from the django-rest-framework docs:
By default nested serializers are
read-only. If you want to support write-operations to a nested
serializer field you'll need to create create() and/or update()
methods in order to explicitly specify how the child relationships
should be saved.
You have to override the update method in the serializer to allow that behavior:
class Serializer_UpdateEmployer(ModelSerializer):
user = Serializer_UpdateUser()
class Meta:
model = modelEmployer
fields = [
'user',
'employer_zip',
]
def update(self, instance, validated_data):
user_data = validated_data.pop('user', {})
user_serializer = Serializer_UpdateUser(instance.user, data=user_data)
user_serializer.save()
return instance
Another solution is to use drf-writable-nested. It automatically makes your nested serializers updatable.
from drf_writable_nested import WritableNestedModelSerializer
class Serializer_UpdateEmployer(WritableNestedModelSerializer):
user = Serializer_UpdateUser()
class Meta:
model = modelEmployer
fields = [
'user',
'employer_zip',
]

I think drf-writable-nested can help you to update nested data.
In you case:
from django.contrib.auth import password_validation
class Serializer_UpdateUser(ModelSerializer):
def update(self, instance, validated_data):
password = validated_data.pop('password', None)
super(Serializer_UpdateUser, self).update(instance, validated_data)
if password is not None:
instance.set_password(password)
instance.save()
return instance
def validate_password(self, value):
password_validation.validate_password(value)
return value
class Meta:
model = User
fields = ('first_name','last_name','password')
class Serializer_UpdateEmployer(WritableNestedModelSerializer):
user = Serializer_UpdateUser()
class Meta:
model = modelEmployer
fields = [
'user',
'employer_zip',
]
Note you need special handling password field.

Related

Single Update and Delete API for two models connected with a OneToOne relationship in Django Rest Framework

I've looked extensively on here and probably exhausted all the answers and still haven't found a solution to my particular problem, which is to make an API that update/delete from both models, and I am getting the following error:
The .update()method does not support writable nested fields by default. Write an explicit.update()method for serializeruser_profile.serializers.UserSerializer, or set read_only=True on nested serializer fields.
In this particular instance this happens when I try to update a field from the user_profile model
I have separated my Django project into several apps/folders with each model being in its own folder.
I have a user app and a user_profile app each with their own models.
the user model is basically an AbstractUser sitting in its own app
the user_profile model is as follows:
class UserProfile(models.Model):
user = models.OneToOneField(to=User, on_delete=models.CASCADE, related_name='userprofile')
location = models.CharField(blank=True, max_length=30)
created_time = models.DateTimeField(auto_now_add=True)
updated_time = models.DateTimeField(auto_now=True)
The serializers are as follows:
class UserProfileCrudSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = ('location', 'created_time', 'updated_time')
class UserSerializer(serializers.ModelSerializer):
profile = UserProfileCrudSerializer(source='userprofile', many=False)
class Meta:
model = User
fields = ('username', 'email', 'first_name', 'last_name', 'profile')
def update(self, instance, validated_data):
userprofile_serializer = self.fields['profile']
userprofile_instance = instance.userprofile
userprofile_data = validated_data.pop('userprofile', {})
userprofile_serializer.update(userprofile_instance, userprofile_data)
instance = super().update(instance, validated_data)
return instance
and my view is:
class RetrieveUpdateView(RetrieveUpdateAPIView):
serializer_class = UserSerializer
queryset = User.objects.all()
def get_object(self):
return self.request.user
when I do a GET I am getting the following response without any problems:
{
"username": "blue",
"email": "bluebear#bluebear.com",
"first_name": "Blue",
"last_name": "Bear",
"profile": {
"location": "London",
"created_time": "2023-02-03T00:39:15.149924Z",
"updated_time": "2023-02-03T00:39:15.149924Z"
}
}
and I do a patch request like this:
{
"profile": {
"location": "Paris"
}
}
The way the code is now I have no issue updating username, email, first_name, and last_name which come from the AbstractUser but I am getting the above error when I try to patch the location which is in the UserProfile model.
I've looked at many similar solutions online, but none that pertain to my particular situation.
The .update() method does not support writable nested fields by default. Write an explicit .update() method for serializeruser_profile.serializers.UserSerializer, or set read_only=True on nested serializer fields.
It already shows in the message, you need to explicitly write the update method for the writable nested serializer which is documented here https://www.django-rest-framework.org/topics/writable-nested-serializers/ or you can use another module that is also referred to in the docs https://github.com/beda-software/drf-writable-nested.
Your approach is correct already but there is some typo and wrong indentation in your code:
class UserSerializer(serializers.ModelSerializer):
profile = UserProfileCrudSerializer(source='userprofile', many=False)
class Meta:
model = User
fields = ('username', 'email', 'first_name', 'last_name', 'profile')
def update(self, instance, validated_data):
# update is a method of serializer not serializer.Meta
userprofile_serializer = self.fields['profile']
userprofile_instance = instance.userprofile
# should be 'profile' here instead of 'userprofile' as you defined in serializer
userprofile_data = validated_data.pop('profile', {})
userprofile_serializer.update(userprofile_instance, userprofile_data)
instance = super().update(instance, validated_data)
return instance

DRF SERILAZATION

I serialize the field named "product" with ProductSerializer() inside OrderItemSerializer().
That's what I want.
class OrderItemSerializer(serializers.ModelSerializer):
product = ProductSerializer()
class Meta:
model = models.OrderItem
fields = ('id','order', 'product', 'quantity')
The output is;
But when I try to request with POST Method needs to send Product as a dictionary, just giving the id value is not enough.
How can I POST by sending only the id value?
I haven't written anything about the operation yet. Default ModelViewSet
class OrderItemViewSet(ModelViewSet):
queryset = OrderItem.objects.all()
serializer_class = serializers.OrderItemSerializer
permission_classes = (IsOwnerOrNot, IsAuthenticated)
def get_queryset(self):
user = self.request.user
return self.filter_queryset(queryset=self.queryset.filter(order__user=self.request.user))
If you're supporting writable nested representations you'll need to write .create() or .update() methods that handle saving multiple objects.
class UserSerializer(serializers.ModelSerializer):
profile = ProfileSerializer()
class Meta:
model = User
fields = ['username', 'email', 'profile']
def create(self, validated_data):
profile_data = validated_data.pop('profile')
user = User.objects.create(**validated_data)
Profile.objects.create(user=user, **profile_data)
return user

Updating User and extended User Profile in one API Call in Django Rest Framework

I have a form on my frontend where the user can update some info - part of the fields are supposed to update the default Django User Model, while others should update the User Profile model, which has a One-To-One Relationship to the User.
I am running the code below but get the following error:
AttributeError: 'str' object has no attribute '_meta'
api.py
# Get and Update User API
class UserAPI(generics.RetrieveUpdateAPIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = UserSerializer
def retrieve(self, request, *args, **kwargs):
serializer = self.serializer_class(request.user)
return Response(serializer.data)
def update(self, request, *args, **kwargs):
user_data = request.data['user']
profile_data = request.data['profile']
user_serializer = self.serializer_class(request.user, data=user_data, partial=True)
user_serializer.is_valid(raise_exception=True)
user_serializer.save()
if profile_data:
profile_serializer_class = ProfileSerializer
profile_serializer = ProfileSerializer(request.user.username, data=profile_data, partial=True)
print(profile_serializer)
profile_serializer.is_valid(raise_exception=True)
profile_serializer.save()
response = {'user': user_serializer.data, 'profile': profile_serializer.data}
return Response(response)
# UserProfile API
class ProfileAPI(generics.RetrieveAPIView):
lookup_field = 'user'
serializer_class = ProfileSerializer
queryset = Profile.objects.all()
serializers.py
# User Serializer
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'email', 'first_name', 'last_name')
# Profile Serializer
class ProfileSerializer(serializers.ModelSerializer):
user = serializers.SlugRelatedField(read_only=True, slug_field="username")
class Meta:
model = Profile
fields = '__all__'
I am making a patch request in the following format:
{
"user": {
"username": "testuser"
},
"profile": {
"bio": "hello this is my bio"
}
}
The patch worked when I was only updating the User model, however I am not sure if this is the correct way to include the Profile model. What would I need to change to make this work?
The first positional argument to Serializer and its subclasses should be the instance you want to serialize, create, or update. In the case of ModelSerializer it is a model instance.
You should be therefore passing the Profile instance. I assume it is user.profile
profile_serializer = ProfileSerializer(request.user.profile, data=profile_data, partial=True)

Django Rest Framework: Dynamically return subset of fields

Problem
As recommended in the blogpost Best Practices for Designing a Pragmatic RESTful API, I would like to add a fields query parameter to a Django Rest Framework based API which enables the user to select only a subset of fields per resource.
Example
Serializer:
class IdentitySerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = models.Identity
fields = ('id', 'url', 'type', 'data')
A regular query would return all fields.
GET /identities/
[
{
"id": 1,
"url": "http://localhost:8000/api/identities/1/",
"type": 5,
"data": "John Doe"
},
...
]
A query with the fields parameter should only return a subset of the fields:
GET /identities/?fields=id,data
[
{
"id": 1,
"data": "John Doe"
},
...
]
A query with invalid fields should either ignore the invalid fields or throw a client error.
Goal
Is this possible out of the box somehow? If not, what's the simplest way to implement this? Is there a 3rd party package around that does this already?
You can override the serializer __init__ method and set the fields attribute dynamically, based on the query params. You can access the request object throughout the context, passed to the serializer.
Here is a copy&paste from Django Rest Framework documentation example on the matter:
from rest_framework import serializers
class DynamicFieldsModelSerializer(serializers.ModelSerializer):
"""
A ModelSerializer that takes an additional `fields` argument that
controls which fields should be displayed.
"""
def __init__(self, *args, **kwargs):
# Instantiate the superclass normally
super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
fields = self.context['request'].query_params.get('fields')
if fields:
fields = fields.split(',')
# Drop any fields that are not specified in the `fields` argument.
allowed = set(fields)
existing = set(self.fields.keys())
for field_name in existing - allowed:
self.fields.pop(field_name)
class UserSerializer(DynamicFieldsModelSerializer, serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'email')
This functionality is available from a 3rd-party package.
pip install djangorestframework-queryfields
Declare your serializer like this:
from rest_framework.serializers import ModelSerializer
from drf_queryfields import QueryFieldsMixin
class MyModelSerializer(QueryFieldsMixin, ModelSerializer):
...
Then the fields can now be specified (client-side) by using query arguments:
GET /identities/?fields=id,data
Exclusion filtering is also possible, e.g. to return every field except id:
GET /identities/?fields!=id
disclaimer: I'm the author/maintainer.
serializers.py
class DynamicFieldsSerializerMixin(object):
def __init__(self, *args, **kwargs):
# Don't pass the 'fields' arg up to the superclass
fields = kwargs.pop('fields', None)
# Instantiate the superclass normally
super(DynamicFieldsSerializerMixin, self).__init__(*args, **kwargs)
if fields is not None:
# Drop any fields that are not specified in the `fields` argument.
allowed = set(fields)
existing = set(self.fields.keys())
for field_name in existing - allowed:
self.fields.pop(field_name)
class UserSerializer(DynamicFieldsSerializerMixin, serializers.HyperlinkedModelSerializer):
password = serializers.CharField(
style={'input_type': 'password'}, write_only=True
)
class Meta:
model = User
fields = ('id', 'username', 'password', 'email', 'first_name', 'last_name')
def create(self, validated_data):
user = User.objects.create(
username=validated_data['username'],
email=validated_data['email'],
first_name=validated_data['first_name'],
last_name=validated_data['last_name']
)
user.set_password(validated_data['password'])
user.save()
return user
views.py
class DynamicFieldsViewMixin(object):
def get_serializer(self, *args, **kwargs):
serializer_class = self.get_serializer_class()
fields = None
if self.request.method == 'GET':
query_fields = self.request.QUERY_PARAMS.get("fields", None)
if query_fields:
fields = tuple(query_fields.split(','))
kwargs['context'] = self.get_serializer_context()
kwargs['fields'] = fields
return serializer_class(*args, **kwargs)
class UserList(DynamicFieldsViewMixin, ListCreateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
Configure a new pagination serializer class
from rest_framework import pagination, serializers
class DynamicFieldsPaginationSerializer(pagination.BasePaginationSerializer):
"""
A dynamic fields implementation of a pagination serializer.
"""
count = serializers.Field(source='paginator.count')
next = pagination.NextPageField(source='*')
previous = pagination.PreviousPageField(source='*')
def __init__(self, *args, **kwargs):
"""
Override init to add in the object serializer field on-the-fly.
"""
fields = kwargs.pop('fields', None)
super(pagination.BasePaginationSerializer, self).__init__(*args, **kwargs)
results_field = self.results_field
object_serializer = self.opts.object_serializer_class
if 'context' in kwargs:
context_kwarg = {'context': kwargs['context']}
else:
context_kwarg = {}
if fields:
context_kwarg.update({'fields': fields})
self.fields[results_field] = object_serializer(source='object_list',
many=True,
**context_kwarg)
# Set the pagination serializer setting
REST_FRAMEWORK = {
# [...]
'DEFAULT_PAGINATION_SERIALIZER_CLASS': 'DynamicFieldsPaginationSerializer',
}
Make dynamic serializer
from rest_framework import serializers
class DynamicFieldsModelSerializer(serializers.ModelSerializer):
"""
A ModelSerializer that takes an additional `fields` argument that
controls which fields should be displayed.
See:
http://tomchristie.github.io/rest-framework-2-docs/api-guide/serializers
"""
def __init__(self, *args, **kwargs):
# Don't pass the 'fields' arg up to the superclass
fields = kwargs.pop('fields', None)
# Instantiate the superclass normally
super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
if fields:
# Drop any fields that are not specified in the `fields` argument.
allowed = set(fields)
existing = set(self.fields.keys())
for field_name in existing - allowed:
self.fields.pop(field_name)
# Use it
class MyPonySerializer(DynamicFieldsModelSerializer):
# [...]
Last, use a homemage mixin for your APIViews
class DynamicFields(object):
"""A mixins that allows the query builder to display certain fields"""
def get_fields_to_display(self):
fields = self.request.GET.get('fields', None)
return fields.split(',') if fields else None
def get_serializer(self, instance=None, data=None, files=None, many=False,
partial=False, allow_add_remove=False):
"""
Return the serializer instance that should be used for validating and
deserializing input, and for serializing output.
"""
serializer_class = self.get_serializer_class()
context = self.get_serializer_context()
fields = self.get_fields_to_display()
return serializer_class(instance, data=data, files=files,
many=many, partial=partial,
allow_add_remove=allow_add_remove,
context=context, fields=fields)
def get_pagination_serializer(self, page):
"""
Return a serializer instance to use with paginated data.
"""
class SerializerClass(self.pagination_serializer_class):
class Meta:
object_serializer_class = self.get_serializer_class()
pagination_serializer_class = SerializerClass
context = self.get_serializer_context()
fields = self.get_fields_to_display()
return pagination_serializer_class(instance=page, context=context, fields=fields)
class MyPonyList(DynamicFields, generics.ListAPIView):
# [...]
Request
Now, when you request a resource, you can add a parameter fields to show only specified fields in url.
/?fields=field1,field2
You can find a reminder here : https://gist.github.com/Kmaschta/e28cf21fb3f0b90c597a
You could try Dynamic REST, which has support for dynamic fields (inclusion, exclusion), embedded / sideloaded objects, filtering, ordering, pagination, and more.
If you want something flexible like GraphQL, you can use django-restql. It supports nested data (both flat and iterable).
Example
from rest_framework import serializers
from django.contrib.auth.models import User
from django_restql.mixins import DynamicFieldsMixin
class UserSerializer(DynamicFieldsMixin, serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'email', 'groups')
A regular request returns all fields.
GET /users
[
{
"id": 1,
"username": "yezyilomo",
"email": "yezileliilomo#hotmail.com",
"groups": [1,2]
},
...
]
A request with the query parameter on the other hand returns only a subset of
the fields:
GET /users/?query={id, username}
[
{
"id": 1,
"username": "yezyilomo"
},
...
]
With django-restql you can access nested fields of any level. E.g
GET /users/?query={id, username, date_joined{year}}
[
{
"id": 1,
"username": "yezyilomo",
"date_joined": {
"year": 2018
}
},
...
]
For iterable nested fields, E.g groups on users.
GET /users/?query={id, username, groups{id, name}}
[
{
"id": 1,
"username": "yezyilomo",
"groups": [
{
"id": 2,
"name": "Auth_User"
}
]
},
...
]
Such functionality we've provided in drf_tweaks / control-over-serialized-fields.
If you use our serializers, all you need is to pass ?fields=x,y,z parameter in the query.
For nested data, I am using Django Rest Framework with the package recommended in the docs, drf-flexfields
This allows you to restrict the fields returned on both the parent and child objects. The instructions in the readme are good, just a few things to watch out for:
The URL seems to need the / like this '/person/?expand=country&fields=id,name,country' instead of as written in the readme '/person?expand=country&fields=id,name,country'
The naming of the nested object and its related name need to be completely consistent, which isn't required otherwise.
If you have 'many' e.g. a country can have many states, you'll need to set 'many': True in the Serializer as described in the docs.
The solution suggested at the [DRF-Documentation][1] worked for me, however when I called the serializer from the View with:
class SomeView(ListAPIView):
def get(self, request, *args, **kwargs):
qry=table.objects.filter(column_value=self.kwargs['urlparameter'])
fields=['DBcol1','DBcol2','DBcol3']
serializer=SomeSerializer(qry,many=True,fields=fields)
I had to add many=True, otherwise it was not working.
[1]: https://www.django-rest-framework.org/api-guide/serializers/#example
Another alternative is to make use of GraphWrap: https://github.com/PaulGilmartin/graph_wrap
By adding /graphql to your urlpatterns, you add layer your REST API with a fully compliant GraphQL queryable API.

Django Rest Framework return nested object using PrimaryKeyRelatedField

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.