Как переопределить функцию сохранения модели при использовании фабричного мальчика?

Я использую Factory Boy для тестирования проекта Django, и у меня возникла проблема при тестировании модели, для которой я переопределил метод сохранения.

Модель:

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.

Фабрика:

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)

Тест:

class ProfileTest(TestCase):

    def test_profile_creation(self):
        o = factories.ProfileFactory()
        self.assertTrue(isinstance(o, models.Profile))

Когда я запускаю тесты, я получаю следующую ошибку:

django.db.utils.IntegrityError: UNIQUE constraint failed: simtrack_profile.id

Если я закомментирую последний последний / второй оператор 'super' в методе сохранения профиля, тесты пройдут. Интересно, пытается ли это утверждение снова создать профиль с тем же идентификатором? Я пробовал разные вещи, такие как указание в мета-классе django_get_or_create и различные взломанные версии переопределения метода _generation для Factory с отключением и подключением сохранения после генерации, но я не могу заставить его работать.

Тем временем я установил стратегию построения, но очевидно, что это не будет проверять мой метод сохранения.

Любая помощь с благодарностью.

J.

1 ответ

Решение

factory_boy использует MyModel.objects.create() функция из ORM Джанго.

Эта функция вызывает obj.save(force_insert=True): https://github.com/django/django/blob/master/django/db/models/query.py

С твоим перегруженным save() функция, это означает, что вы получите:

  1. Вызов super(Profile, self).save(force_insert=True)
    • [SQL: INSERT INTO simtrack_profile SET ...; ]
    • => self.pk устанавливается на рк вновь вставленной строки
  2. Выполните ваш пользовательский код
  3. Вызов super(Profile, self).save(force_insert=True)
    • Это генерирует этот SQL: INSERT INTO simtrack_profile SET id=N, ..., с N быть ПК объекта
    • Очевидно, происходит сбой: уже есть строка с id=N,

Вы должны исправить save() функция, так что во второй раз вы звоните super(Profile, self).save() без повторения *args, **kwargs снова.

Заметки:

  • Ваш код сломается, когда вы добавите объект через администратора Django, или когда вы будете использовать Profile.objects.create(),
  • Так как вы не модифицируете self в твоем перегруженном save() функция, вы должны быть в состоянии удалить второй вызов super(Profile, self).save() в целом; хотя сохранение его может быть полезным, чтобы избежать странных ошибок, если вам нужно добавить больше пользовательского поведения позже.
Другие вопросы по тегам