ValueError:&#34; <task:task =“”object =“”>&#34;需要有一个字段的值&#34; id&#34;在此之前可以使用多对多关系

时间:2018-05-22 05:37:26

标签: python django django-models django-rest-framework django-serializer

问题描述

我有2个具有多对一关系的模型(每个事件可以有1个任务),所以我已经设置了一个与相关名称相反的ForeignKey关系,如下所示。我希望能够在事件的POST上创建一个或多个任务,所以我使用嵌套的序列化器,如documentation for writable nested serializers中显示的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.。< / p>

由于以下几个原因,这令人困惑:

  1. TaskSerializer自行接受有效负载以创建新任务。
  2. 事件实际上已创建,但tasks=[]
  3. 事件和任务不是多对多关系,因此它必须在assignees上出错,但我只是给它一组主键,而不是对象。而且,只有POST才能创建新任务。
  4. 逐行从views.py运行命令会显示serializer.is_valid()返回True,但在serializer.save()时,我收到错误(如下所示,完整错误)。
  5. 模型/视图/串行器

    活动模式

    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.
    

1 个答案:

答案 0 :(得分:2)

问题在于任务的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()