What is the proper model definition for SQLAlchemy to avoid error message : AttributeError: 'InstrumentedList' object has no attribute'?

StackOverflow https://stackoverflow.com/questions/19232725

Pregunta

I am creating a Point of Sales application, with the typical data hierarchy : Company->branches->Sales->SaleData, this is the model definition (Note that, the User model already prepare and working as a flask-login compatible model) :

from flask_sqlalchemy import SQLAlchemy
from main import db
from collections import OrderedDict


class Users(db.Model,object):                                                                                                                                                                                                     
    '''                                                                                                                                                                                                                           
    Adding object to trun sqlalchemy into json object                                                                                                                                                                             
    '''                                                                                                                                                                                                                           
    id = db.Column(db.Integer, primary_key=True)                                                                                                                                                                                  
    username = db.Column(db.String(60), unique=True)                                                                                                                                                                              
    firstname = db.Column(db.String(20))                                                                                                                                                                                          
    lastname = db.Column(db.String(20))                                                                                                                                                                                           
    password = db.Column(db.String)                                                                                                                                                                                               
    email = db.Column(db.String(100), unique=True)                                                                                                                                                                                
    role = db.Column(db.String(20))                                                                                                                                                                                               
    active = db.Column(db.Boolean)                                                                                                                                                                                                
    company_id = db.Column(db.Integer, db.ForeignKey('companies.id'))                                                                                                                                                             

    def __init__(self, username=None, password=None, email=None, firstname=None, lastname=None):                                                                                                                                  
        self.username = username                                                                                                                                                                                                  
        self.email = email                                                                                                                                                                                                        
        self.firstname = firstname                                                                                                                                                                                                
        self.lastname = lastname                                                                                                                                                                                                  
        self.password = password                                                                                                                                                                                                  
        self.active = True                                                                                                                                                                                                        
        self.role = 'Admin'                                                                                                                                                                                                       

    def is_authenticated(self):                                                                                                                                                                                                   
        return True                                                                                                                                                                                                               

    def is_active(self):                                                                                                                                                                                                          
        return self.active                                                                                                                                                                                                        

    def is_anonymous(self):                                                                                                                                                                                                       
        return False                                                                                                                                                                                                              

    def get_id(self):                                                                                                                                                                                                             
        return unicode(self.id)                                                                                                                                                                                                   

    def _asdict(self):                                                                                                                                                                                                            
        '''                                                                                                                                                                                                                       
        Thanks to http://stackoverflow.com/questions/7102754/jsonify-a-sqlalchemy-result-set-in-flask                                                                                                                             
    '''                                                                                                                                                                                                                       
        result = OrderedDict()                                                                                                                                                                                                    
        for key in self.__mapper__.c.keys():                                                                                                                                                                                      
            result[key] = getattr(self, key)                                                                                                                                                                                      
        return result 


class Companies(db.Model):                                                                                                                                                                                                        
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100), unique=True)                                                                                                                                                                                 
    address = db.Column(db.String)                                                                                                                                                                                                
    users = db.relation('Users', backref=db.backref('users'))                                                                                                                                                                     
    token = db.Column(db.String) #for identification of client                                                                                                                                                                    
    branches = db.relationship("Branches")                                                                                                                                                                                        

    def __init__(self, name=None, address=None, token=None):                                                                                                                                                                      
        self.name = name                                                                                                                                                                                                          
        self.address = address                                                                                                                                                                                                    
        self.token = token             

class Branches(db.Model):                                                                                                                                                                                                         
    id = db.Column(db.Integer, primary_key=True)                                                                                                                                                                                  
    name = db.Column(db.String(255), unique=True)                                                                                                                                                                                 
    address = db.Column(db.String)                                                                                                                                                                                                
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'))                                                                                                                                                                    
    token = db.Column(db.String) #for identification of client                                                                                                                                                                    
    company_id = db.Column(db.Integer, db.ForeignKey('companies.id'))                                                                        
    sales = db.relation('Sales',                                                                                                                                                                                                  
                    backref=db.backref('sales', lazy='dynamic'),                                                                                                                                                              
                    cascade="all, delete-orphan",                                                                                                                                                                             
                    lazy='dynamic',                                                                                                                                                                                           
                    passive_deletes=True)                                                                                                                                                                                     

    def __init__(self, name=None, address=None, token=None, user_id=None):                                                                                                                                                        
        self.name = name                                                                                                                                                                                                          
        self.address = address                                                                                                                                                                                                    
        self.token = token                                                                                                                                                                                                        
        self.user_id = user_id                                                                                                                                                                                                    

class Sales(db.Model):                                                                                                                                                                                                            
    id = db.Column(db.Integer, primary_key=True)                                                                                                                                                                                  
    day = db.Column(db.Date)                                                                                                                                                                                                      
    branch_id = db.Column(db.Integer, db.ForeignKey('branches.id'))                                                                                                                                                               
    data = db.relationship("SaleData")                                                                                                                                                                                            

    def __init__(self, day=None):                                                                                                                                                                                                 
        self.day = day                                                                                                                                                                                                            

class SaleData(db.Model):                                                                                                                                                                                                         
    id = db.Column(db.Integer, primary_key=True)                                                                                                                                                                                  
    sale_id = db.Column(db.Integer, db.ForeignKey('sales.id'))                                                                                                                                                                    
    cash_start_of_day = db.Column(db.Integer)                                                                                                                                                                                     
    cash_end_of_day = db.Column(db.Integer)                                                                                                                                                                                       
    income = db.Column(db.Integer) # which is end - start                                                                                                                                                                         

    def __init__(self, cash_start_of_day = None, cash_end_of_day = None, income = None):                                                                                                                                          
        self.cash_start_of_day = cash_start_of_day                                                                                                                                                                                
        self.cash_end_of_day = cash_end_of_day                                                                                                                                                                                    
        self.income = income                                                                           

Now, if I try to add a Sales data to the branches, it didn't happen if I do this :

 branch1 = company1.branches.filter().all()

I can olny do this :

 branch1 = company1.branches[0]

If not using that [] operator, I got error message : AttributeError: 'InstrumentedList' object has no attribute'. I have already browse another answer here in SO, it got to do with that lazy things in backref definition, so I already modify my current model

But it seems like I am missing something here.. any clue? Thanks!

EDIT 1 : Unit test added & User Model added too

I already got a concise answer from Mark Hildreth, and it saves me a lot! Because of that, I am going to put here the complete unit test of this model. I am sure it will help newbies out there in their very first step in SQLAlchemy. So, here goes :

import unittest
from main import db
import models
import md5
import helper


class DbTest(unittest.TestCase):                                                                                                                                                                                                  
    def setUp(self):                                                                                                                                                                                                              
        db.drop_all()                                                                                                                                                                                                             
        db.create_all()                                                                                                                                                                                                           

    def test_user_and_company(self):                                                                                                                                                                                              
        """admin of a company"""                                                                                                                                                                                                  

        user1 = models.Users('eko', helper.hash_pass('rahasia'), 'swdev.bali@gmail.com')                                                                                                                                          
        db.session.add(user1)                                                                                                                                                                                                     
        db.session.commit()                                                                                                                                                                                                       

        """the company"""                                                                                                                                                                                                         
        company1 = models.Companies('CDI','Glagah Kidul', 'empty')                                                                                                                                                                
        db.session.add(company1)                                                                                                                                                                                                  
        company1.users.append(user1)                                                                                                                                                                                              
        db.session.commit()                                                                                                                                                                                                       
        assert company1.users[0].id == user1.id                                                                                                                                                                                   

        """branches"""                                                                                                                                                                                                            
        company1.branches.append(models.Branches(name='Kopjar',address='Penjara Malaysia', token='empty token', user_id=user1.id))                                                                                                
        company1.branches.append(models.Branches(name='Selangor',address='Koperasi Selangor', token='empty token',  user_id=user1.id))                                                                                            
        db.session.commit()                                                                                                                                                                                                       

        '''sales'''
        branch1 = company1.branches.filter(models.Branches.name=='Kopjar').first()
        assert branch1.name=='Kopjar' and branch1.company_id == company1.id


        sale = models.Sales(day='2013-02-02')
        sale.data.append(models.SaleData(cash_start_of_day = 0, cash_end_of_day = 500000, income = 500000))
        branch1.sales.append(sale)
        db.session.commit()

        assert sale.id is not None


    if __name__ == '__main__':                                                                                                                                                                                                        
        unittest.main()   

There may be bad practice in this model or unit test, and I will be delighted if you point that out :)

Thanks!

¿Fue útil?

Solución

You may wish to review the "Collection Configuration" section of the documentation. There are a few main, built-in ways to deal with how relationships are handled in SQLAlchemy, and that section of the documentation shows the various ways.

By default, when you have...

class Companies(db.Model):                                                                                                                                                                                                        
    ...
    branches = db.relationship("Branches")                                                                                                                                                                                        
    ...

Loading a Company will load all of the branches in (and by default, it loads them into a list). Therefore, after retrieving a company, company.branches returns to you a list of branches. Because it is a list, it doesn't have functions such as filter() or all(). If you are not expecting a large list of branches, this might be preferred since it might make more sense for you to use branches as a list rather than as a query object. Having this as a list allows you to do things such as...

company = session.query(Companies).first()
my_branch = Branches(...)
company.branches.append(my_branch)
session.commit()

This will properly create the new Branches object without needing to add it specifically to the session (which I think is pretty nifty).

As a side note, if you were to do type(company.branches), you would not get <type 'list'>, because in order to pull off this magic, SQLAlchemy will actually set branches to an object type that works LIKE a list, but actually has additional SQLAlchemy-specific info. This object type, if you haven't guessed, is the "InstrumentedList" that you are getting the error message about.

However, you might not want to do this; specifically, this requires you to load in all of the branches at once, and you might only want to load a few in at a time because you have thousands of them (thousands of branches in a company, just imagine the bureaucracy...)

So, you change the relation, as the docs say...

A key feature to enable management of a large collection is the so-called “dynamic” relationship. This is an optional form of relationship() which returns a Query object in place of a collection when accessed. filter() criterion may be applied as well as limits and offsets, either explicitly or via array slices:

It appears that this is what you want to do if you want to be able to do things like company.branches.filter(...).all(). To do this, you would do as the docs show, by making the lazy attribute of the relationship "dynamic"...

class Companies(db.Model):                                                                                                                                                                                                        
    ...
    branches = db.relationship("Branches", lazy='dynamic')                                                                                                                                                                                        
    ...

It looks like you've done this already for the branches -> sales relationship, but you haven't for the company -> branches relationship, which is what is giving you the error.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top