2015-07-15 13 views
5

Qual è il modo corretto di utilizzare factory boy con i limiti Flask-SQLAlchemy e Foreign Key?Come si impostano le fabbriche dipendenti utilizzando Factory Boy e Flask-SQLAlchemy?

Si consideri la seguente configurazione Flask SQLAlchemy Modello:

# coding=utf-8 
from flask import Flask 
from flask.ext.sqlalchemy import SQLAlchemy 

app = Flask(__name__) 
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db' 
db = SQLAlchemy(app) 

# ------------------------------ 
# SQLAlchemy Table Models 
# ------------------------------ 
class User(db.Model): 
    """ A SQLAlchemy simple model class who represents a user with a ForeignKey Constraint""" 
    __tablename__ = 'UserTable' 

    user_pk = db.Column(db.Integer(), primary_key=True) 
    name = db.Column(db.Unicode(20)) 
    group_fk = db.Column(db.ForeignKey("GroupTable.group_pk"), nullable=False) 


class Group(db.Model): 
    """ A SQLAlchemy simple model class who represents a user """ 
    __tablename__ = 'GroupTable' 

    group_pk = db.Column(db.Integer(), primary_key=True) 
    name = db.Column(db.String(), nullable=False) 


# ------------------------- 
# Create the SQL tables 
# ------------------------- 
db.create_all() 

Lo schema utente richiede una chiave esterna Gruppo durante la creazione di un nuovo utente. Poiché la chiave primaria del gruppo viene assegnata dal database, la factory dovrebbe impegnare una voce di gruppo e ottenere la chiave primaria della voce in modo che possa fornirla al nuovo utente.

Come creare un gruppo, salvarlo nel DB e fornire la chiave per la User Factory?

Factory Boy has examples for dealing with Foreign Keys ma non sembrano applicabili a SQLAlchemy. Qui ci sono le fabbriche e il punto di errore:

# ---------------------------------------- 
# Factory-Boy User and Group Factories 
# ---------------------------------------- 
from factory import alchemy, Sequence, RelatedFactory 


class GroupFactory(alchemy.SQLAlchemyModelFactory): 
    class Meta(object): 
     model = Group 
     sqlalchemy_session = db.session # the SQLAlchemy session object 

    name = Sequence(lambda n: "Group {}".format(n)) 
    # group_pk = Sequence(lambda n: n) 


class UserFactory(alchemy.SQLAlchemyModelFactory): 
    class Meta(object): 
     model = User 
     sqlalchemy_session = db.session # the SQLAlchemy session object 

    user_pk = Sequence(lambda n: n) 
    name = Sequence(lambda n: u'User %d' % n) # coding=utf-8 
    group_fk = RelatedFactory(GroupFactory) 


# ---------------------- 
# Factory tests 
# ---------------------- 
# Create a new Group from our factory 
group_from_factory = GroupFactory(name='a new group name') 
assert group_from_factory.group_pk is None 
# Save it to our DB 
db.session.add(group_from_factory) 
db.session.commit() 

# Verify that Group Saved correctly to DB 
group_from_db = db.session.query(Group).filter(Group.group_pk == group_from_factory.group_pk).first() 
assert group_from_db.group_pk is not None 
assert group_from_db.name == 'a new group name' 
assert group_from_db.group_pk == group_from_factory.group_pk 

# Create a new User from our factory 
user_from_factory = UserFactory() 
db.session.add(user_from_factory) 
# ---------------------------------------------- 
# FAILS AT COMMIT() - NOT NULL constraint failed (group_fk is null) 
# ---------------------------------------------- 
db.session.commit() 

assert user_from_factory.user_pk is not None 
assert user_from_factory.name is not None 
assert user_from_factory.group_fk is not None 
+0

** ** SubFactory non funziona. Vedi i commenti su questo. Secondo la Factory Boy api dovrebbe, ma non è questo il motivo per cui ho postato su questo. – etiology

risposta

5

Il problema viene da utilizzando un RelatedFactory: quelli sono destinati ad inversaForeignKey relazioni (per esempio se si vuole costruire un oggetto Group che già contiene un User).

Per una diretta ForeignKey - come la relazione da User a Group, uso un SubFactory:

class UserFactory(factory.alchemy.SQLAlchemyModelFactory): 
    class Meta: 
     model = User 
     sqlalchemy_session = db.session 

    # No need to force the user_pk, it is built automatically from the database 
    # user_pk = Sequence(lambda n: n) 
    name = Sequence(lambda n: u'User %d' % n) # coding=utf-8 
    group_fk = factory.SubFactory(GroupFactory) 

Io non sono molto familiare con la boccetta-SQLAlchemy, ma ho appena aggiunto un piccolo esempio al repository (a https://github.com/rbarrois/factory_boy/tree/master/examples/flask_alchemy) che funziona ancora è abbastanza simile alla tua situazione.

+0

Grazie per il suggerimento, ma SubFactory non risolve questo problema. SQL genera un errore di tipo "non supportato" poiché SubFactory restituisce un'istanza di gruppo anziché un intero. Quindi, quello di cui ho bisogno è comunicare il valore intero Group.group_pk all'interfaccia User.group_fk in modo che SQL non diventi pazzo quando esegue l'istruzione INSERT. Anche l'oggetto Gruppo restituito da SubFactory è ** non ancora salvato nel database ** – etiology

+0

sqlalchemy.exc.InterfaceError: (sqlite3.InterfaceError) Errore nel collegamento del parametro 1 - tipo probabilmente non supportato. [SQL: u'INSERT INTO "UserTable" (nome, gruppo_fk) VALUES (?,?) '] [Parametri: (u'User 0', <__ main__.Group oggetto a 0x101c20650>)] – etiology

+0

Il codice github che hai scritto è grande. Sembra che questo potrebbe essere il modo per farlo. Richiede una modifica nel modello di tabella SQLAlchemy, motivo per cui non ho visto SubFactory come soluzione. È una soluzione se il modello di tabella è costruito in un modo particolare, che è una costruzione migliore. Ho bisogno di più tempo per esaminarlo prima di contrassegnare la risposta, ma questo è buono. – etiology

2

È possibile utilizzare LazyAttribute e un lambda per generare un nuovo gruppo e quindi estrarlo "group_pk".

versione funzionante del codice qui sotto:

# coding=utf-8 
from flask import Flask 
from flask.ext.sqlalchemy import SQLAlchemy 

app = Flask(__name__) 
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db' 
db = SQLAlchemy(app) 

# ------------------------------ 
# SQLAlchemy Table Models 
# ------------------------------ 
class User(db.Model): 
    """ A SQLAlchemy simple model class who represents a user with a ForeignKey Constraint""" 
    __tablename__ = 'UserTable' 

    user_pk = db.Column(db.Integer(), primary_key=True) 
    name = db.Column(db.Unicode(20)) 
    group_fk = db.Column(db.ForeignKey("GroupTable.group_pk"), nullable=False) 


class Group(db.Model): 
    """ A SQLAlchemy simple model class who represents a user """ 
    __tablename__ = 'GroupTable' 

    group_pk = db.Column(db.Integer(), primary_key=True) 
    name = db.Column(db.String(), nullable=False) 


# ------------------------- 
# Create the SQL tables 
# ------------------------- 
db.drop_all() 
db.create_all() 
# ---------------------------------------- 
# Factory-Boy User and Group Factories 
# ---------------------------------------- 
from factory import alchemy, Sequence, LazyAttribute 


class GroupFactory(alchemy.SQLAlchemyModelFactory): 
    class Meta(object): 
     model = Group 
     sqlalchemy_session = db.session # the SQLAlchemy session object 

    name = Sequence(lambda n: "Group {}".format(n)) 
    group_pk = Sequence(lambda n: n) 


class UserFactory(alchemy.SQLAlchemyModelFactory): 
    class Meta(object): 
     model = User 
     sqlalchemy_session = db.session # the SQLAlchemy session object 

    user_pk = Sequence(lambda n: n) 
    name = Sequence(lambda n: u'User %d' % n) # coding=utf-8 
    group_fk = LazyAttribute(lambda a: GroupFactory().group_pk) 


# ---------------------- 
# Factory tests 
# ---------------------- 
# Create a new Group from our factory 
group_from_factory = GroupFactory(name='a new group name') 
# Save it to our DB 
db.session.add(group_from_factory) 
db.session.commit() 

# Verify that Group Saved correctly to DB 
group_from_db = db.session.query(Group).filter(Group.group_pk == group_from_factory.group_pk).first() 
assert group_from_db.group_pk is not None 
assert group_from_db.name == 'a new group name' 
assert group_from_db.group_pk == group_from_factory.group_pk 

# Create a new User from our factory 
user_from_factory = UserFactory() 
db.session.add(user_from_factory) 
# ---------------------------------------------- 
# FAILS AT COMMIT() - NOT NULL constraint failed (group_fk is null) 
# ---------------------------------------------- 
db.session.commit() 

assert user_from_factory.user_pk is not None 
assert user_from_factory.name is not None 
assert user_from_factory.group_fk is not None 
3

collegamento git di Xelnor mostra la risposta migliore finora, ma richiede modifiche al modello SQLAlchemy. Ecco la copia finito di lavorare del mio post usando la soluzione di Xelnor:

# coding=utf-8 
from flask import Flask 
from flask.ext.sqlalchemy import SQLAlchemy 

app = Flask(__name__) 
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db' 
db = SQLAlchemy(app) 

modelle SQLAlchemy Tabella

class User(db.Model): 
    """ A SQLAlchemy simple model class who represents a user with a ForeignKey Constraint""" 
    __tablename__ = 'user' 

    id = db.Column(db.Integer(), primary_key=True) 
    name = db.Column(db.Unicode(20)) 

    group_id = db.Column(db.Integer, db.ForeignKey('group.id'), nullable=False) 

Il 'gruppo' db.relationship è ciò che rende il lavoro chiamata SubFactory. UserFactory passa il gruppo al modello Utente, che viene impostato con questa definizione di relazione() di .

group = db.relationship('Group', backref=db.backref('groups', lazy='dynamic')) 

    def __init__(self, name, group): 
     self.name = name 
     self.group = group 

    def __repr__(self): 
     return '<Group for %r: %s>' % (self.group, self.name) 


class Group(db.Model): 
    """ A SQLAlchemy simple model class who represents a user """ 
    __tablename__ = 'group' 

    id = db.Column(db.Integer(), primary_key=True) 
    name = db.Column(db.String(), nullable=False) 

    def __init__(self, name): 
     self.name = name 

    def __repr__(self): 
     return '<Group %r>' % self.name 

creare le tabelle SQL

db.create_all() 

fabbrica-Boy utente e stabilimenti del Gruppo

from factory import alchemy, Sequence, SubFactory, fuzzy 


class BaseFactory(alchemy.SQLAlchemyModelFactory): 
    class Meta(object): 
     abstract = True 
     sqlalchemy_session = db.session 


class GroupFactory(BaseFactory): 
    class Meta(object): 
     model = Group 
     sqlalchemy_session = db.session # the SQLAlchemy session object 

    name = fuzzy.FuzzyText() 


class UserFactory(BaseFactory): 
    class Meta: 
     model = User 
     sqlalchemy_session = db.session 

    name = fuzzy.FuzzyText() 
    group = SubFactory(GroupFactory) 

fabbrica Prove

# Create a new Group from our factory 
group_from_factory = GroupFactory(name='a new group name') 
assert group_from_factory.id is None 
# Save it to our DB 
db.session.add(group_from_factory) 
db.session.commit() 

# Verify that Group Saved correctly to DB 
group_from_db = db.session.query(Group).filter(Group.id == group_from_factory.id).first() 
assert group_from_db.id is not None 
assert group_from_db.name == 'a new group name' 
assert group_from_db.id == group_from_factory.id 

# Create a new User from our factory 
user1_from_factory = UserFactory(name=u'first') 
user2_from_factory = UserFactory(name=u'second') 
db.session.add(user1_from_factory) 
db.session.add(user2_from_factory) 
db.session.commit() 

assert user1_from_factory.id is not None 
assert user1_from_factory.name is not None 
assert user1_from_factory.id is not None 

assert user2_from_factory.id is not None 
assert user2_from_factory.name is not None 
assert user2_from_factory.id is not None