I'm using Factory Boy for testing a Django project and I've run into an issue while testing a model for which I've overridden the save method.
The model:
class Profile(models.Model):
active = models.BooleanField()
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE,
related_name='profiles')
department = models.ForeignKey(Department, null=True, blank=True)
category_at_start = models.ForeignKey(Category)
role = models.ForeignKey(Role)
series = models.ForeignKey(Series, null=True, blank=True)
status = models.ForeignKey('Status', Status)
def save(self, *args, **kwargs):
super(Profile, self).save(*args, **kwargs)
active_roles = []
active_status = []
for profile in Profile.objects.filter(user=self.user):
if profile.active:
active_roles.append(profile.role.code)
active_status.append(profile.status.name)
self.user.current_role = '/'.join(set(active_roles))
if 'Training' in active_status:
self.user.current_status = 'Training'
elif 'Certified' in active_status:
self.user.current_status = 'Certified'
else:
self.user.current_status = '/'.join(set(active_status))
self.user.save()
super(Profile, self).save(*args, **kwargs) ### <-- seems to be the issue.
The factory:
class ProfileFactory(f.django.DjangoModelFactory):
class Meta:
model = models.Profile
active = f.Faker('boolean')
user = f.SubFactory(UserFactory)
department = f.SubFactory(DepartmentFactory)
category_at_start = f.SubFactory(CategoryFactory)
role = f.SubFactory(RoleFactory)
series = f.SubFactory(SeriesFactory)
status = f.SubFactory(StatusFactory)
The test:
class ProfileTest(TestCase):
def test_profile_creation(self):
o = factories.ProfileFactory()
self.assertTrue(isinstance(o, models.Profile))
When I run the tests, I get the following error:
django.db.utils.IntegrityError: UNIQUE constraint failed: simtrack_profile.id
If I comment out the last last/second 'super' statement in the Profile save method the tests pass. I wonder if this statement is trying to create the profile again with the same ID? I've tried various things such as specifying in the Meta class django_get_or_create and various hacked versions of overriding the _generation method for the Factory with disconnecting and connecting the post generation save, but I can't get it to work.
In the meantime, I've set the strategy to build but obviously that won't test my save method.
Any help greatly appreciated.
J.
factory_boy
uses the MyModel.objects.create()
function from Django's ORM.
That function calls obj.save(force_insert=True)
: https://github.com/django/django/blob/master/django/db/models/query.py#L384
With your overloaded save()
function, this means that you get:
super(Profile, self).save(force_insert=True)
INSERT INTO simtrack_profile SET ...;
]self.pk
is set to the pk of the newly inserted linesuper(Profile, self).save(force_insert=True)
INSERT INTO simtrack_profile SET id=N, ...
, with N
being the pk of the objectid=N
.You should fix your save()
function, so that the second time you call super(Profile, self).save()
without repeating *args, **kwargs
again.
Profile.objects.create()
.self
in your overloaded save()
function, you should be able to remove the second call to super(Profile, self).save()
altogether; although keeping it around might be useful to avoid weird bugs if you need to add more custom behavior later.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With