I'm in trouble creating a bunch of related models using DRF nested serializers.
They are failing validation on the foreign key.
Models
class Employee(models.Model):
user = models.OneToOneField(User) # Django user
...
class Task(models.Model):
author = models.ForeignKey(Employee, related_name='tasks')
title = models.CharField(max_length=64)
...
class EmployeeTarget(models.Model):
employee = models.ForeignKey(Employee, null=False)
task = models.ForeignKey(Task, null=False, related_name='employee_targets')
...
Objective
Basically I have the Employees already created, and I want to create a Task and related EmployeeTarget in a single request, getting the request user as the author. JSON request example:
{
"title": "Lorem Ipsum",
"employee_targets": [
{ "employee": 10 },
{ "employee": 11 }]
}
/* or */
{
"title": "Lorem Ipsum",
"employee_targets": [10,11]
}
Serializers
class EmployeeSerializer(serializers.ModelSerializer):
name = serializers.CharField(source="user.get_full_name", read_only=True)
email = serializers.CharField(source="user.email", read_only=True)
class Meta:
model = Employee
class EmployeeTargetSerializer(serializers.ModelSerializer):
employee = EmployeeSerializer()
class Meta:
model = EmployeeTarget
class TaskSerializer(base.ModelSerializer):
employee_targets = EmployeeTargetSerializer(many=True, required=False)
class Meta:
model = Task
def create(self, validated_data):
employee_target_data = validated_data.pop('employee_targets')
task = Task.objects.create(**validated_data)
EmployeeTarget.objects.create(task=task, **employee_target_data)
return task
ViewSet
class TaskViewSet(ModelViewSet):
serializer_class = TaskSerializer
def get_queryset(self):
request_employee = self.request.user.employee
return Task.objects.filter(Q(author=request_employee) |
Q(employee_targets__employee=request_employee))
def perform_create(self, serializer):
serializer.save(author=self.request.user.employee)
Result
I'm getting 400 BAD REQUEST with the following error:
{
"employee_targets": [
{
"employee": {
"non_field_errors": ["Invalid data. Expected a dictionary, but got int."]
},
"task": ["This field is required."]
}
],
"author": ["This field is required."]
}
The employee error was expected, but I haven't figured out how to create them using only the ID.
The bigger problem here is the employee_targets failing validation at the task FK, before the enclosing TaskSerializer specify them at create method.
Can you try with this:
class EmployeeSerializer(serializers.ModelSerializer):
name = serializers.CharField()
email = serializers.CharField()
class Meta:
depth = 2
model = Employee
Related
I have made Sample model as a ForeignKey to other two child model, and sample_ID as a primary key using lookup_field.
models.py
class Sample(models.Model):
sample_ID = models.CharField(max_length=10)
class Label(models.Model):
AI_sample = models.ForeignKey(Sample, null=True, on_delete=models.CASCADE, related_name='AI_sample')
AI_position = models.CharField(max_length=50)
AI_defect = models.CharField(max_length=50)
AI_tool = models.CharField(max_length=50)
class Manual(models.Model):
manual_sample = models.ForeignKey(Sample, null=True, on_delete=models.CASCADE, related_name='manual_sample')
manual_position = models.CharField(max_length=50)
manual_defect = models.CharField(max_length=50)
manual_tool = models.CharField(max_length=50)
Now my API endpoint looks like this:
{
"id": 3,
"sample_ID": "a000001",
"AI_sample": [
{
"id": 3,
"AI_position": "Position 1",
"AI_defect": "Defect 1",
"AI_tool": "Tool 1"
}
],
"manual_sample": [
{
"id": 8,
"manual_position": "Position 3",
"manual_defect": "Scratch",
"manual_tool": "Tool 2"
}
]
}
Currently I am using reverse lookup of foreignKey to get the child data from sample_ID. Now I can easily make GET request by destructing the data like this:
axios.get(url)
.then(resp => {
let data = [];
const manual_sample = resp.data.manual_sample;
manual_sample.forEach(el => {
data.push({
id: el.id,
manual_position: el.manual_position,
manual_defect: el.manual_defect,
manual_tool: el.manual_tool
});
});
})
However, I am not able to make POST request out of a child data, because the I cannot set the primary key of sample_ID in a nested object. I am sending the manual_sample data request like this:
{
"id": 9,
"manual_position": "Position 5",
"manual_defect": "Crack",
"manual_tool": "Tool 4"
}
Now, is there a possible soluation to make a POST request out of manual_sample data under sample_ID: a000001.
Here is my serializers.py & views.py if you are interested:
serializers.py
class LabelSerializer(serializers.ModelSerializer):
class Meta:
model = Label
fields = ['id', 'AI_position', 'AI_defect', 'AI_tool']
class ManualSerializer(serializers.ModelSerializer):
class Meta:
model = Manual
fields = ['id', 'manual_position', 'manual_defect', 'manual_tool']
class SampleSerialier(serializers.ModelSerializer):
AI_sample = LabelSerializer(many=True, read_only=True)
manual_sample = ManualSerializer(many=True, read_only=True)
class Meta:
model = Sample
fields = ['id', 'sample_ID', 'AI_sample', 'manual_sample']
views.py
class SampleViewSet(viewsets.ModelViewSet):
queryset = Sample.objects.all()
serializer_class = SampleSerialier
lookup_field = "sample_ID"
class LabelViewSet(viewsets.ModelViewSet):
queryset = Label.objects.all()
serializer_class = LabelSerializer
class ManualViewSet(viewsets.ModelViewSet):
queryset = Manual.objects.all()
serializer_class = ManualSerializer
I don't know if I described it correctly and precisely, but please free to leave a comment, and let me know if I missed something. Thanks
i can't send post data to api using django rest framework. i used postman to send data only user part is adding to the database , the activity of user is rejecting . i can't figure out problem can anyone help me to solve this problem
sending post data to api
{
"name": "karen",
"username": "karen",
"timezone": "US/Samoa",
"activity_periods": [
{
"log_in": "2020-06-09T21:53:25.120897Z",
"log_out": null
},
{
"log_in": "2020-06-09T22:02:35.289891Z",
"log_out": null
},
{
"log_in": "2020-06-09T22:03:36.425212Z",
"log_out": null
}
]
}
but only the user data is stored the activity is ignored
like this
{
"name": "karen",
"username": "karen",
"timezone": "US/Samoa",
"activity_periods": []
}
how can i add activity data to user...?
models.py
class User(models.Model):
name = models.CharField(max_length=20)
username = models.CharField(max_length=20)
password = models.CharField(max_length=20)
timezone = models.CharField(max_length=32, choices=TIMEZONES, default='UTC')
def __str__(self):
return self.name
class Activity(models.Model):
user = models.ForeignKey(User, related_name="activity_periods",on_delete=models.CASCADE,null=True, blank=True)
log_in = models.DateTimeField(null=True, blank=True)
log_out = models.DateTimeField(null=True, blank=True)
def __str__(self):
return self.user.name
serializers.py
class ActivitySerializer(serializers.ModelSerializer):
class Meta:
model = Activity
fields = ['log_in', 'log_out']
class UserSerializer(serializers.ModelSerializer):
# Passing login Logout to User
activity_periods = ActivitySerializer(many=True, read_only=True)
class Meta:
model = User
fields = ['name', 'username','timezone', 'activity_periods']
views.py
class ActivityListView(generics.ListCreateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
name = "activity-list"
urls.py
path('rest/',views.ActivityListView.as_view())
how can i add activity data to user...?
In your UserSerializer, you have the following line:
activity_periods = ActivitySerializer(many=True, read_only=True)
Since read_only is set to True, when you POST data it will not be written to the database. Try setting it to False instead.
I am trying to get a Json of elements with their related elements
I had two tables, Service and Room. One service have many rooms. I would like to get the service where have room_id = x.
Models
class Service(models.Model):
name = models.CharField(max_length=255, blank=True, null=True)
class Meta:
managed = True
db_table = 'Service'
class Room(models.Model):
name = models.CharField(max_length=255, blank=True, null=True)
service = models.ForeignKey(Service, models.DO_NOTHING, blank=True,
null=True)
class Meta:
managed = True
db_table = 'Room'
Serializer
class ServiceSerializer(serializers.ModelSerializer):
room_set = RoomSerializer(many=True, read_only=True)
class Meta:
model = Service
fields = ('name','room_set')
class RoomSerializer(serializers.ModelSerializer):
class Meta:
model = Room
fields = '__all__'
View
queryset = Service.objects.filter(room__id=1)
serializer = ServiceSerializer(queryset, many=True)
return JsonResponse(serializer.data, safe=False)
I expect a Json like this:
{
"name": "Hotel1",
"room_set": [
{
"id": 1,
"name": "Room1"
},
But I get this:
{
"name": "Hotel1",
"room_set": [
{
"id": 1,
"name": "Room1",
},
{
"id": 2,
"name": "Room2",
},
{
"id": 3,
"name": "Room3",
}
}
Is it possible to get a json like the one I'm expecting?
You can patch the set by adding a custom Prefetch object [Django-doc] with a filtered queryset, like:
from django.db.models import Prefetch
queryset = Service.objects.filter(
room__id=1
).prefetch_related(
Prefetch('room_set', queryset=Room.objects.filter(id=1), to_attr='room_set1')
)
serializer = ServiceSerializer(queryset, many=True)
return JsonResponse(serializer.data, safe=False)
and let the Serializer parse the new related manager:
class ServiceSerializer(serializers.ModelSerializer):
room_set = RoomSerializer(many=True, read_only=True, source='room_set1')
class Meta:
model = Service
fields = ('name','room_set1')
class RoomSerializer(serializers.ModelSerializer):
class Meta:
model = Room
fields = '__all__'
You can pass the room id via the serializer context and filter accordingly inside a SerializerMethodField()
class ServiceSerializer(serializers.ModelSerializer):
rooms = serializers.SerializerMethodField()
class Meta:
model = Service
fields = ('name','rooms')
get_rooms(self,service):
room_id = self.get_context('room')
if room_id:
queryset = service.rooms_set.filter(id=room_id)
return RoomSerializer(queryset,many=True).data
return RoomSerializer(service.rooms_set.all(),many=True).data
serializer = ServiceSerializer(queryset, many=True,context={'room':1})
return JsonResponse(serializer.data, safe=False)
This's how to do it via the serializer and it's highly customizable , Willem Van Onsem's answer is brief enough , but it also requires two queries the same as of mine.
I'm trying to use the Django Rest-Framework to produce some JSON that shows all the user's posts, but also shows the images for that post. Image is a foreign key to Post. Here are the models:
models.py
class Post(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
status = models.CharField(max_length=200)
class Image(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE)
img = models.CharField(max_length=120)
views_count = models.IntegerField(default=0)
views.py
class GetPosts(ListAPIView):
serializer_class = PostSerializer
def get_queryset(self):
requested_user = get_requested_user(self)
return Post.objects.filter(user=requested_user).order_by('-created_at')
def get_requested_user(self):
filter_kwargs = {'username': self.kwargs['username']}
return get_object_or_404(User.objects.all(), **filter_kwargs)
serializers.py
class PostSerializer(serializers.ModelSerializer):
image_img = serializers.RelatedField(source='Image', read_only=True)
class Meta:
model = Post
fields = ('status', 'image_img ')
In the serializers.py, I'd like to show all of the fields for Image (img, views_count) What I get with my current code is this:
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"status": "I am number 1"
}
]
}
Which contains the user's posts, but not the user's posts and each post's images. Note: Query url looks like this: /api/posts/user/
You should use Nested serializer here,
class ImageSerializer(serializers.ModelSerializer):
class Meta:
model = Image
fields = ('img',)
class PostSerializer(serializers.ModelSerializer):
image_img = ImageSerializer(source='image_set', many=True)
class Meta:
model = Post
fields = '__all__'
Hence the response will be like,
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"status": "I am number 1",
"image_img": [
{"img": "image_url"},
{"img": "image_url"},
....
]
}
]
}
How to display all field of model class in serializer?
From the doc,
You can also set the fields attribute to the special value '__all__' to indicate that all fields in the model should be used.
Reference
1. DRF- Nested Realtions
2. source argument
3. Specifying which fields to include
Related to this Topic
Hi,
I cannot follow the answer at the attached topic, because an ID is missing after serialization.
Model.py
class Owner(models.Model):
name = models.CharField(db_index=True, max_length=200)
class Car(models.Model):
name = models.CharField(db_index=True, max_length=200)
LCVS = models.ForeignKey(Owner)
View.py
class OwnerViewSet(viewsets.ModelViewSet):
queryset = Owner.objects.all()
serializer_class = OwnerSerializer
class CarViewSet(viewsets.ModelViewSet):
serializer_class = CarSerializer
queryset = Car.objects.all()
Serializer.py
class OwnerSerializer(serializers.ModelSerializer):
class Meta:
model = Owner
fields = ('id', 'name')
class CarSerializer(serializers.ModelSerializer):
owner = OwnerSerializer()
class Meta:
model = Car
fields = ('id', 'name', 'owner')
def create(self, validated_data):
tmp_owner = Owner.objects.get(id=validated_data["car"]["id"])
car = Car.objects.create(name=self.data['name'],owner=tmp_owner)
return car
Now i send the following request :
Request URL:http://localhost:9000/api/v1/cars
Request Method:POST
Request Paylod :
{
"name": "Car_test",
"ower": {
"id":1,
"name": "Owner_test"
}
}
But, here the validated_data don't contain the owner ID !
Traceback | Local vars
validated_data {u'Owner': OrderedDict([(u'name', u'Owner_test')]), u'name': u'Car_test'}
#Kevin Brown :
Workful ! Thanks
I'll validate your answer but I get a new problem...
Now when I try to put a new Owner, an error raise :
{
"id": [
"This field is required."
]
}
I had to create a new serializer ?
Any AutoFields on your model (which is what the automatically generated id key is) are set to read-only by default when Django REST Framework is creating fields in the background. You can confirm this by doing
repr(CarSerializer())
and seeing the field generated with read_only=True set. You can override this with the extra_kwargs Meta option which will allow you to override it and set read_only=False.
class OwnerSerializer(serializers.ModelSerializer):
class Meta:
model = Owner
fields = ('id', 'name')
extra_kwargs = {
"id": {
"read_only": False,
"required": False,
},
}
This will include the id field in the validated_data when you need it.