Случайный выбор 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)
    ...
Другие вопросы по тегам