Why isn't the create method in my custom manager working?
I'm trying to create a custom manager for a model class in my Django project.
The model classes Story and Plot have a one-to-one relationship. I want a new Plot instance to be automatically created when a new Story instance is created. Right now I do this through the view function, but it fails when testing Story object creation without the view function.
I know there are a few ways to accomplish this, but I've decided to use a custom manager to add this post-creation logic by overriding the objects.create method. The relevant code is below:
class Story(models.Model):
... other fields
objects = models.Manager()
class Plot(models.Model):
... other fields
story = models.OneToOneField(Story, on_delete=models.CASCADE, null=True, default=None, related_name='plot')
class StoryManager(models.Manager):
def create(self, **kwargs):
story = super().create(**kwargs)
plot = Plot.objects.create(
... other kwargs,
story_id=story.id
)
return story
# Assign the story manager to the Story model
Story.objects = StoryManager()
Now I'm getting the following traceback while running tests that use the Story.objects.create method:
File "C:\Users\jacob\documents\programming\story-builder\storybuilder\app\models.py", line 221, in create
story = super().create(**kwargs)
File "C:\Users\jacob\documents\programming\story-builder\.venv\Lib\site-packages\django\db\models\manager.py", line 87, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
File "C:\Users\jacob\documents\programming\story-builder\.venv\Lib\site-packages\django\db\models\query.py", line 669, in create
self.model._meta._reverse_one_to_one_field_names
^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute '_meta'
I'm new to the concept of Django's ORM and how it works under the hood, so maybe this is not even the recommended way of automatically creating an object upon the creation of a different object. In my research so far, I've seen the advice of overriding the first model's save method, which is impractical because the save method is defined before the associated model class.
I also considered doing a post_save signal, but my impression is that they have a lot of potential side-effects that are hard to debug, and that most of the time it's better to use an option like the custom manager creation.
But what are your thoughts?
I believe it's because of your weird patching of the objects
attribute. It must be using the default manager until it is patched. Doing it the proper way just works:
class StoryManager(models.Manager):
def create(self, **kwargs):
story = super().create(**kwargs)
Plot.objects.create(story_id=story.id)
return story
class Story(models.Model):
objects = StoryManager()
class Plot(models.Model):
story = models.OneToOneField(
Story, on_delete=models.CASCADE, null=True, default=None, related_name="plot"
)
I believe it's because of your weird patching of the objects
attribute. It must be using the default manager until it is patched. Doing it the proper way just works:
class StoryManager(models.Manager):
def create(self, **kwargs):
story = super().create(**kwargs)
Plot.objects.create(story_id=story.id)
return story
class Story(models.Model):
objects = StoryManager()
class Plot(models.Model):
story = models.OneToOneField(
Story, on_delete=models.CASCADE, null=True, default=None, related_name="plot"
)
tests.py
class TestStory(TestCase):
def test_story(self):
story = Story.objects.create()
self.assertEqual(story.plot.story, story)