I have a Django project with some models. The one which is giving me trouble is this one:
class Job(models.Model):
patient = models.CharField(max_length=64, null=False, blank=False)
how = MultiSelectField(choices=HOW_CHOICES, default=1)
with_who = MultiSelectField(choices=WITH_WHO_CHOICES, default=1)
accessories = MultiSelectField(choices=ACCESSORIES, null=True, blank=True)
created = models.DateTimeField(auto_now_add=True, null=False, blank=False)
scheduled = models.DateTimeField(null=True, blank=True)
arrived = models.DateTimeField(null=True, blank=True)
started = models.DateTimeField(null=True, blank=True)
completed = models.DateTimeField(null=True, blank=True)
scheduled_end = models.DateTimeField(null=True, blank=True)
detected_end = models.DateTimeField(null=True, blank=True)
approved = models.BooleanField(default=False)
area_start = models.ForeignKey(Area, on_delete=models.CASCADE, null=False, related_name='jobs_start')
area_end = models.ForeignKey(Area, on_delete=models.CASCADE, null=False, related_name='jobs_end')
comment = models.CharField(max_length=256, null=True, blank=True)
worker = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='jobs_worker')
I have this URL to GET, POST, PUT and DELETE.
router.register(r'jobs', views.JobList)
The view linked to inherits from ModelViewSet:
class JobList(LoginRequiredMixin, viewsets.ModelViewSet):
queryset = Job.objects.all()
serializer_class = JobsSerializer
For my understanding, simply by inheriting from ModelViewSet the API handles GET, POST and PUT by default. That's probably why in the web API I see the form with the data to POST (see this screenshot). The view handles the GET perfectly, but the problem comes at the POST request.
As you can see in the screenshot, there are only 3 fields to be filled for the POST data. But in the model I have more fields declared null=False
which are different from the ones in the POST data box. So, one question would be:
If I try the POST, I get this error:
NOT NULL constraint failed: job_manager_job.area_end_id
I added the create() functions to the view and the serializer in order to debug the API process. At this point I added manually some of the fields required for the Job to be created (see this other screenshot). As you can see, I added the same value in two similar keys, which brings me to another question:
The view looks like this:
class JobList(LoginRequiredMixin, viewsets.ModelViewSet):
queryset = Job.objects.all()
serializer_class = JobsSerializer
def create(self, request, *args, **kwargs):
json_data = request.data
job_serializer = self.serializer_class(data=json_data)
if job_serializer.is_valid():
job_serializer.save()
print("valid data")
pass
And the serializer like this:
class JobsSerializer(serializers.ModelSerializer):
status = serializers.SerializerMethodField('get_status')
area_from = serializers.StringRelatedField(source='area_start.name', default=None)
area_to = serializers.StringRelatedField(source='area_end.name', default=None)
assigned = serializers.SerializerMethodField('get_assigned')
info = serializers.SerializerMethodField('get_info')
def create(self, validated_data):
print(validated_data)
Job.objects.create(**validated_data)
pass
def get_status(self, a_job):
if a_job.approved:
return 'Approved'
if a_job.completed:
return 'Completed'
if a_job.started:
return 'Started'
if a_job.arrived:
return 'Arrived'
if a_job.scheduled:
return 'Scheduled'
if a_job.created:
return 'Created'
return 'Defined'
def get_info(self, a_job):
return get_job_info_options(a_job)
def get_assigned(self, a_job):
try:
a = a_job.worker.first_name + ' ' + a_job.worker.last_name
except AttributeError:
a = None
return a
class Meta:
model = Job
fields = ['pk', 'status', 'scheduled', 'scheduled_end', 'assigned', 'area_from', 'area_to', 'patient', 'info']
The problem is that in the debugger I see that the view receives all the JSON data (including the extra keys) which are passed to the serializer in self.serializer_class(data=json_data)
but the validated_data in the serializer includes only the 3 keys that are shown in the POST box.I don't know how this data disappeared in the serializer.
Check this screenshot to see that the data={...}
of the serializer is different to the validated_data
in the create function
I will appreciate a lot not only solving this problem but also if anyone could answer the questions above regarding how django works internally.
Thank you in advance.
Let's take a look:
fields = [
'pk', # unused in create
'status', # on serializer, read only method field
'scheduled', # OK
'scheduled_end', # OK
'assigned', # on serializer, read only method field
'area_from', # on serializer, read only string related field
'area_to', # on serializer, read only string related field
'patient', # OK
'info' # on serializer, read only method field
]
I got 3 OK's, so I think DRF is correct :)
Questions:
All writeable fields that are in serializer.Meta.fields end up in the form. They don't have to be required in order for the form to render them.
Field name of the serializer. But to be more accurate, Parsers are first in the pipeline, which allows you to send camelCase json and get snake_case in validated data.
The simplest solution to your problem is PrimaryKeyRelatedField. This means consumer needs to remember primary keys, but SlugRelatedField can keep things readable.