" 在使用这种多对多关系之前,"id" 字段需要有一个值,python,django,django-models,django-rest-framework,django-serializer"/>

首页 > 解决方案 > 值错误:“" 在使用这种多对多关系之前,"id" 字段需要有一个值

问题描述

问题描述

我有 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..

这令人困惑有几个原因:

  1. TaskSerializer 自己接受负载以创建新任务。
  2. 事件实际上是被创建的,但是有tasks=[]
  3. 事件和任务不是多对多的关系,所以它一定是错误的assignees,但我只是给它一个主键数组,而不是对象。同样,它仅在 POST 以创建新任务时起作用。
  4. 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.

标签: pythondjangodjango-modelsdjango-rest-frameworkdjango-serializer

解决方案


看起来问题出在任务的assignees字段上。由于它是多对多的,因此您应该在添加受让人之前分别保存每个任务:

if tasks_data is not None:
    for task_data in tasks_data:
        assignees = task_data.pop('assignees')
        task = Task.objects.create(event=event, **task_data)
        task.assignees = assignees
        task.save()

推荐阅读