Случайный выбор Factory Boy для поля с опцией "Выбор"
Когда поле в модели Django имеет варианты выбора, см. Параметр поля Django choices, оно использует итерацию, содержащую итерации из 2 элементов, чтобы определить, какие значения допустимы. Например:
модели
class IceCreamProduct(models.Model):
PRODUCT_TYPES = (
(0, 'Soft Ice Cream'),
(1, 'Hard Ice Cream'),
(2, 'Light Ice Cream'),
(3, 'French Ice Cream'),
(4, 'Italian-style Gelato'),
(5, 'Frozen Dairy Dessert'),
)
type = models.PositiveSmallIntegerField('Type', choices=PRODUCT_TYPES, default=0)
Чтобы сгенерировать случайное значение в Factory Boy для выбора, я бы использовал factory.fuzzy.FuzzyChoice, но при этом выбирается только итерация из 2 элементов. Он не может взять первый элемент из выбранного итерируемого. Например:
Фабрики
class IceCreamProductFactory(factory.django.DjangoModelFactory):
class Meta:
model = IceCreamProduct
type = factory.fuzzy.FuzzyChoice(IceCreamProduct.PRODUCT_TYPES)
ошибка
TypeError: int() argument must be a string, a bytes-like object or a number, not 'tuple'
Получить первый элемент кортежа невозможно. Например:
Фабрики
class IceCreamProductFactory(factory.django.DjangoModelFactory):
class Meta:
model = IceCreamProduct
type = factory.fuzzy.FuzzyChoice(IceCreamProduct.PRODUCT_TYPES)[0]
ошибка
TypeError: 'FuzzyChoice' object does not support indexing
Это возможно при использовании случайного итератора Python по умолчанию, но при этом генерируется значение во время объявления, поэтому каждый объект фабрики будет иметь одинаковое случайное значение. Например:
Фабрики
class IceCreamProductFactory(factory.django.DjangoModelFactory):
class Meta:
model = IceCreamProduct
type = random.choice(IceCreamProduct.PRODUCT_TYPES)][0]
Как это можно решить в Factory Boy? Нужно ли создавать собственный FuzzyAttribute? (Если да, приведите пример)
5 ответов
Вам не понадобится FuzzyAttribute.
Вы можете либо ограничить возможные значения и передать FuzzyChoice значение int каждого типа продукта только следующим образом:
PRODUCT_IDS = [x[0] for x in IceCreamProduct.PRODUCT_TYPES]
class IceCreamProductFactory(factory.django.DjangoModelFactory):
class Meta:
model = IceCreamProduct
type = factory.fuzzy.FuzzyChoice(PRODUCT_IDS)
Это должно сделать работу.
Обратите внимание, что нечеткий модуль недавно был объявлен устаревшим, см. ( https://factoryboy.readthedocs.org/en/latest/fuzzy.html), вы можете вместо этого использовать функцию LazyFunction.
Вы можете сделать так легко, как это
class IceCreamProductFactory(factory.django.DjangoModelFactory):
type = factory.Faker(
'random_element', elements=[x[0] for x in IceCreamProduct.PRODUCT_TYPES]
)
class Meta:
model = IceCreamProduct
PS. Не использовать type
как атрибут
Вот как я смог это сделать используя factory.LazyFunction
как предположил Лотиральдан:
import random
...
def get_license_type():
"Return a random license type from available choices."
lt_choices = [x[0] for x in choices.LICENSE_TYPE_CHOICES]
return random.choice(lt_choices)
def get_line_type():
"Return a random line type from available choices."
lt_choices = [x[0] for x in choices.LINE_TYPE_CHOICES]
return random.choice(lt_choices)
class ProductFactory(ModelFactory):
name = factory.Faker('name')
description = factory.Faker('text')
license_type = factory.LazyFunction(get_license_type)
line_type = factory.LazyFunction(get_line_type)
class Meta:
model = 'products.ProductBaseV2'
Поскольку я должен был сделать это для довольно большого количества моделей, я придумал более абстрактную версию решения Эрихонканена. Я определяю вспомогательный класс, который помещаю в каталог тестирования верхнего уровня моего проекта и импортирую его в модули, содержащие фабрики:
test/helpers.py
import factory
import random
class ModelFieldLazyChoice(factory.LazyFunction):
def __init__(self, model_class, field, *args, **kwargs):
choices = [choice[0] for choice in model_class._meta.get_field(field).choices]
super(ModelFieldLazyChoice, self).__init__(
function=lambda: random.choice(choices),
*args, **kwargs
)
И в app/factories.py
from app.models import IceCreamProduct
from test.helpers import ModelFieldLazyChoice
class IceCreamProductFactory(factory.django.DjangoModelFactory):
class Meta:
model = IceCreamProduct
type = ModelFieldLazyChoice(IceCreamProduct, 'type')
Если вы делаете выбор на основе класса...
class IceCreamProduct(models.Model):
class ProductTypes(models.TextChoices):
soft_ice_crem = (0, 'Soft Ice Cream')
hard_ice_cream = (1, 'Hard Ice Cream')
...
class IceCreamProductFactory(factory.django.DjangoModelFactory):
class Meta:
model = IceCreamProduct
type = factory.fuzzy.FuzzyChoice(IceCreamProduct.ProductTypes)
...