How to update a list in a nested serializer by id
I have a user who has multiple contacts
Example:
contact serializer
class ContactSerializer(serializers.ModelSerializer):
class Meta:
model = Contact
fields = [
'id',
'name',
'last_name'
]
user serializer
class UserSerializer(serializers.ModelSerializer):
email = serializers.EmailField(
required=True,
validators=[
UniqueValidator(queryset=User.objects.all())
]
)
contacts = ContactSerializer(many=True)
class Meta:
model = User
fields = [
"email",
"contacts"
]
def update(self, instance, validated_data):
contacts_data = validated_data.pop('contacts')
contacts = (instance.contacts).all()
contacts = list(contacts)
instance.name = validated_data.get('name', instance.name)
instance.save()
# many contacts
for contact_data in contacts_data:
contact = contacts.pop(0)
contact.name = contact_data.get('name', contact.name)
contact.last_name = contact_data.get('last_name', contact.last_name)
contact.save()
return instance
I want to update contacts by ID, for now it works, but it only updates the first contact in the list
Example I want to update the contact with ID 4 and 6
Payload request
{
"name": "string",
"contacts": [
{
"id": 4,
"name": "string",
"last_name": "string"
},
{
"id": 6,
"name": "string",
"last_name": "string"
}
]
}
Any ideas or recommendations?
Try it like this
def update(self, instance, validated_data):
contacts_data = validated_data.pop('contacts')
instance.name = validated_data.get('name', instance.name)
instance.save()
# many contacts
for contact_data in contacts_data:
contact = Contact.objects.get(pk=contact_data['id']) # this will crash if the id is invalid though
contact.name = contact_data.get('name', contact.name)
contact.last_name = contact_data.get('last_name', contact.last_name)
contact.save()
return instance
Related
I have following Serializer I am facing problem with Json with serializing. I have user named daniel james and he have multiple subject like maths science I am providing nested serializer to fill all subject but based on subject users name also repeats below is more specific qsn
This is my model.
class Result(BaseModel):
semester_choices = (
('first', 'First'),
('second', 'Second'),
('third', 'Third'),
('fourth', 'Fourth'),
('fifth', 'Fifth'),
('sixth', 'Sixth'),
('seventh', 'Seventh'),
('eight', 'Eight'),
('all', 'All'),
)
user = models.ForeignKey(User, on_delete=models.CASCADE)
registration_number = models.CharField(null=True, blank=True, max_length=20)
semester = models.CharField(max_length=50, choices=semester_choices, null=True, blank=True)
gpa = models.CharField(max_length=20)
subject = models.CharField(max_length=50)
serializers.py
class ResultSerializer(serializers.ModelSerializer):
class Meta:
model = Result
fields = '__all__'
class ListResultSerializer(ResultSerializer):
user = serializers.CharField()
semester = serializers.CharField()
subject = serializers.SerializerMethodField()
class Meta(ResultSerializer.Meta):
fields = (
'user',
'semester',
'subject',
)
def get_subject(self, instance):
return SubjectSerializer(instance).data
This is my SubjectSerializer
class SubjectSerializer(ResultSerializer):
class Meta(ResultSerializer.Meta):
fields = (
'gpa',
'subject'
)
And In my views.py I have done like this.
class ListResultView(rest_generics.ListAPIView, UserMixin):
serializer_class = serializers.ListResultSerializer
permission_classes = (AllowAny,)
def get_object(self):
return self.get_user()
def get_queryset(self):
return usecases.ListResultUseCase(
user=self.get_user()
).execute()
I use usecases.py to filter the data here is further code
class ListResultUseCase:
def __init__(self, user: User):
self._user = user
def execute(self):
self._factory()
return self._result
def _factory(self):
self._result = Result.objects.filter(user=self._user)
Now this is the Json I am getting right now from above code.
[
{
"user": "daniel james",
"semester": "first",
"subject": {
"gpa": "a+",
"subject": "maths"
}
},
{
"user": "daniel james",
"semester": "first",
"subject": {
"gpa": "A",
"subject": "data structures"
}
}
]
I want my json to be in this format
[
{
"user": "daniel james",
"semester": "first",
"subject": [
{
"gpa": "a+",
"subject": "maths"
},
{
"gpa": "A",
"subject": "data structures"
}
]
}
]
Any help in serializer?
You could create a seperate serializer to use with the nested serializer.
class SubjectNestedSerializer(serializers.ModelSerializer):
class Meta:
model = Subject
exclude = ['user']
P.S
Try to change your model like this
class Semester(BaseModel):
semester_choices = (
('first', 'First'),
('second', 'Second'),
('third', 'Third'),
('fourth', 'Fourth'),
('fifth', 'Fifth'),
('sixth', 'Sixth'),
('seventh', 'Seventh'),
('eight', 'Eight'),
('all', 'All'),
)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='semesters')
registration_number = models.CharField(null=True, blank=True, max_length=20)
semester = models.CharField(max_length=50, choices=semester_choices, null=True, blank=True)
class Subject(BaseModel):
semester = models.ForeignKey(Semester, on_delete=models.CASCADE, related_name='semester_subjects')
gpa = models.CharField(max_length=20)
subject = models.CharField(max_length=50)
You could create a Serializer for your user, and adding a method field that retrieve all the subjects of the user like this.
def get_subjects(self,instace):
subjects = Subject.objects.filter(user=instance)
return SubjectSerializer(subjects, many=True)
What you want is an aggregation of existing data to have a more meaningful result for frontend. I'd rather generating my own response than using a serializer for this.
Here is a simple example for you.
Let's say you want results for multiple users. (It's even easier if you want for one user)
def get_item_with_attribute_from_list(items, attribute, attribute_value):
"""
This is just a utility function that I use occasionally
"""
if items is None or len(items) == 0:
return None
if isinstance(items[0], dict):
for item in items:
if item.get(attribute) == attribute_value:
return item
else:
for item in items:
if getattr(item, attribute, None) == attribute_value:
return item
results = Result.objects.all().select_related('user')
response = []
for result in results:
user_dict = get_item_with_attribute_from_list(response, "user", result.user.name)
subject = {"gpa": "A", "subject": "data structures"}
if user_dict:
semester_list = user_dict['semesters']
semester = get_item_with_attribute_from_list(semester_list, 'semester_name', result.semester)
if semester:
semester['subjects'].append(subject)
else:
semester_list.append({'subjects': [subject]})
else:
response.append({'user': result.user.name, 'semesters': [{'semester_name': result.semester, 'subjects': [subject]}]})
Resulting JSON will be something like this.
[
{
"user": "daniel james",
"semesters": [
{
"semester_name": "first",
"subjects": [
{
"gpa": "a+",
"subject": "maths"
},
{
"gpa": "A",
"subject": "data structures"
}
]
},
{
"semester_name": "second",
"subjects": [
{
"gpa": "b+",
"subject": "geometry"
},
{
"gpa": "C",
"subject": "chemistry"
}
]
}
]
},
... # Other user's data
]
This way, you'll have more structured data, you'll be able to calculate SPA's for each semester easily.
I am trying to create a simple model with foreign keys using Django rest framework.
This are the models:
class Route(models.Model):
place_origin = models.ForeignKey(
Place, null=False, on_delete=models.DO_NOTHING)
class Place(models.Model):
name = models.CharField(max_length=50, null=False)
This are the serializers for each model:
class PlaceSerializer(serializers.ModelSerializer):
class Meta:
model = Place
fields = ["id", "name"]
class RouteSerializer(serializers.ModelSerializer):
place_origin = PlaceSerializer(many=False, read_only=True)
class Meta:
model = Route
fields = ["id", "place_origin"]
This RouteSerializer has the place_origin property in order to show the place details(all the fields from it) when I am looking at the route detail. What I mean is for routes I want to display:
[
{
"id": 1,
"place_origin": {
"id": 1,
"name": "New york"
}
},
{
"id": 2,
"place_origin": {
"id": 2,
"name": "Boston"
}
}
]
And not just:
[
{
"id": 1,
"place_origin": 1
},
{
"id": 2,
"place_origin": 2
}
]
This is the view:
#api_view(['POST'])
def routes_new_route_view(request):
"""
Create a new route
"""
if request.method == "POST":
data = JSONParser().parse(request)
place_origin = Place.objects.get(id=data["place_origin"])
data["place_origin"] = PlaceSerializer(place_origin)
data["place_origin"] = data["place_origin"].data
serializer = RouteSerializer(data=data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, status=201)
else:
return JsonResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
I want to send the request from postman this way:
{
"place_origin": 3
}
But I am getting the error from the title.
Thanks for all the help!
The error is that you're trying to send data via a PlaceSerializer but this field is read_only. On the other hand, your DB expects place_origin since you precised null=False in your model. Both combined gives the error "Not NULL constraint failed".
The easiest way is to slightly modify your serializer in order to have one field for write and another for read.
class RouteSerializer(serializers.ModelSerializer):
place_origin = PlaceSerializer(many=False, read_only=True)
place = serializers.PrimaryKeyRelatedField(source="place_origin",queryset=Place.objects.all(),write_only=True)
class Meta:
model = Route
fields = ["id", "place_origin", "place"]
Here, you will use place field as a way to create the relationship with your Route instance.
I'm trying to create a serializer which outputs the Report and also the User information.
My task is accomplished by this serializer:
class ReportSerializer(serializers.ModelSerializer):
latitude = serializers.CharField()
longitude = serializers.CharField()
city = serializers.IntegerField()
type = serializers.IntegerField()
# We have created a field which returns a value from get_marker_icon_url
marker_icon = serializers.SerializerMethodField('get_marker_icon_url')
status_str = serializers.SerializerMethodField('convert_status_toStr')
type_str = serializers.SerializerMethodField('convert_type_toStr')
status_color = serializers.SerializerMethodField('get_status_color')
likes = serializers.SerializerMethodField('random_likes')
user = ReportUserSerializer()
class Meta:
model = Reports
fields = [
'user',
'id',
'type',
'city',
'latitude',
'longitude',
'likes',
'type_str',
'status_str',
'status_color',
'attached_message',
'marker_icon',
'attached_photo',
'date_created'
]
...
With this code my serializer returns a response like this:
[
{
"user": {
"id": 1,
"username": "3nematix",
"profile_pic": "http://192.168.0.29:8000/frontend/static/frontend/images/reports/user_profile_pic.jpg",
"verified": false
},
"id": 1,
"type": 9,
"city": 0,
"latitude": "6.5123333",
"longitude": "51.512586",
"likes": 27,
"type_str": "OTHER",
"status_str": "PENDING",
"status_color": "orange",
"attached_message": "test",
"marker_icon": "OTHER",
"attached_photo": "http://192.168.0.29:8000/frontend/static/frontend/images/reports/user_profile_pic_VRjIYTs.jpg",
"date_created": "2020-10-21T23:19:06.899302Z"
},
......
]
And this is exactly what I need, but the problem is that when I'm trying to create a new object by a POST request, I get this response:
{
"user": [
"This field is required."
]
}
If I would remove 'user' from Meta and user = ReportUserSerializer() from the ReportSerializer class, then I can create a new object, but when I wish to get the Reports I with the Users information I need to add these two again, how can I fix it?
You need to fill the user field yourself before calling save.
Here's the easy way:
class YourView(generics.ListCreateAPIView):
...
def perform_create(self, serializer):
serializer.save(user=self.request.user)
Or slightly differently:
class YourView(generics.ListCreateAPIView):
...
def perform_create(self, serializer):
serializer.validated_data['user'] = self.request.user
return super().perform_create(serializer)
I'm trying to make custom create()method to save my user profile when a User is created, the serializer works perfect for the GET method, but for POST it only shows one field instead of all fields.
I specified the fields = '__all__' property, but still don't work
GET:
JSON Response:
[
{
"job_title": {
"id": 1,
"name": "Desarrollador"
},
"birthdate": "2019-11-06",
"roles": [
{
"id": 1,
"name": "Administrador",
"description": "Administrador del sistema",
"key": "ADMIN"
}
]
}
]
POST:
Expected JSON:
[
{
"birthdate": "2019-11-06",
}
]
Serializers:
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = '__all__'
depth = 1
class UserSerializer(serializers.ModelSerializer):
user_profile = UserProfileSerializer(read_only=False, many=False)
class Meta:
model = User
fields = '__all__'
depth = 1
def create(self, validated_data):
profile_data = validated_data.pop('user_profile')
user = User.objects.create(**validated_data)
UserProfile.objects.create(user=user, **profile_data)
return user
views.py
class UserDetail(generics.RetrieveUpdateDestroyAPIView):
profile = serializers.PrimaryKeyRelatedField(many=False, queryset=UserProfile.objects.all())
queryset = User.objects.all()
serializer_class = UserSerializer
class UserProfileList(generics.ListCreateAPIView):
queryset = UserProfile.objects.all()
serializer_class = UserProfileSerializer
models.py
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name='user_profile', on_delete=models.CASCADE)
birthdate = models.DateField(default=datetime.date.today)
job_title = models.ForeignKey(JobTitle, related_name='job_title', on_delete=models.CASCADE)
roles = models.ManyToManyField(Role)
def createProfile(sender, **kwargs):
if kwargs['created']:
user_profile = UserProfile.objects.created(user=kwargs['instance'])
post_save.connect(createProfile, sender=User)
def __str__(self):
return self.user.username
I need that POST method also requires the full JSON, the same as the GET JSON response, in order to complete the create method. Need this for POST method:
{
"id": 1,
"user_profile": {
"job_title": {
"id": 1,
"name": "Desarrollador"
},
"birthdate": "2019-11-06",
"roles": [
{
"id": 1,
"name": "Administrador",
"description": "Administrador del sistema",
"key": "ADMIN"
}
]
},
"password": "pbkdf2_sha256$150000$0XdmQpeiPtVl$rQh2MEYV+IO5Y4gm2o1md2cVzgn/mL95r6m1TvRmG3g=",
"last_login": "2019-11-06T00:34:25-06:00",
"is_superuser": true,
"username": "aruiz",
"first_name": "",
"last_name": "",
"email": "correo#correo.com",
"is_staff": true,
"is_active": true,
"date_joined": "2019-11-05T11:37:49-06:00",
"groups": [],
"user_permissions": []
}
I'm trying to implement two serializer classes which will allow me to create both user and profile objects from a flattened POST request.
I tried the implementation described here and it works perfectly fine for updating (and only updating) existing objects.
Here is my current implementation:
# serializers.py
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(
write_only=True, required=True, style={"input_type": "password"}
)
class Meta:
model = User
fields = (
"username",
"password",
# ...
"date_joined",
)
read_only_fields = ("date_joined")
class UserProfileSerializer(serializers.ModelSerializer):
user = UserSerializer()
def to_representation(self, instance):
representation = super().to_representation(instance)
representation.update(representation.pop("user"))
return representation
def to_internal_value(self, data):
user_internal = {}
for key in UserSerializer.Meta.fields:
if key in data:
user_internal[key] = data.pop(key)
internal = super().to_internal_value(data)
internal["user"] = user_internal
return internal
def create(self, validated_data):
user_data = validated_data.pop("user")
user = User.objects.create(**user_data)
user.set_password(user_data["password"])
user.save()
profile = UserProfile.objects.create(user=user, **validated_data)
return profile
class Meta:
model = UserProfile
fields = (
"user",
"date_updated",
# ...
"phone_number",
)
# views.py
class Register(generics.CreateAPIView):
serializer_class = UserProfileSerializer
name = "userprofile-create"
I expect the app to take the flattened JSON and create both user and profile objects.
Example POST body:
{
"username": "test_user",
"password": "P#$$w0rd",
"first_name": "Foo",
"last_name": "Boo",
"email": "foo#example.com",
"street": "Random Street",
"street_number": "11",
"flat_number": "11",
"zip_code": "11-111",
"city": "Some City",
"province": 1,
"phone_number": "111222333"
}
When I'm browsing through the API, the view still expects JSON with nested User object:
{
"user": {
"username": "",
"password": "",
"first_name": "",
"last_name": "",
"email": ""
},
"street": "",
"street_number": "",
"flat_number": "",
"zip_code": "",
"city": "",
"province": null,
"phone_number": ""
}
Overriding to_internal_value() and to_representation() wasn't correct approach.
I've created only one serializer with all fields and overriden create() method:
class UserProfileSerializer(serializers.ModelSerializer):
username = serializers.CharField(source="user.username")
password = serializers.CharField(
source="user.password",
write_only=True,
required=True,
style={"input_type": "password"},
)
first_name = serializers.CharField(source="user.first_name")
last_name = serializers.CharField(source="user.last_name")
email = serializers.EmailField(source="user.email")
def create(self, validated_data):
user_data = validated_data.pop("user")
user = User.objects.create(**user_data)
user.set_password(user_data["password"])
user.save()
profile = UserProfile.objects.create(user=user, **validated_data)
return profile
class Meta:
model = UserProfile
fields = (
"username",
"password",
"first_name",
"last_name",
"email",
"date_updated",
"street",
"street_number",
"flat_number",
"zip_code",
"city",
"province",
"phone_number",
)