Django: как правильно использовать ManyToManyField с фабриками и сериализаторами Factory Boy?

Проблема

Я использую класс модели Event, который содержит необязательный объект ManyToManyField для другого класса модели, User (у разных событий могут быть разные пользователи), с фабричным классом EventFactory (с использованием библиотеки Factory Boy) и сериализатором EventSerializer . Я считаю, что следовал документам по производству и сериализации на заводе, но получаю сообщение об ошибке:

ValueError: «‹ Event: Test Event >» должно иметь значение для поля «id», прежде чем можно будет использовать эту связь «многие ко многим».

Я знаю, что оба экземпляра модели должны быть созданы в ManyToMany перед их связыванием, но я не вижу, где вообще происходит добавление!

Вопрос

Может ли кто-нибудь объяснить, как правильно использовать ManyToManyField с помощью моделей, factory boy и сериализаторов так, как этого еще не делаю я?

Настройка

Вот мой код:

models.py

@python_2_unicode_compatible
class Event(CommonInfoModel):
    users = models.ManyToManyField(User, blank=True, related_name='events')
    # other basic fields...

factories.py

class EventFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.Event

    @factory.post_generation
    def users(self, create, extracted, **kwargs):
        if not create:
            # Simple build, do nothing.
            return

        if extracted:
            # A list of users were passed in, use them
            # NOTE: This does not seem to be the problem. Setting a breakpoint                     
            # here, this part never even fires
            for users in extracted:
                self.users.add(users)

сериализаторы.py

class EventSerializer(BaseModelSerializer):
    serialization_title = "Event"
    # UserSerializer is a very basic serializer, with no nested 
    # serializers
    users = UserSerializer(required=False, many=True)

    class Meta:
        model = Event
        exclude = ('id',)

test.py

class EventTest(APITestCase):
@classmethod
def setUpTestData(cls):
    cls.user = User.objects.create_user(email='[email protected]',  
    password='password')

def test_post_create_event(self):
    factory = factories.EventFactory.build()
    serializer = serializers.EventSerializer(factory)

    # IMPORTANT: Calling 'serializer.data' is the exact place the error occurs!
    # This error does not occur when I remove the ManyToManyField
    res = self.post_api_call('/event/', serializer.data)

Информация о версии

  • Джанго 1.11
  • Питон 2.7.10

Спасибо за любую помощь, которую вы можете оказать!


person gpsugy    schedule 14.02.2019    source источник
comment
Я не знаю, упустил ли я что-то, но вы сохраняете сгенерированное фабрикой событие в self.event, но в своем сериализаторе вы передаете factory. Я предполагаю, что это импортированный FactoryBoy factory. Возможно, вы хотели вместо этого поставить self.event?   -  person malberts    schedule 14.02.2019
comment
Ах, прости за это! Использование event осталось после того, как я попытался сузить круг причин, вызывающих ошибку. factory поскольку моя собственная объявленная переменная является предполагаемым использованием. Я обновил свой пост, чтобы представить это изменение. Спасибо, что указали на это!   -  person gpsugy    schedule 14.02.2019
comment
Эта линия может быть вашей проблемой. build() не сохраняет объект (фабричные документы), поэтому он выиграл не имеют идентификатора (документы модели), что похоже на то, о чем говорит ошибка. Попробуйте с factories.EventFactory.create() или просто factories.EventFactory() (они делают то же самое). Что происходит тогда?   -  person malberts    schedule 14.02.2019
comment
Я думаю, ты определенно на что-то наткнулся. Единственная проблема заключается в том, что create() также сохраняет экземпляр модели в базе данных, что противоречило бы цели моего теста, состоящей в проверке запроса POST для создания нового экземпляра Event. Я обновил код, чтобы более четко показать это. Какие-нибудь мысли?   -  person gpsugy    schedule 14.02.2019
comment
В качестве примечания изменение на create() действительно устраняет ошибку.   -  person gpsugy    schedule 14.02.2019
comment
Да, так что прямая ошибка здесь в том, что у вас не может быть отношений «многие ко многим» без идентификаторов, что имеет смысл, потому что именно так они связаны. Однако, если вы действительно хотите протестировать создание Event, вам не следует использовать фабрику для его создания. Фабрики предназначены для создания фоновых тестовых данных (например, вещей, которые вам нужны для теста, а не вещей, которые вы тестируете напрямую). Продолжение в следующем комментарии...   -  person malberts    schedule 14.02.2019
comment
Давайте продолжим это обсуждение в чате.   -  person malberts    schedule 14.02.2019


Ответы (1)


Что касается ошибки: похоже, что отсутствующий id связан с использованием .build() вместо .create() (или просто EventFactory()). Первый не сохраняет модель и, следовательно, не получает значение id, а второй получает (см. заводские документы и документы модели).

Я подозреваю, что сериализатор по-прежнему ожидает, что объект будет иметь id, даже если отношение «многие ко многим» является необязательным, поскольку он не может обеспечить потенциальное отношение без id.

Тем не менее, может быть более простое решение реальной задачи. Вышеупомянутый метод — это способ генерации данных POST, передаваемых в post_api_call(). Если вместо этого эти данные были созданы вручную, то и фабрика, и сериализатор становятся ненужными. Метод явных данных может быть даже лучше с точки зрения тестирования, потому что теперь вы можете видеть точные данные, которые должны дать ожидаемый результат. Принимая во внимание, что с методом фабрики и сериализатора это гораздо более неявно в том, что фактически используется в тесте.

person malberts    schedule 14.02.2019