5

The Problem

I am using a model class Event that contains an optional ManyToManyField to another model class, User (different events can have different users), with a factory class EventFactory (using the Factory Boy library) with a serializer EventSerializer. I believe I have followed the docs for factory-making and serializing, but am receiving the error:

ValueError: "< Event: Test Event >" needs to have a value for field "id" before this many-to-many relationship can be used.

I know that both model instances must be created in a ManyToMany before linking them, but I do not see where the adding is even happening!

The Question

Can someone clarify how to properly use a ManyToManyField using models, factory boy, and serializers in a way I am not already doing?

The Set-Up

Here is my code:

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)

serializers.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='test@gmail.com',  
    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)

Version Info

  • Django 1.11
  • Python 2.7.10

Thank you for any help you can give!

gpsugy
  • 1,199
  • 1
  • 11
  • 25
  • I don't know if I'm missing something, but you're saving the factory generated Event in `self.event`, but in your serializer you pass in `factory`. I assume this is FactoryBoy's imported `factory`. Did you perhaps mean to put `self.event` in there instead? – malberts Feb 14 '19 at 17:32
  • Ah, sorry about that! Using `event` was leftover after I tried to narrow-down what was causing the error. `factory` as my own declared variable is the intended usage. I have updated my post to represent this change. Thank you for pointing that out! – gpsugy Feb 14 '19 at 18:43
  • That line might be your problem. `build()` doesn't save the object ([factory docs](https://factoryboy.readthedocs.io/en/latest/#using-factories)), therefore it won't have an id ([model docs](https://docs.djangoproject.com/en/dev/ref/models/instances/#auto-incrementing-primary-keys)), which seems like what the error is saying. Try it with `factories.EventFactory.create()` or just `factories.EventFactory()` (they do the same thing). What happens then? – malberts Feb 14 '19 at 18:46
  • I think you're definitely onto something. The only problem is that `create()` also saves the model instance to the database, which would defeat the purpose of my test, which is to test the POST request to create a new `Event`. I've updated the code to more clearly show that. Any thoughts? – gpsugy Feb 14 '19 at 18:59
  • As a side note, changing to `create()` does remove the error. – gpsugy Feb 14 '19 at 19:07
  • Yes, so the direct error here is you cannot have a many-to-many relationship without id's, which makes sense because that's how they get linked up. However, If you actually want to test the *creation* of an `Event` then you shouldn't use a factory to create it. Factories are for creating background test data (like stuff you need for the test, not stuff you're testing directly). Continued in next comment... – malberts Feb 14 '19 at 19:08
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/188440/discussion-between-malberts-and-gpsugy). – malberts Feb 14 '19 at 19:08

1 Answers1

2

Regarding the error: It seems like the missing id is due to the use of .build() instead of .create() (or just EventFactory()). The former does not save the model, and therefore it does not get an id value, whereas the latter does (refer to factory docs and model docs).

I suspect the serializer still expects the object to have an id, even though the many-to-many relationship is optional, because it cannot enforce a potential relationship without an id.

However, there might be a simpler solution to the actual task. The above method is a way of generating the POST data passed to post_api_call(). If this data was instead created manually, then both the factory and serializer become unnecessary. The explicit data method might even be better from a test-perspective, because you can now see the exact data which has to produce an expected outcome. Whereas with the factory and serializer method it is much more implicit in what is actually used in the test.

malberts
  • 2,488
  • 1
  • 11
  • 16