# ! /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.
Почему все это? Потому что я уже начал этот путь, и я зашел слишком далеко, чтобы просто оставить его. Кроме того, это должно быть возможно, поэтому я хочу знать, как это делается.
Для этого проекта (вишневый веб-интерфейс) я получил уже завершенный модуль с табличными классами. Я переместил его в нижнюю часть этого сообщения, потому что, возможно, это даже не нужно для вас.
Ниже приведен только один пример попытки объединения нескольких таблиц. Я выбрал простой случай с более чем двумя таблицами и соединительной таблицей. Здесь я не пишу в эти объединенные таблицы, но это необходимо где-то еще. Вот почему классы будут хорошим решением этой проблемы.
Моя попытка класса объединения
который является комбинацией данного модуля классов таблиц и примеров из этих двух веб-сайтов:
- Сопоставление класса с несколькими таблицами
- 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)]
-
Затем я попробовал еще два прямых доступа, которые предоставили мне оба KeyErrors для 'id' и 'persons.id':
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 уже является частью таблицы лиц и пользователей, поэтому мне пришлось сопоставить его дважды.
Я даже попытался удалить таблицу 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", потому что я даже не мог присоединиться к ней с таблицей "группы" или "пользователи". И он всегда говорит, что объект класса не имеет атрибута 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))
Возможно, здесь будет полезен следующий script, который создал базу данных, а также уже предоставлен. Поскольку последняя часть содержит некоторые тестовые данные, но между столбцами должны быть вкладки, без пробелов. Из-за этого этот script также можно найти как gist на github:
-- 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.!#$%&\'\*\+\-/=\?\^_\`{\|}\~\.][email protected][-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 [email protected]
2 Test User [email protected]
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;