ValueError: ""должно иметь значение для поля" id ", прежде чем можно будет использовать это отношение многие ко многим - PullRequest
0 голосов
/ 22 мая 2018

Описание проблемы

У меня есть 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.

1 Ответ

0 голосов
/ 22 мая 2018

Похоже, проблема в поле 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()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...