Reputation: 570
I have the Task model:
class Task(models.Model):
name = models.CharField(max_length=200, blank=True)
description = models.TextField(max_length=1000, blank=True)
completed = models.BooleanField(default=False)
date_created = models.DateField(auto_now_add=True)
due_date = models.DateField(null=True, blank=True)
date_modified = models.DateField(auto_now=True)
tasklist = models.ForeignKey(Tasklist, null=True, related_name='tasks', on_delete=models.CASCADE)
tags = models.ManyToManyField(TaskType, related_name='tasks')
And class TaskType (tag in other words):
class TaskType(models.Model):
name = models.CharField(max_length=200)
I also have TaskSerializer:
class TaskSerializer(serializers.ModelSerializer):
tags = serializers.SlugRelatedField(many=True, slug_field='name', queryset=TaskType.objects.all())
class Meta:
model = Task
fields = '__all__'
read_only_fields = ('date_created', 'date_modified', 'tasklist')
When I create a Task, to add some tags I need to create them in appropriate view firstly, but I want them to be created on the fly.
So in case of editing the task, I added update
method:
def update(self, request, *args, **kwargs):
instance = self.get_object()
tag_names = request.data.get('tags', [])
for tag_name in tag_names:
tag, created = TaskType.objects.get_or_create(name=tag_name)
instance.tags.add(tag)
serializer = self.serializer_class(instance=instance, data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
It works fine, but when I add new tags when creating a new task it fails (400 bad request):
{
"tags": [
"Object with name=%new_tag% does not exist."
]
}
I figured out that it would be a good way to create appropriate tag object before creating Task with it, so I added perform_create
method:
def perform_create(self, serializer):
print('debug')
tag_names = self.request.data.get('tags', [])
for tag_name in tag_names:
tag, created = TaskType.objects.get_or_create(name=tag_name)
list_id = self.kwargs.get('list_id', None)
try:
tasklist = Tasklist.objects.get(pk=list_id)
except Tasklist.DoesNotExist:
raise NotFound()
serializer.save(tasklist=tasklist)
It doesn't help me, actually I am not sure if perform_create method at least is called, because I see no print('debug') in the console (when I create Task with an existing tag I see it). So the question is how to change perform_create method to be able to create fresh Tasks without creating Tag firstly.
Upvotes: 1
Views: 1256
Reputation: 10806
First, you don't need to manually add
tags in your view methods. The serializer will do it for you.
Second, the update
method is used when you update your model. When creating you need to override create
method, perform_create
works, but happens too late:
def create(self, request, *args, **kwargs):
tag_names = request.data.get('tags', [])
for tag_name in tag_names:
TaskType.objects.get_or_create(name=tag_name)
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
Third, once you've created new tags, call super().*method*
and let the framework do the work for you:
def update(self, request, *args, **kwargs):
tag_names = request.data.get('tags', [])
for tag_name in tag_names:
TaskType.objects.get_or_create(name=tag_name)
return super().update(request, *args, **kwargs)
def create(self, request, *args, **kwargs):
tag_names = request.data.get('tags', [])
for tag_name in tag_names:
TaskType.objects.get_or_create(name=tag_name)
return super().create(request, *args, **kwargs)
There is also partial_update
method you need to add if you are planning to use PATCH method, so you might be better off overriding your serializer's to_internal_value
method instead of each of those:
class TaskSerializer(serializers.ModelSerializer):
tags = serializers.SlugRelatedField(
many=True, slug_field='name', queryset=TaskType.objects.all())
class Meta:
model = Task
fields = '__all__'
read_only_fields = ('date_created', 'date_modified', 'tasklist')
def to_internal_value(self, data):
for tag_name in data.get('tags', []):
TaskType.objects.get_or_create(name=tag_name)
return super().to_internal_value(data)
Upvotes: 1
Reputation: 9245
Try editing your view like this,
def update(self, request, *args, **kwargs):
instance = self.get_object()
tag_names = request.data.get('tags', [])
serializer = self.serializer_class(instance=instance, data=request.data)
if serializer.is_valid(raise_exception=True)
new_object = serializer.save()
if new_object:
for tag_name in tag_names:
tag, created = TaskType.objects.get_or_create(name=tag_name)
new_object.tags.add(tag)
return Response(serializer.data)
Upvotes: 0