Creating multiple objects with foreign key - python

I need to create ten sample users (User) and each of them must have fifty documents (Doc). How to do this in tests.py using factoryboy?
#factories.py
from app_name.models import *
import factory
from datetime import datetime, timedelta, time
from django.contrib.auth.models import User
class UserFactory(factory.Factory):
FACTORY_FOR = User
username = factory.Sequence(lambda n: 'User ' + n)
email = 'demo#mail.com'
password = '1234567'
class DocFactory(factory.Factory):
FACTORY_FOR = Doc
user = factory.SubFactory(UserFactory)
kategories = '1'
doc_number = '12345678'
date_join = factory.Sequence(lambda n:(datetime.now() + timedelta(days=n)).date(), int)
in my tests.py:
from django.test import TestCase
from django_dynamic_fixture import G
from factories import *

users = UserFactory.create_batch(10)
for user in users:
doc = DocFactory.create(user=user)

You can use a post_generation decorator:
class UserFactory(factory.Factory):
...
#factory.post_generation
def create_docs(self, create, extracted, **kwargs):
if not create:
return
for i in range(50):
doc = DocFactory.create(user=self)

For those of you working with SQLAlchemy, this can be done with the following recipe (notice that I'm using the Person/Address models instead of the User/Docs model example above).
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)
city = Column(Text)
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"
city = "Boston"
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))
This creates 3 workouts for each person created, where each workout references the person via the person_id FK.

You can simply do batch_create so that for each DocFactory object new UserFactory object will create
`DocFactory.create_batch(10)`

Related

Python Alchemy multiple relationship

I have a one to many relationship that returns an error each time I try to call it.
USER Model:
from config import db
from models.mixins import *
from models.decorators import *
import uuid
from sqlalchemy import Column, String, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from sqlalchemy.dialects import mysql
class User(db.Base):
__tablename__ = 'user'
id = Column(GUID(), primary_key=True, default=uuid.uuid4, unique=True)
name = Column(String(255), nullable=True)
address_to = Column(GUID(), ForeignKey('addess.id'), nullable=True)
address_from = Column(GUID(), ForeignKey('address.id'), nullable=True)
u_address_from = relationship('Address', foreign_keys='address_from',
back_populates='user_address_from')
u_address_to = relationship('Address', foreign_keys='address_to',
back_populates='user_address_to')
Address Model:
from config import db
from models.decorators import *
import uuid
from sqlalchemy import Column, String, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from sqlalchemy.dialects import mysql
class Address(db.Base):
__tablename__ = 'address'
id = Column(GUID(), primary_key=True, default=uuid.uuid4, unique=True)
street = Column(String(64), nullable=False)
city = Column(String(64), nullable=False)
user_address_from = relationship('Address', foreign_keys='User.address_from',
back_populates='u_address_from')
address_to = relationship('Address', foreign_keys='User.address_to',
back_populates='u_address_to')
Function to get user
class GetUser:
def user(user_id: str):
with db.Session() as session:
user = session.query(User).where(
User.id == user_id,
User.deleted_at.is_(None)
).first()
return user
The error I get is:
[ERROR] InvalidRequestError: When initializing mapper mapped class User->user, expression 'address_from' failed to locate a name ("name 'address_from' is not defined"). If this is a class name, consider adding this relationship() to the <class 'user.User'> class after both dependent classes have been defined.

Generating JSON from SQLAlchemy base class including subclases from relationship

I'm trying to generate the JSON of my SQLAlchemy classes, I followed this example:
https://blogs.gnome.org/danni/2013/03/07/generating-json-from-sqlalchemy-objects/
It’s working very fine, but now I want to include all the data of the subclasses generated by the relationship of SQLAchemy. I've tried several things, the last one is trying to iterate over the subclases but I don't know why the method subclasses doesn't return anything. This is the function tojson modified:
def tojson(self):
res=self.columnitems
for cls in self.__class__.__subclasses__():
res[cls.__name__]=cls.tojson()
return res
Do you know any way to do it?
Thanks in advance
I can't comment yet but based on the information provided I'm assuming you are trying to generate a json from your (related) sqlalchemy classes. You can use the marshmallow (https://marshmallow.readthedocs.io/en/latest/) for this.
The (quick) example below shows how you can generate a json using marshmallow of two related tables.
from sqlalchemy import Column, Integer, String, Boolean
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine, ForeignKey
from sqlalchemy.orm import sessionmaker, relationship
Base = declarative_base()
# Creating dummy classes...
class Owner(Base):
__tablename__ = 'owner'
id = Column('id', Integer, primary_key=True)
name = Column('name', String(250))
interested_in_cars = Column('interest', Boolean)
car = relationship('Car', uselist=False, back_populates="owner")
def __init__(self, name, interested_in_cars, id):
self.id = id
self.name = name
self.interested_in_cars = interested_in_cars
def __repr__(self):
return '< (id={id}) Owner: {name} - interested: {interested_in_cars} >'.format(id=self.id,
name=self.name,
interested_in_cars=self.interested_in_cars)
class Car(Base):
__tablename__ = 'car'
id = Column('id', Integer, primary_key=True)
brand = Column(String(250))
owner_id = Column(Integer, ForeignKey('owner.id'))
owner = relationship('Owner', back_populates='car')
def __init__(self, owner_id, brand):
self.owner_id = owner_id
self.brand = brand
def __repr__(self):
return '< Owner: {owner_id} - Car: {brand} >'.format(owner_id=self.owner_id, brand=self.brand)
engine = create_engine('sqlite:///')
session = sessionmaker()
session.configure(bind=engine)
ex_ses = session()
Base.metadata.create_all(engine)
owner_1 = Owner(interested_in_cars=True, name='Owner a', id=1)
owner_2 = Owner(interested_in_cars=False, name='Owner b', id=2)
ex_ses.add(owner_1)
ex_ses.add(owner_2)
# ID's - quick example
car_1 = Car(owner_id=1, brand='Car a')
car_2 = Car(owner_id=2, brand='Car b')
ex_ses.add(car_1)
ex_ses.add(car_2)
ex_ses.commit()
# Using marshmallow to generate the json
from marshmallow import Schema, fields, pprint
class OwnerShema(Schema):
id = fields.Int()
name = fields.String()
interested_in_cars = fields.Boolean()
car = fields.Nested('CarShema')
class CarShema(Schema):
id = fields.Int()
brand = fields.String()
# Example Owners and cars
owners_cars = ex_ses.query(Owner).all()
print('Owners and cars: ', owners_cars)
owners_cars_shema = OwnerShema()
pprint(owners_cars_shema.dump(owners_cars, many=True).data)
For more information see the marshmallow documentation (link provided above).

Flask-admin, editing relationship giving me object representation of Foreign Key object

I have a flask project, and I am getting started learning the flask-admin module.
SqlAlchemy schema for the required tables.
import datetime
import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import backref, relationship
Base = declarative_base()
class Workgroup(Base):
__tablename__ = 'workgroups'
id = sqlalchemy.Column(sqlalchemy.Integer,
primary_key=True,
autoincrement=True
)
name = sqlalchemy.Column(sqlalchemy.String(16))
shorthand = sqlalchemy.Column(sqlalchemy.String(4))
def __unicode__(self):
return self.name
class Drive(Base):
"""
A drive in an edit station.
"""
__tablename__ = 'drives'
id = sqlalchemy.Column(sqlalchemy.Integer,
primary_key=True,
autoincrement=True
)
name = sqlalchemy.Column(sqlalchemy.String(64))
computer_id = sqlalchemy.Column(sqlalchemy.Integer,
sqlalchemy.ForeignKey(Computer.id)
)
computer = relationship('Computer', backref='drives')
is_active = sqlalchemy.Column(sqlalchemy.Boolean)
free_space = sqlalchemy.Column(sqlalchemy.BigInteger)
used_space = sqlalchemy.Column(sqlalchemy.BigInteger)
total_space = sqlalchemy.Column(sqlalchemy.BigInteger)
percentage_full = sqlalchemy.Column(sqlalchemy.Float)
boot_time = sqlalchemy.Column(sqlalchemy.DateTime)
last_changed_workgroup = sqlalchemy.Column(sqlalchemy.DateTime)
last_checked_in = sqlalchemy.Column(sqlalchemy.DateTime)
last_notified = sqlalchemy.Column(sqlalchemy.DateTime)
image_version = sqlalchemy.Column(sqlalchemy.String(64))
image_date = sqlalchemy.Column(sqlalchemy.DateTime)
current_workgroup_id = sqlalchemy.Column(sqlalchemy.Integer,
sqlalchemy.ForeignKey(Workgroup.id)
)
workgroup = relationship('Workgroup', backref='drives')
Admin Test
class DriveAdmin(sqla.ModelView):
column_display_pk = True
column_hide_backrefs = False
column_display_all_relations = True
form_columns = [ 'computer_id', 'workgroup.name', ]
column_list = ('computer.name', 'name', 'workgroup', 'computer.short_description', 'computer.notes',
'computer.station_type.description', 'computer.room.name')
class WorkgroupAdmin(sqla.ModelView):
column_display_pk = True # optional, but I like to see the IDs in the list
column_hide_backrefs = False
column_list = ('id', 'name', 'shorthand')
# Create admin
admin = admin.Admin(app, name='Example: SQLAlchemy2', template_mode='bootstrap3')
admin.add_view(WorkgroupAdmin(schema.Workgroup, db))
admin.add_view(DriveAdmin(schema.Drive, db))
replacing form columns for 'workgroup' with 'workgroup.name' gives me an invalid model property name, even though I have successfully used schema.workgroup.name elsewhere in code.
The resulting admin form looks like this.
How do I go about getting the workgroup.name value to appear as opposed to the object representation?
Thanks for reading!
You need to get the workgroup class to return its name via the repr function. That way it will show in the field.
class Workgroup(Base):
__tablename__ = 'workgroups'
id = sqlalchemy.Column(sqlalchemy.Integer,
primary_key=True,
autoincrement=True
)
name = sqlalchemy.Column(sqlalchemy.String(16))
shorthand = sqlalchemy.Column(sqlalchemy.String(4))
def __unicode__(self):
return self.name
def __repr__(self):
return '<Workgroup %r>' % (self.name)

One-to-many relationships in factory_boy

I use SQLalchemy as my ORM and am trying to port my test fixtures to factory_boy. My schema includes two objects in a one-to-many relation. I.e. instances of one model have list like structures with instances of the other. Example:
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")
Now I am trying to create a factory which creates persons with a couple of addresses. Factory_boy has the SubFactory. But I only see how you can use that in a one-to-one relationship. I know I can create the addresses with a separate factory and then attach them, but I would like to do something like person =PersonFactory.create(num_addresses=4)`.
Does anyone know if this is currently possible in factory_boy?
I use factory_boy 2.4.1.
I am using this pattern in my project. Assuming you already have 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)
Usage
PersonFactory(addresses=4)
This will create Person with 4 Addresses
Also this can accept kwargs
PersonFactory(addresses=2, addresses__city='London')
This will create Person with 2 Addresses which have city field set to 'London'
Here is blog post which may help https://simpleit.rocks/python/django/setting-up-a-factory-for-one-to-many-relationships-in-factoryboy/
#Kristen pointed to the right direction, but AdderssFactory didn't related to Person.
In Django we can use post_generation decorator like this.
class PersonFactory(BaseFactory):
#factory.post_generation
def addresses(self, create, extracted, **kwargs):
self.addresses_set.add(AddressFactory(person=self))
I had this exact question and was disappointed in the lack of good answers here. Turns out it is possible! Leaving this here for those who have the same question.
First, your model needs to define the relationship on the opposite model from the ForeignKey, so it should look like:
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.
Currently, there is no way to implement a "many-to-one RelatedFactory" such that it is "baked into your factory"...
That said, this behavior can be implemented with a bit of hackery when instantiating your PersonFactory.
The following recipe will get you what you are looking for:
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))
You could use the solution described here: http://factoryboy.readthedocs.org/en/latest/recipes.html#reverse-dependencies-reverse-foreignkey
Basically, just declare a few RelatedFactory on your PersonFactory:
class PersonFactory(factory.alchemy.SQLAlchemyFactory):
class Meta:
model = Person
address_1 = factory.RelatedFactory(AddressFactory, 'person')
address_2 = factory.RelatedFactory(AddressFactory, 'person')

query related entities on sqlalchemy (many-to-many)

i have users and entities (many-to-many) , and using sqlalchemy, with this model:
from sqlalchemy import Table, Column, Unicode, Integer, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import scoped_session, sessionmaker
from zope.sqlalchemy import ZopeTransactionExtension
DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
Base = declarative_base()
users_entities = Table('users_entities', Base.metadata,
Column('userID', Integer, ForeignKey('users.id')),
Column('entitieID', Integer, ForeignKey('entities.id'))
)
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
username = Column(Unicode(20))
password = Column(Unicode(101))
entities = relationship("Entities", secondary=users_entities)
def __init__(self, username, password):
self.username = username
self.password = password
class Entities(Base):
__tablename__ = 'entities'
id = Column(Integer, primary_key=True)
name = Column(Unicode(20))
descr = Column(Unicode(101))
url = Column(Unicode(101))
def __init__(self, name, descr, url):
self.name = name
self.descr = descr
self.url = url
so, when i use:
user = dbsession.query(User).filter_by(id=session["userID"]).first()
entities = user.entities
i get the user with the user data, and the entities(user.entities) with all the user's entities.
but instead of getting all the entities, now i need to get an entity from the user where the id is = X
something that would work like this:
user.query(Entities).filter_by(id=X)
i can't find a simple("best") way to do this, am i missing something?
You are looking for Dynamic Relationship Loaders, they're exactly what you want to do, and enable you to write:
user.entities.filter_by(id=X)
By the way, you can write user = dbsession.query(User).get(session["userID"]). It does the same query, but only if the object is not already in the session cache (plus, it's shorter).

Categories