Описание проблемы
У меня есть 2 модели, которые имеют отношение многие к одному (может быть 1-много задач на событие), поэтому я настроил обратную связь ForeignKey со связанным именем, как выпосмотрим ниже.Я хочу иметь возможность создавать одну или несколько из этих задач на POST события, поэтому я использую вложенные сериализаторы, как показано в документации для записываемых вложенных сериализаторов для Django Rest Framework (я использовал этометод с большим успехом в прошлом).
Я могу создать задачу без каких-либо затруднений, и я могу создать событие без проблем (с tasks=[]
).
Проблема в том, что на этот раз, по какой-то причине, если я вложу ту же самую полезную нагрузку, которую я использую для создания Задачи в массив tasks
, я получаю следующую ошибку: ValueError: "<Task: Task object>" needs to have a value for field "id" before this many-to-many relationship can be used.
.
Это сбивает с толку по нескольким причинам:
- TaskSerializer самостоятельно принимает полезную нагрузку для создания новой задачи.
- Событие фактически получаетсоздано, но с
tasks=[]
- События и задачи не является отношением «многие ко многим», поэтому должно быть ошибка на
assignees
, но я просто даю ему массив первичных ключей, а необъекты.И снова, это работает, когда просто POST создает новую задачу. - Запуск команд из
views.py
строка за строкой показывает, что serializer.is_valid()
возвращает True
, но при serializer.save()
я получаю ошибку(полная ошибка показана ниже).
Модели / Представления / Сериализаторы
Модель события
class Event(models.Model):
owner = models.ForeignKey(ExtendedUser, null=True, related_name='calendar_events')
site = models.ForeignKey(Site, null=True, blank=True, default=None, related_name='calendar_events')
title = models.CharField(max_length=200, blank=True, default='')
description = models.TextField(max_length=200, blank=True, default='')
invitees = models.ManyToManyField(ExtendedUser, default=[], blank=True, related_name='events_invited')
start = models.DateTimeField(null=True, default=None)
stop = models.DateTimeField(null=True, default=None)
frequency = models.ForeignKey(Frequency, null=True, default=None, blank=True)
recurring = models.BooleanField(default=False)
all_day = models.BooleanField(default=False)
# Administrative Fields
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
Модель задачи
class Task(models.Model):
owner = models.ForeignKey(ExtendedUser, null=True, related_name='tasks')
site = models.ForeignKey(Site, null=True, blank=True, default=None, related_name='tasks')
assignees = models.ManyToManyField(ExtendedUser, default=[], blank=True, related_name='tasks_assigned')
title = models.CharField(max_length=200, blank=True, default='')
description = models.TextField(max_length=200, blank=True, default='')
type = models.ForeignKey(TaskType, null=True, default=None)
template = models.ForeignKey(Template, null=True, default=None)
data = JSONField(null=True, blank=True, default=None)
completed = models.BooleanField(default=False)
completed_at = models.DateTimeField(null=True, blank=True, default=None)
completed_by = models.ForeignKey(ExtendedUser, null=True, blank=True, default=None, related_name='task_completed')
priority = models.ForeignKey(Priority, null=True, default=None, related_name='task')
# Optionally, a task can be connected to an Event or Workflow
event = models.ForeignKey(Event, null=True, default=None, related_name='tasks')
workflow = models.ForeignKey(Workflow, null=True, default=None, related_name='tasks')
# Administrative Fields
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
Просмотр задач для GET / POST
class UserTaskListCreateView(generics.ListCreateAPIView):
permission_classes = [IsAuthenticated, IsCurrentUserOrAgentOrDeveloper]
serializer_class = TaskDetailSerializer
pagination_class = Pagination
def get_queryset(self):
user_slug = self.kwargs['user_slug']
return Task.objects.filter(
Q(owner__slug=user_slug) | Q(assignees__slug=user_slug)
)
def create(self, request, user_slug):
request.data['owner'] = ExtendedUser.objects.get(slug=user_slug).id
serializer = TaskSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
Просмотр событий для POST
class UserScheduleListCreateView(generics.ListCreateAPIView):
permission_classes = [IsAuthenticated, IsCurrentUserOrAgentOrDeveloper]
serializer_class = EventDetailSerializer
pagination_class = Pagination
def create(self, request, user_slug):
request.data['owner'] = ExtendedUser.objects.get(slug=user_slug).id
serializer = EventSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
Сериализатор задач
class TaskSerializer(serializers.ModelSerializer):
site = SiteField()
data = serializers.JSONField()
class Meta:
model = Task
fields = [
'id',
'owner',
'site',
'assignees',
'type',
'title',
'description',
'template',
'data',
'completed',
'completed_at',
'completed_by',
'priority',
'event',
'workflow',
'created',
'updated'
]
Сериализатор событий
class EventSerializer(serializers.ModelSerializer):
site = SiteField()
tasks = TaskSerializer(many=True)
class Meta:
model = Event
fields = [
'id',
'owner',
'site',
'title',
'description',
'invitees',
'start',
'stop',
'frequency',
'recurring',
'all_day',
'tasks',
'created',
'updated'
]
def create(self, validated_data):
invitees = validated_data.pop('invitees', None)
tasks_data = validated_data.pop('tasks', None)
event = Event.objects.create(**validated_data)
for invitee in invitees:
event.invitees.add(invitee)
event.save()
if tasks_data is not None:
for task_data in tasks_data:
task = Task.objects.create(event=event, **task_data)
return event
Полный вывод ошибок
ValueError Traceback (most recent call last)
<ipython-input-12-4bde7bc40c4c> in <module>()
----> 1 serializer.save()
~/.virtualenvs/tomis/lib/python3.5/site-packages/rest_framework/serializers.py in save(self, **kwargs)
212 )
213 else:
--> 214 self.instance = self.create(validated_data)
215 assert self.instance is not None, (
216 '`create()` did not return an object instance.'
~/Desktop/sandboxes/python/tomis-backend/component/calendar/serializers.py in create(self, validated_data)
69 print('event:', event)
70 print('task_data:', task_data)
---> 71 Task.objects.create(event=event, **task_data)
72 print('thingy')
73 # event.tasks.create(**task_data)
~/.virtualenvs/tomis/lib/python3.5/site-packages/django/db/models/manager.py in manager_method(self, *args, **kwargs)
83 def create_method(name, method):
84 def manager_method(self, *args, **kwargs):
---> 85 return getattr(self.get_queryset(), name)(*args, **kwargs)
86 manager_method.__name__ = method.__name__
87 manager_method.__doc__ = method.__doc__
~/.virtualenvs/tomis/lib/python3.5/site-packages/django/db/models/query.py in create(self, **kwargs)
390 and returning the created object.
391 """
--> 392 obj = self.model(**kwargs)
393 self._for_write = True
394 obj.save(force_insert=True, using=self.db)
~/.virtualenvs/tomis/lib/python3.5/site-packages/django/db/models/base.py in __init__(self, *args, **kwargs)
566 if prop in property_names or opts.get_field(prop):
567 if kwargs[prop] is not _DEFERRED:
--> 568 _setattr(self, prop, kwargs[prop])
569 del kwargs[prop]
570 except (AttributeError, FieldDoesNotExist):
~/.virtualenvs/tomis/lib/python3.5/site-packages/django/db/models/fields/related_descriptors.py in __set__(self, instance, value)
534 RemovedInDjango20Warning, stacklevel=2,
535 )
--> 536 manager = self.__get__(instance)
537 manager.set(value)
538
~/.virtualenvs/tomis/lib/python3.5/site-packages/django/db/models/fields/related_descriptors.py in __get__(self, instance, cls)
511 return self
512
--> 513 return self.related_manager_cls(instance)
514
515 def _get_set_deprecation_msg_params(self):
~/.virtualenvs/tomis/lib/python3.5/site-packages/django/db/models/fields/related_descriptors.py in __init__(self, instance)
828 raise ValueError('"%r" needs to have a value for field "%s" before '
829 'this many-to-many relationship can be used.' %
--> 830 (instance, self.pk_field_names[self.source_field_name]))
831 # Even if this relation is not to pk, we require still pk value.
832 # The wish is that the instance has been already saved to DB,
ValueError: "<Task: Task object>" needs to have a value for field "id" before this many-to-many relationship can be used.