Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

django_get_or_create does not work when using cutom model manager create method #1094

Open
g-kartik opened this issue Sep 14, 2024 · 1 comment

Comments

@g-kartik
Copy link

g-kartik commented Sep 14, 2024

Description

A clear and concise description of what the bug is.
django_get_or_create does not work when using custom model manger create method

To Reproduce

Share how the bug happened:

  • Override the default model manager create method with your custom method
  • Define the django_get_or_create fields in the Meta class. These fields should be unique fields in the Model as well as Factory definition
  • Try create an instance of the factory two times with same values for the unique fields. Second time, the operation would fail giving unique constraint error.
Model / Factory code
Factory
class VKUserBaseFactory(factory.django.DjangoModelFactory):
    name = factory.sequence(lambda n: f"vkuser {n}")
    email = factory.sequence(lambda n: f"vkuser{n}@vk.com")
    password = factory.django.Password('password')
    is_staff = False
    is_superuser = False
    is_active = True
    is_email_verified = True

    class Meta:
        model = models.VKUser
        django_get_or_create = ('email',)

    @classmethod
    def _create(cls, model_class, *args, **kwargs):
        manager = cls._get_manager(model_class)
        user = manager.create_user(*args, **kwargs)
        return user
Model
class VKUser(PermissionsMixin, AbstractBaseUser):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    name = models.CharField(max_length=100, blank=False)
    email = models.EmailField(unique=True)
    is_staff = models.BooleanField(default=False)
    is_active = models.BooleanField(
        _('active'),
        default=True,
        help_text=_(
            'Designates whether this user should be treated as active. '
            'Unselect this instead of deleting accounts.'
        ),
    )
    date_joined = models.DateTimeField(_('date joined'), default=timezone.now, null=True)
    is_email_verified = models.BooleanField(default=True)
    email_verification = GenericRelation(EmailVerification, content_type_field='content_type',
                                         object_id_field='object_id')

    objects = VKUserManager()
The issue

Add a short description along with your code
user1 = VKUserFactory(email=vkuser0@vk.com)
user2 = VKUserFactory(email=vkuser0@vk.com)

UniqueViolation                           Traceback (most recent call last)
File /opt/venv/lib/python3.8/site-packages/django/db/backends/utils.py:84, in CursorWrapper._execute(self, sql, params, *ignored_wrapper_args)
     [83](https://vscode-remote+attached-002dcontainer-002b6d696e646b6f7368736572766572.vscode-resource.vscode-cdn.net/opt/venv/lib/python3.8/site-packages/django/db/backends/utils.py:83) else:
---> [84](https://vscode-remote+attached-002dcontainer-002b6d696e646b6f7368736572766572.vscode-resource.vscode-cdn.net/opt/venv/lib/python3.8/site-packages/django/db/backends/utils.py:84)     return self.cursor.execute(sql, params)

UniqueViolation: duplicate key value violates unique constraint "authentication_vkuser_email_key"
DETAIL:  Key (email)=(vkuser0@vk.com) already exists.


The above exception was the direct cause of the following exception:

IntegrityError                            Traceback (most recent call last)
Cell In[6], [line 1](vscode-notebook-cell:?execution_count=6&line=1)
----> [1](vscode-notebook-cell:?execution_count=6&line=1) user1 = auth_factory.VKUserFactory(email='vkuser0@vk.com')

File /opt/venv/lib/python3.8/site-packages/factory/base.py:43, in FactoryMetaClass.__call__(cls, **kwargs)
     [41](https://vscode-remote+attached-002dcontainer-002b6d696e646b6f7368736572766572.vscode-resource.vscode-cdn.net/opt/venv/lib/python3.8/site-packages/factory/base.py:41)     return cls.build(**kwargs)
     [42](https://vscode-remote+attached-002dcontainer-002b6d696e646b6f7368736572766572.vscode-resource.vscode-cdn.net/opt/venv/lib/python3.8/site-packages/factory/base.py:42) elif cls._meta.strategy == enums.CREATE_STRATEGY:
---> [43](https://vscode-remote+attached-002dcontainer-002b6d696e646b6f7368736572766572.vscode-resource.vscode-cdn.net/opt/venv/lib/python3.8/site-packages/factory/base.py:43)     return cls.create(**kwargs)
     [44](https://vscode-remote+attached-002dcontainer-002b6d696e646b6f7368736572766572.vscode-resource.vscode-cdn.net/opt/venv/lib/python3.8/site-packages/factory/base.py:44) elif cls._meta.strategy == enums.STUB_STRATEGY:
     [45](https://vscode-remote+attached-002dcontainer-002b6d696e646b6f7368736572766572.vscode-resource.vscode-cdn.net/opt/venv/lib/python3.8/site-packages/factory/base.py:45)     return cls.stub(**kwargs)

File /opt/venv/lib/python3.8/site-packages/factory/base.py:539, in BaseFactory.create(cls, **kwargs)
    [533](https://vscode-remote+attached-002dcontainer-002b6d696e646b6f7368736572766572.vscode-resource.vscode-cdn.net/opt/venv/lib/python3.8/site-packages/factory/base.py:533) @classmethod
...
---> [84](https://vscode-remote+attached-002dcontainer-002b6d696e646b6f7368736572766572.vscode-resource.vscode-cdn.net/opt/venv/lib/python3.8/site-packages/django/db/backends/utils.py:84)     return self.cursor.execute(sql, params)

IntegrityError: duplicate key value violates unique constraint "authentication_vkuser_email_key"
DETAIL:  Key (email)=(vkuser0@vk.com) already exists.

Notes

Add any notes you feel relevant here :)
The expected behavior should be that it should try to get the instance using the django_get_or_create fields no matter if any custom manager method is defined.

@rbarrois
Copy link
Member

The default behaviour of DjangoModelFactory's _create is to hook into the object's manager's get_or_create call if required, or to use that default manager's create() block.
Is there a reason for overriding the _create() block (thus bypassing that behaviour)?

If your custom manager's get_or_create should NOT be called from the factory, you will have to override both the _create() method AND the _get_or_create() one; factory_boy cannot guess which non-standard method to call!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants