在SqlAlchemy中声明类的顺序

时间:2019-01-12 12:20:53

标签: python postgresql flask sqlalchemy

我很想知道这个新问题,但是我几天都无法解决,这让我发疯了。

我正在建立一个小项目,以学习烧瓶,SqlAlchemy和Postrges。 我在SqlAlchemy中声明类存在主要问题。我已经通过删除所有多对多关系简化了模型。但是,尽管我认为我已经尝试了所有可能的选择,但即使是一对多的关系,现在我也遇到了新的问题。也许我一直在忽略打字错误,或者我不掌握一些基本知识。请让我知道...

因此,我在models.py中声明了以下类:

 @login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))


class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key = True)
    username = db.Column(db.String(20), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password = db.Column(db.String(60), nullable=False)
    date_registered = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
    role = db.Column(db.Integer, nullable=False)
    role_exp_date = db.Column(db.DateTime)
    #o2o
    personal_datas = db.relationship('PersonalData', uselist=False, backref='user', lazy=True)  
    persons = db.relationship('Person', uselist=False, backref='user', lazy=True)       
    #o2m
    posts = db.relationship('Post', backref='author', lazy=True)
    comments = db.relationship('PostComment', backref='author', lazy=True)
    projects_owned = db.relationship('ConstrProject', backref='owner', lazy=True)
    attachments = db.relationship('Attachment', backref='author', lazy=True)

    def __repr__(self):
        return f"{self.username} ({self.email})"



class PersonalData(db.Model):
    id = db.Column(db.Integer, primary_key = True)
    date_birth = db.Column(db.DateTime)
    image_file = db.Column(db.String(20), nullable=False, default='default.jpg')
    interests =  db.Column(db.Text)
    experties =  db.Column(db.Text)                     #Потом сделать отдельную таблицу...
    #o2o
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    #o2m


class Person(db.Model):
    id = db.Column(db.Integer, primary_key = True)
    first_name = db.Column(db.String(30), nullable=False)
    middle_name = db.Column(db.String(40), nullable=False)
    last_name = db.Column(db.String(60), nullable=False)
    email = db.Column(db.String(120))
    license = db.Column(db.String(120))
    address = db.Column(db.String(240))
    telephone = db.Column(db.String(30))
    #o2o
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    #o2m
    signers = db.relationship('Signer', backref='person', lazy=True)

    def __repr__(self):
        return f"{self.last_name.Capitalize} {self.first_name[0].Upper}. {self.middle_name[0].Upper}."


class ConstrProject(db.Model):
    __tablename__ = 'constrproject'
    id = db.Column(db.Integer, primary_key = True)
    name = db.Column(db.String(120), nullable=False, default='New Project')
    full_title = db.Column(db.Text, default='New Project')
    notes = db.Column(db.Text)
    public = db.Column(db.Boolean, default=True)                                    #? check expamples
    date_created = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) 
    date_last_edit = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
    document_template = db.Column(db.Integer, nullable=False, default=1)                        #later to m2m
    print_settings = db.Column(db.Integer, nullable=False, default=1)                           #later to m2m
    address = db.Column(db.String(240))
    #o2m
    documents = db.relationship('Document', backref='project', lazy=True)
    #m2o
    owner_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)          #+      #default = CurrentUser
    developer_id = db.Column(db.Integer, db.ForeignKey('company.id'))
    main_contractor_id = db.Column(db.Integer, db.ForeignKey('company.id'))
    architect_id = db.Column(db.Integer, db.ForeignKey('company.id'))
    subcontractor_id = db.Column(db.Integer, db.ForeignKey('company.id'))
    other_id = db.Column(db.Integer, db.ForeignKey('company.id'))


    developer = db.relationship('Company', foreign_keys=[developer_id], back_populates='constr_projects_developed')
    main_contractor = db.relationship('Company', foreign_keys=[main_contractor_id], back_populates='constr_projects_main_contracts')
    architect = db.relationship('Company', foreign_keys=[architect_id], back_populates='constr_projects_architect')
    subcontractor = db.relationship('Company', foreign_keys=[subcontractor_id], back_populates='constr_projects_subcontracts')
    other = db.relationship('Company', foreign_keys=[other_id], back_populates='constr_projects_other')


    tech_control_reps_id = db.Column(db.Integer, db.ForeignKey('signer.id'), nullable=False)
    main_contractor_reps_id = db.Column(db.Integer, db.ForeignKey('signer.id'), nullable=False)
    architect_reps_id = db.Column(db.Integer, db.ForeignKey('signer.id'), nullable=False)
    subcontractor_reps_id = db.Column(db.Integer, db.ForeignKey('signer.id'), nullable=False)
    other_reps_id = db.Column(db.Integer, db.ForeignKey('signer.id'), nullable=False)

    tech_control_reps = db.relationship('Signer', foreign_keys=[tech_control_reps_id], back_populates='tech_control_projects')
    main_contractor_reps = db.relationship('Signer', foreign_keys=[main_contractor_reps_id], back_populates='main_contractor_projects')
    architect_reps = db.relationship('Signer', foreign_keys=[architect_reps_id], back_populates='architect_projects')
    subcontractor_reps = db.relationship('Signer', foreign_keys=[subcontractor_reps_id], back_populates='subcontractor_projects')
    other_reps = db.relationship('Signer', foreign_keys=[other_reps_id], back_populates='others_projects')

    def __repr__(self):
        return f"Site: {self.name},  (id{self.id})"





class Signer(db.Model):
    id = db.Column(db.Integer, primary_key = True)
    decree = db.Column(db.String(120))
    job_title = db.Column(db.String(120))
    date_duty_start = db.Column(db.DateTime)
    date_duty_end = db.Column(db.DateTime)
    #o2m
    person_id = db.Column(db.Integer, db.ForeignKey('person.id'), nullable=False)
    company_id = db.Column(db.Integer, db.ForeignKey('company.id'), nullable=False)
    #m2o
    tech_control_projects = db.relationship('ConstrProject', back_populates='tech_control_reps')
    main_contractor_projects = db.relationship('ConstrProject', back_populates='main_contractor_reps')
    architect_projects = db.relationship('ConstrProject', back_populates='architect_reps')
    subcontractor_projects = db.relationship('ConstrProject', back_populates='subcontractor_reps')
    others_projects = db.relationship('ConstrProject', back_populates='other_reps')

    def __repr__(self):
        return f"{self.job_title} as per {self.decree}."       #название компании как подтянуть



class Company(db.Model):
    id = db.Column(db.Integer, primary_key = True)
    name = db.Column(db.String(60))
    full_title = db.Column(db.String(240))
    tin = db.Column(db.Integer)
    kpp = db.Column(db.Integer)
    ogrn = db.Column(db.Integer)
    email = db.Column(db.String(120))
    address = db.Column(db.String(240))
    telephone = db.Column(db.String(30))
    #o2m
    license_number = db.Column(db.String(40), nullable = False)
    license_date_issued = db.Column(db.DateTime) 
    license_category = db.Column(db.String(120), default = '2nd')
    license_issued_by = db.Column(db.String(120))
    license_issued_by_tin = db.Column(db.Integer)
    license_issued_by_kpp = db.Column(db.Integer)
    license_issued_by_ogrn = db.Column(db.Integer)

    signers = db.relationship('Signer', backref='company', lazy=True)
    constr_projects_developed = db.relationship('ConstrProject', back_populates='developer') 
    constr_projects_main_contracts = db.relationship('ConstrProject', back_populates='main_contractor')
    constr_projects_architect = db.relationship('ConstrProject', back_populates='architect')
    constr_projects_subcontracts = db.relationship('ConstrProject', back_populates='subcontractor')
    constr_projects_other = db.relationship('ConstrProject', back_populates='other')

    def __repr__(self):
        return f"{self.name}"



class Post(db.Model):
    id = db.Column(db.Integer, primary_key = True)
    title = db.Column(db.String(100), nullable=False)
    date_posted = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
    content = db.Column(db.Text, nullable=False)
    #o2m
    comments = db.relationship('PostComment', backref='Post', lazy=True)
    #m2o
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)


    def __repr__(self):
        return f"Post('{self.title}', '{self.date_posted}')"



class PostComment(db.Model):
    id = db.Column(db.Integer, primary_key = True)
    date_posted = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
    content = db.Column(db.Text, nullable=False)
    #m2o
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=False)

    def __repr__(self):
        return f"Comment('{self.id}', '{self.date_posted}')"




class Document(db.Model):
    id = db.Column(db.Integer, primary_key = True)
    type = db.Column(db.String(60), nullable=False, default='АОСР')
    date_last_edit = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
    notes = db.Column(db.Text)
    public = db.Column(db.Boolean, default=True)
    number = db.Column(db.String(20), nullable=False)
    date = db.Column(db.DateTime, default=datetime.utcnow)
    job_name = db.Column(db.Text)                               #? обязательный? на каком этапе делать проверку?
    job_place = db.Column(db.String(200))
    date_job_start = db.Column(db.DateTime, default=datetime.utcnow)
    date_job_end = db.Column(db.DateTime, default=datetime.utcnow)
    regulations = db.Column(db.Text)
    next_job_allowed = db.Column(db.String(240))
    attachments_user_defined = db.Column(db.Text)
    #o2m
    attachments = db.relationship('Attachment', backref='document', lazy=True)
    #m2o
    project_id = db.Column(db.Integer, db.ForeignKey('constrproject.id'), nullable=False)
    author_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    #m2m

    arch_docs  = db.Column(db.Text)
    building_materials  = db.Column(db.Text)
    work_drawings  = db.Column(db.Text)

    def __repr__(self):
        return f"АОСР ('{self.number}', '{self.job_name} {self.job_place}', '{self.project}' )"



class Attachment(db.Model):
    id = db.Column(db.Integer, primary_key = True)
    type_of_document = db.Column(db.String(60), nullable=False, default="QAC")
    number = db.Column(db.String(50), nullable=False)
    date = db.Column(db.DateTime)
    date_valid_start = db.Column(db.DateTime)
    date_valid_end = db.Column(db.DateTime)
    contents = db.Column(db.Text)
    type_of_file = db.Column(db.String(10), nullable=False, default = 'jpg')
    image_file = db.Column(db.String(20), nullable=False)
    #m2o
    author_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    document_id = db.Column(db.Integer, db.ForeignKey('document.id'), nullable=False)

    def __repr__(self):
        if self.text:
            return f'{self.text}'
        return f'+{self.type_of_document} id{self.id}  ({self.type_of_file})'

当我尝试创建“文档”的实例时,我不明白为什么 我收到这样的错误:

  

sqlalchemy.exc.AmbiguousForeignKeysError:无法确定联接   父/子表之间的关系条件   Signer.tech_control_projects-有多个外键路径   链接表。指定“ foreign_keys”参数,提供一个   这些列应视为包含外来元素的列表   对父表的键引用。

  

sqlalchemy.exc.InvalidRequestError:初始化映射器时   Mapper | ConstrProject | constrproject,表达式'tech_control_reps_id'   找不到名称(“名称'tech_control_reps_id'不是   已定义”)。如果这是一个类名,请考虑添加此名称   与类的Relationship()   在定义了两个依赖类之后。

我什至没有尝试创建这些类。添加“ foreign_keys”似乎也无济于事。所有声明为字符串的关系。我也尝试使用lambda,但没有成功。

尽管如此,改变类清除的顺序也会改变我得到的错误消息...

我找不到在每个类中具有多个多对多和一对多关系的更复杂数据库的任何好的示例(资源)。通常,示例是非常基本且显而易见的。 因此,如果您发布指向此类项目或教程的链接,我将不胜感激。

1 个答案:

答案 0 :(得分:0)

我想自己学习一些内容,因此我根据您的代码(UserPerson,{{ 1}}和Post类)。我希望这对您来说将是一个好例子(简单但不琐碎)。

Comment

一些评论:

  • 我认为在强制性一对一关系中同时使用外键并不容易。
  • 我让用户名和人名使用相同的ID号,因为它们是一对一的。
  • 我将PostgreSQL now()设置为时间戳的默认值,而不是客户端utcnow。
  • 我使时间戳记使用“带时区的时间戳记”类型-"timestamp" type (without timezone) is an abomination
  • “用户”是表的坏名称,因为它也是PostgreSQL中的关键字,因此我将其更改为“用户”。为了保持一致性,其他表格也更改为复数形式。
  • 必须存储安全的密码。
  • 我一贯使用'''SQLAlchemy one-to-one and one-to-many SSCCE''' import sqlalchemy import sqlalchemy.ext.declarative from passlib.hash import pbkdf2_sha256 Base = sqlalchemy.ext.declarative.declarative_base() class User(Base): __tablename__ = 'users' user_id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True) username = sqlalchemy.Column(sqlalchemy.String(20), unique=True, nullable=False) email = sqlalchemy.Column(sqlalchemy.String(120), unique=True, nullable=False) password = sqlalchemy.Column(sqlalchemy.String(100), nullable=False) registered = sqlalchemy.Column( sqlalchemy.DateTime(timezone=True), nullable=False, server_default=sqlalchemy.func.now() ) #o2o person = sqlalchemy.orm.relationship( 'Person', uselist=False, back_populates='user', lazy='joined' ) #o2m posts = sqlalchemy.orm.relationship('Post', back_populates='user') comments = sqlalchemy.orm.relationship('Comment', back_populates='user') def __repr__(self): return f'{self.username} ({self.email})' class Person(Base): __tablename__ = 'persons' person_id = sqlalchemy.Column( sqlalchemy.Integer, sqlalchemy.ForeignKey('users.user_id'), primary_key=True ) first_name = sqlalchemy.Column(sqlalchemy.String(30), nullable=False) middle_name = sqlalchemy.Column(sqlalchemy.String(40), nullable=False) last_name = sqlalchemy.Column(sqlalchemy.String(60), nullable=False) #o2o user = sqlalchemy.orm.relationship('User', back_populates='person', lazy='joined') def __repr__(self): return ( f'{self.last_name.upper()}' f' {self.first_name[:1].upper()}.' f' {self.middle_name[:1].upper()}.' ) class Post(Base): __tablename__ = 'posts' post_id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True) title = sqlalchemy.Column(sqlalchemy.String(100), nullable=False) posted = sqlalchemy.Column( sqlalchemy.DateTime(timezone=True), nullable=False, server_default=sqlalchemy.func.now() ) content = sqlalchemy.Column(sqlalchemy.Text, nullable=False) #o2m comments = sqlalchemy.orm.relationship('Comment', back_populates='post') #m2o user_id = sqlalchemy.Column( sqlalchemy.Integer, sqlalchemy.ForeignKey('users.user_id'), nullable=False ) user = sqlalchemy.orm.relationship('User', uselist=False, back_populates='posts', lazy='joined') def __repr__(self): return f'Post({self.title!r}, {self.posted!r})' class Comment(Base): __tablename__ = 'comments' comment_id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True) posted = sqlalchemy.Column( sqlalchemy.DateTime(timezone=True), nullable=False, server_default=sqlalchemy.func.now() ) content = sqlalchemy.Column(sqlalchemy.Text, nullable=False) #m2o user_id = sqlalchemy.Column( sqlalchemy.Integer, sqlalchemy.ForeignKey('users.user_id'), nullable=False ) user = sqlalchemy.orm.relationship( 'User', uselist=False, back_populates='comments', lazy='joined' ) post_id = sqlalchemy.Column( sqlalchemy.Integer, sqlalchemy.ForeignKey('posts.post_id'), nullable=False ) post = sqlalchemy.orm.relationship( 'Post', uselist=False, back_populates='comments', lazy='joined' ) def __repr__(self): return f'Comment({self.comment_id!r}, {self.posted!r})' def main(): engine = sqlalchemy.create_engine( 'postgresql+psycopg2:///stack', echo=True, server_side_cursors=True, use_batch_mode=True ) Base.metadata.create_all(engine) Session = sqlalchemy.orm.sessionmaker(bind=engine) session = Session() session.commit() try: the_user = session.query(User).filter(User.username == 'example').one() except sqlalchemy.orm.exc.NoResultFound: the_user = User( username='example', email='example@example.com', password=pbkdf2_sha256.hash('correct horse battery staple') ) the_user.person = Person(first_name='Ex', middle_name='', last_name='Ample') session.add(the_user) print(the_user) print(the_user.person) if not the_user.posts: the_user.posts.append(Post(title='First post', content='Lorem ipsum')) session.commit() print(the_user.posts[0]) if not the_user.posts[0].comments: the_user.posts[0].comments.append(Comment(content='Me too', user=the_user)) session.commit() print(the_user.posts[0].comments[0]) if __name__ == '__main__': main() ,因为它比back_populates更明确,并且在静态代码分析器中工作得更好。