Отношения «один ко многим» в factory_boy

Я использую SQLalchemy в качестве ORM и пытаюсь перенести свои тестовые приборы на factory_boy. Моя схема включает два объекта в отношении «один ко многим». т.е. экземпляры одной модели имеют спископодобные структуры с экземплярами другой. Пример:

class Person(...):
  id = Column(Integer, primary_key=True)
  name = Column(Text)
  [...]

class Address(...):
  id = Column(Integer, primary_key=True)
  city = Column(Text)
  [...]
  person_id = Column(Integer, ForeignKey('person.id'))
  person = relationship("Person", backref="addresses")

Теперь я пытаюсь создать фабрику, которая создает людей с парой адресов. Factory_boy имеет SubFactory. Но я вижу, как ты можешь использовать это только в отношениях один на один. Я знаю, что могу создать адреса с помощью отдельной фабрики, а затем присоединить их, но я хотел бы сделать что-то вроде person =PersonFactory.create(num_addresses=4)`.

Кто-нибудь знает, возможно ли это сейчас в factory_boy?

Я использую factory_boy 2.4.1.


person NiklasMM    schedule 25.11.2014    source источник


Ответы (5)


Я использую этот шаблон в своем проекте. Предполагая, что у вас уже есть AddressFactory.

https://factoryboy.readthedocs.io/en/latest/reference.html?highlight=post_generation#factory.post_generation

class PersonFactory(factory.alchemy.SQLAlchemyFactory):
    class Meta:
        model = Person

    @factory.post_generation
    def addresses(obj, create, extracted, **kwargs):
        if not create:
            return

        if extracted:
            assert isinstance(extracted, int)
            AddressFactory.create_batch(size=extracted, person_id=self.id, **kwargs)

Применение

PersonFactory(addresses=4)

Это создаст Person с 4 Addresses

Также это может принимать kwargs

PersonFactory(addresses=2, addresses__city='London')

Это создаст Person с 2 Addresses, у которых поле city установлено на 'London'

Вот сообщение в блоге, которое может помочь https://simpleit.rocks/python/django/setting-up-a-factory-for-one-to-many-relationships-in-factoryboy/

person Sardorbek Imomaliev    schedule 08.02.2019

У меня был именно этот вопрос, и я был разочарован отсутствием хороших ответов здесь. Оказывается можно! Оставьте это здесь для тех, у кого есть тот же вопрос.

Во-первых, ваша модель должна определить отношение к модели, противоположной внешнему ключу, поэтому она должна выглядеть так:


class Person(...):
    id = Column(Integer, primary_key=True)
    name = Column(Text)
    addresses = relationship("Person", backref="person")
    [...]

class Address(...): id = Column(Integer, primary_key=True) city = Column(Text) [...] person_id = Column(Integer, ForeignKey('person.id'))

Then, on your PersonFactory, you can add a post_generation hook like this:


class PersonFactory(BaseFactory):
    [...attributes...]

    @factory.post_generation
    def addresses(self, create, extracted, **kwargs):
        return AddressFactory.create_batch(4)

and replace the '4' with whatever number you want. Obviously, you need to define the AddressFactory as well.

person Kristen    schedule 17.06.2016
comment
Это просто не работает. Не могли бы вы отредактировать ответ со своими полными классами AddressFactory и PersonFactory, если считаете, что это работает? - person The Aelfinn; 09.05.2017
comment
Почему вы минусуете этот пост? оно работает. @Kristen укажи правильное направление. Эта цель может быть достигнута с помощью декоратора post_generation factoryboy.readthedocs .io/ru/последние/ - person Daniil Mashkin; 03.07.2018

В настоящее время нет способа реализовать "многие к одному RelatedFactory" таким образом, чтобы он "встраивался в вашу фабрику"...

Тем не менее, такое поведение можно реализовать с небольшим количеством хакерских действий при создании экземпляра PersonFactory.

Следующий рецепт даст вам то, что вы ищете:

from sqlalchemy import create_engine, Integer, Text, ForeignKey, Column
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, scoped_session, sessionmaker
import factory
from factory.alchemy import SQLAlchemyModelFactory as sqla_factory
import random

engine = create_engine("sqlite:////tmp/factory_boy.sql")
session = scoped_session(sessionmaker(bind=engine))
Base = declarative_base()

class Person(Base):
    id = Column(Integer, primary_key=True)
    name = Column(Text)
    addresses = relationship("Address", backref="person")

class Address(Base):
    id = Column(Integer, primary_key=True)
    street = Column(Text)
    street_number = Column(Integer)
    person_id = Column(Integer, ForeignKey('person.id'))

class AddressFactory(sqla_factory):
    class Meta:
        model = Address
        sqlalchemy_session = session
    street_number = random.randint(0, 10000)
    street = "Commonwealth Ave"

class PersonFactory(sqla_factory):
    class Meta:
        model = Person
        sqlalchemy_session = session
    name = "John Doe"

Base.metadata.create_all(engine)
for i in range(100):
    person = PersonFactory(addresses=AddressFactory.create_batch(3))
person The Aelfinn    schedule 09.05.2017

@Kristen указала в правильном направлении, но AdderssFactory не имела отношения к Person. В Django мы можем использовать post_generation такой декоратор.

class PersonFactory(BaseFactory):
    @factory.post_generation
    def addresses(self, create, extracted, **kwargs):
        self.addresses_set.add(AddressFactory(person=self))
person Daniil Mashkin    schedule 03.07.2018

Вы можете использовать решение, описанное здесь: http://factoryboy.readthedocs.org/en/latest/recipes.html#reverse-dependencies-reverse-foreignkey

По сути, просто объявите несколько RelatedFactory на вашем PersonFactory:

class PersonFactory(factory.alchemy.SQLAlchemyFactory):
    class Meta:
        model = Person

    address_1 = factory.RelatedFactory(AddressFactory, 'person')
    address_2 = factory.RelatedFactory(AddressFactory, 'person')
person Xelnor    schedule 25.11.2014
comment
Но это не дает никакой гибкости, верно? У всех людей будет два адреса (или сколько я их определяю), правильно? - person NiklasMM; 25.11.2014
comment
@Xelnor, в этом отсутствует смысл отношения «многие к одному». Приведенный выше ответ не масштабируется для поддержки n адресов на человека. - person The Aelfinn; 09.05.2017