38
# ! /usr/bin/env python
# -*- coding: utf-8 -*-
# login_frontend.py

""" Python        2.7.3
    Cherrypy      3.2.2
    PostgreSQL    9.1
    psycopy2      2.4.5
    SQLAlchemy    0.7.10
"""

我在一个 Python/SQLAlchemy 类中加入四个表时遇到问题。我正在尝试这个,所以我可以迭代这个类的实例,而不是通过使用 ORM 连接表获得的命名元组。

为什么这一切?因为我已经开始这样做了,而且我走得太远了,不能离开它。此外,它必须是可能的,所以我想知道它是如何完成的。

对于这个项目(cherrypy web-frontend),我得到了一个已经完成的带有表格类的模块。我把它移到了这篇文章的底部,因为也许你甚至不需要它。

以下只是连接多个表类尝试的一个示例。我选择了一个简单的案例,其中不仅有两张桌子和一张连接桌子。在这里,我不会写入这些连接表,但在其他地方是必要的。这就是为什么类将是解决这个问题的好方法。


我尝试加入课程,

这是给定表类模块和这两个网站的示例的组合:

-将一个类映射到多个表
- SQLAlchemy:一个类 - 两个表

class JoinUserGroupPerson (Base):

    persons = md.tables['persons']
    users = md.tables['users']
    user_groups = md.tables['user_groups']
    groups = md.tables['groups']

    user_group_person =(
        join(persons, users, persons.c.id == users.c.id).
        join(user_groups, users.c.id == user_groups.c.user_id).
        join(groups, groups.c.id == user_groups.c.group_id))

    __table__ = user_group_person

    """ I expanded the redefinition of 'id' to three tables,
        and removed this following one, since it made no difference:
        users_id = column_property(users.c.id, user_groups.c.user_id)
    """

    id = column_property(persons.c.id, users.c.id, user_groups.c.user_id)
    groups_id = column_property(groups.c.id, user_groups.c.group_id)
    groups_name = groups.c.name

    def __init__(self, group_name, login, name, email=None, phone=None):
        self.groups_name = group_name
        self.login = login
        self.name = name
        self.email = email
        self.phone = phone

    def __repr__(self):
        return(
            "<JoinUserGroupPerson('%s', '%s', '%s', '%s', '%s')>" %(
            self.groups_name, self.login, self.name, self.email, self.phone))

此连接类的不同表访问

  • 这就是我尝试在另一个模块中查询此类的方式:

    pg = sqlalchemy.create_engine(
        'postgresql://{}:{}@{}:{}/{}'.
        format(user, password, server, port, data))
    Session = sessionmaker(bind=pg)
    s1 = Session()
    
    query = (s1.query(JoinUserGroupPerson).
        filter(JoinUserGroupPerson.login==user).
        order_by(JoinUserGroupPerson.id))
    
        record = {}
        for rowX in query:
            for colX in rowX.__table__.columns:
                record[column.name] = getattr(rowX,colX.name)
    
    
    """ RESULT:
    """
    
    
    Traceback (most recent call last):
      File "/usr/local/lib/python2.7/dist-packages/cherrypy/_cprequest.py", line 656, in respond
        response.body = self.handler()
      File "/usr/local/lib/python2.7/dist-packages/cherrypy/lib/encoding.py", line 228, in __call__
        ct.params['charset'] = self.find_acceptable_charset()
      File "/usr/local/lib/python2.7/dist-packages/cherrypy/lib/encoding.py", line 134, in find_acceptable_charset
        if encoder(encoding):
      File "/usr/local/lib/python2.7/dist-packages/cherrypy/lib/encoding.py", line 86, in encode_string
        for chunk in self.body:
      File "XXX.py", line YYY, in ZZZ
        record[colX.name] = getattr(rowX,colX.name)
    AttributeError: 'JoinUserGroupPerson' object has no attribute 'user_id'
    
  • 然后我检查了表属性:

    for rowX in query:
        return (u'{}'.format(rowX.__table__.columns))
    
    
    """ RESULT:
    """
    
    
    ['persons.id',
     'persons.name',
     'persons.email',
     'persons.phone',
     'users.id',
     'users.login',
     'user_groups.user_id',
     'user_groups.group_id',
     'groups.id',
     'groups.name']
    
  • 然后,我使用计数器检查了查询或我的课程是否根本不起作用。我起床(计数 == 5),所以前两个连接表。但是当我将条件设置为 (count == 6) 时,我又收到了第一条错误消息。AttributeError:“JoinUserGroupPerson”对象没有属性“user_id”。:

    list = []
    for rowX in query:
        for count, colX in enumerate(rowX.__table__.columns):
            list.append(getattr(rowX,colX.name))
            if count == 5:
                break
    return (u'{}'.format(list))
    
    
    """ RESULT:
    """
    
    
    [4, u'user real name', None, None, 4, u'user']
    
    
    """ which are these following six columns:
        persons[id, name, email, phone], users[id, login]
    """
    
  • 然后我检查了每一列:

    list = []
    for rowX in query:
        for colX in rowX.__table__.columns:
            list.append(colX)
    return (u'{}'.format(list))
    
    
    """ RESULT:
    """
    
    
    [Column(u'id', INTEGER(), table=, primary_key=True, nullable=False, server_default=DefaultClause(, for_update=False)),
     Column(u'name', VARCHAR(length=252), table=, nullable=False),
     Column(u'email', VARCHAR(), table=),
     Column(u'phone', VARCHAR(), table=),
     Column(u'id', INTEGER(), ForeignKey(u'persons.id'), table=, primary_key=True, nullable=False),
     Column(u'login', VARCHAR(length=60), table=, nullable=False),
     Column(u'user_id', INTEGER(), ForeignKey(u'users.id'), table=, primary_key=True, nullable=False),
     Column(u'group_id', INTEGER(), ForeignKey(u'groups.id'), table=, primary_key=True, nullable=False),
     Column(u'id', INTEGER(), table=, primary_key=True, nullable=False),
     Column(u'name', VARCHAR(length=60), table=, nullable=False)]
    
  • 然后我尝试了另外两个直接访问,这让我得到了 'id' 和 'persons.id' 的 KeyErrors:

    for rowX in query:
        return (u'{}'.format(rowX.__table__.columns['id'].name))
    
    for rowX in query:
        return (u'{}'.format(rowX.__table__.columns['persons.id'].name))
    

结论

我尝试了其他一些事情,这些事情更加令人困惑。由于他们没有透露更多信息,我没有添加它们。我看不出我的班级哪里错了。

我想,不知何故,我必须以某种方式设置类,它只会正确连接前两个表。但连接至少部分有效,因为当“user_groups”表为空时,我也得到了一个空查询。

或者也许我对这个“user_groups”表的映射做错了。由于使用连接,一些列是双列,它们需要一个额外的定义。'user_id' 已经是 person 和 users 表的一部分,所以我不得不映射它两次。

我什至尝试从联接中删除“user_groups”表,因为它在关系中(与辅助)。它给了我一个外键错误消息。但也许我只是做错了。

诚然,我什至不知道为什么......

rowX.__table__.columns                  # column names as table name suffix 

...具有与...不同的属性名称

colX in rowX.__table__.columns        # column names without table names

额外编辑

  • 另一个想法!所有这一切都可以通过继承实现吗?每个类都有自己的映射,但可能需要 user_groups 类。连接必须在单个类之间。init() 和 repr() 仍然需要重新定义。

  • 它可能与“user_groups”表有关,因为我什至无法将它与“groups”或“users”表连接起来。它总是说,类对象没有属性'user_id'。也许这与多对多关系有关。


附件

这是已经给出的 SQLAlchemy 模块,带有标题,没有关于数据库的特定信息,以及连接表的类:

#!/usr/bin/python
# vim: set fileencoding=utf-8 :

import sqlalchemy
from sqlalchemy import join
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, backref, column_property

pg = sqlalchemy.create_engine(
    'postgresql://{}@{}:{}/{}'.format(user, host, port, data))

md = sqlalchemy.MetaData(pg, True)
Base = declarative_base()



""" ... following, three of the four joined tables.
    UserGroups isn't necessary, so it wasn't part of the module.
    And the other six classes shouldn't be important for this ...
"""


class Person(Base):
    __table__ = md.tables['persons']

    def __init__(self, name, email=None, phone=None):
        self.name = name
        self.email = email
        self.phone = phone

    def __repr__(self):
        return(
            "<Person(%s, '%s', '%s', '%s')>" %(
            self.id, self.name, self.email, self.phone))

class Group(Base):
    __table__ = md.tables['groups']

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

    def __repr__(self):
        return("<Group(%s, '%s')>" %(self.id, self.name))

class User(Base):
    __table__ = md.tables['users']

    person = relationship('Person')
    groups = relationship(
        'Group', secondary=md.tables['user_groups'], order_by='Group.id',
        backref=backref('users', order_by='User.login'))

    def __init__(self, person, login):
        if isinstance(person, Person):
            self.person = person
        else:
            self.id = person
        self.login = login

    def __repr__(self):
        return("<User(%s, '%s')>" %(self.id, self.login))

也许下面的脚本,它创建了数据库,也已经给出,在这里会证明是有用的。最后一部分是一些测试数据——但列之间应该是制表符,没有空格。因此,这个脚本也可以在 github 上作为 gist找到:

-- file create_str.sql
-- database creation script
-- central script for creating all database objects

-- set the database name
\set strdbname logincore

\c admin

BEGIN;
\i str_roles.sql
COMMIT;

DROP DATABASE IF EXISTS :strdbname;
CREATE DATABASE :strdbname TEMPLATE template1 OWNER str_db_owner
    ENCODING 'UTF8';
\c :strdbname

SET ROLE str_db_owner;

BEGIN;
\i str.sql
COMMIT;
RESET ROLE;





-- file str_roles.sql
-- create roles for the database

-- owner of the database objects
SELECT create_role('str_db_owner', 'NOINHERIT');

-- role for using
SELECT create_role('str_user');

-- make str_db_owner member in all relevant roles
GRANT str_user TO str_db_owner WITH ADMIN OPTION;





-- file str.sql
-- creation of database

-- prototypes
\i str_prototypes.sql

-- domain for non empty text
CREATE DOMAIN ntext AS text CHECK (VALUE<>'');

-- domain for email addresses
CREATE DOMAIN email AS varchar(252) CHECK (is_email_address(VALUE));

-- domain for phone numbers
CREATE DOMAIN phone AS varchar(60) CHECK (is_phone_number(VALUE));

-- persons
CREATE TABLE persons (
    id    serial       PRIMARY KEY,
    name  varchar(252) NOT NULL,
    email email,
    phone phone
);

GRANT SELECT, INSERT, UPDATE, DELETE ON persons TO str_user;
GRANT USAGE ON SEQUENCE persons_id_seq TO str_user;

CREATE TABLE groups (
    id   integer     PRIMARY KEY,
    name varchar(60) UNIQUE NOT NULL
);

GRANT SELECT ON groups TO str_user;

-- database users
CREATE TABLE users (
    id    integer     PRIMARY KEY REFERENCES persons(id) ON UPDATE CASCADE,
    login varchar(60) UNIQUE NOT NULL
);

GRANT SELECT ON users TO str_user;

-- user <-> groups
CREATE TABLE user_groups (
    user_id  integer NOT NULL REFERENCES users(id)
                              ON UPDATE CASCADE ON DELETE CASCADE,
    group_id integer NOT NULL REFERENCES groups(id)
                              ON UPDATE CASCADE ON DELETE CASCADE,
    PRIMARY KEY (user_id, group_id)
);

-- functions
\i str_functions.sql





-- file str_prototypes.sql
-- prototypes for database

-- simple check for correct email address
CREATE FUNCTION is_email_address(email varchar) RETURNS boolean
    AS $CODE$
    SELECT FALSE
    $CODE$ LANGUAGE sql IMMUTABLE STRICT;

-- simple check for correct phone number
CREATE FUNCTION is_phone_number(nr varchar) RETURNS boolean
    AS $CODE$
    SELECT FALSE
    $CODE$ LANGUAGE sql IMMUTABLE STRICT;





-- file str_functions.sql
-- functions for database

-- simple check for correct email address
CREATE OR REPLACE FUNCTION is_email_address(email varchar) RETURNS boolean
    AS $CODE$
    SELECT $1 ~ E'^[A-Za-z0-9.!#$%&\'\*\+\-/=\?\^_\`{\|}\~\.]+@[-a-z0-9\.]+$'
    $CODE$ LANGUAGE sql IMMUTABLE STRICT;

-- simple check for correct phone number
CREATE OR REPLACE FUNCTION is_phone_number(nr varchar) RETURNS boolean
    AS $CODE$
    SELECT $1 ~ E'^[-+0-9\(\)/ ]+$'
    $CODE$ LANGUAGE sql IMMUTABLE STRICT;





-- file fill_str_test.sql
-- test data for database
-- between the columns are supposed to be tabs, no spaces !!!

BEGIN;

COPY persons (id, name, email) FROM STDIN;
1   Joseph Schneider    jschneid@lab.uni.de
2   Test User   jschneid@lab.uni.de
3   Hans Dampf  \N
\.
SELECT setval('persons_id_seq', (SELECT max(id) FROM persons));

COPY groups (id, name) FROM STDIN;
1   IT
2   SSG
\.

COPY users (id, login) FROM STDIN;
1   jschneid
2   tuser
3   dummy
\.

COPY user_groups (user_id, group_id) FROM STDIN;
1   1
2   1
3   2
\.

COMMIT;

4

1 回答 1

1

关于: 打印在对象中KeyError的字符串不是键,并且因为您有多个列,所以会出现一些名称变化。您可能想要做而不是,但我建议您打印以确保。repr__table__.columnsid"persons_id""persons.id"__table__.columns.keys()

关于AttributeError:默认情况下,SQLAlchemy 将列名直接映射到属性,除非您自己定义属性映射,您就是这样。您将id属性定义为column_propertyon的事实persons.c.id, users.c.id, user_groups.c.user_id意味着这些列都不再直接映射到 ORM 类上的属性,但它们仍将在columns集合中。因此,您不能将columns其用作属性名称的可迭代对象。

我没有重现你所有的代码/数据,但我用 3 个表(包括 m2m 关系)组合了一个更简单的测试用例来验证这些项目。

于 2014-07-29T20:01:42.830 回答