我正在尝试执行以下操作,定义两个实例相互引用的类,例如以下示例中的Users和Groups。用户可以属于多个组,组可以包含多个用户。实际数据存储在数据库中,这是使用外键的多对多关系的简单问题。没问题。
之后,数据通过ORM加载并存储在python对象的实例中。使用ORM(SQLAlchemy)管理backrefs仍然没有问题。
现在我想使用zope.interface和zope.schema检查python对象是否符合某些接口。这就是我遇到麻烦的地方。
import zope.schema as schema
from zope.interface import Interface, implements
class IGroup(Interface):
name = schema.TextLine(title=u"Group's name")
# user_list = schema.List(title = u"List of Users in this group", value_type = sz.Object(IUser))
class IUser(Interface):
name = schema.TextLine(title=u"User's name")
group_list = schema.List(title = u"List of Groups containing that user",
value_type = schema.Object(IGroup))
IGroup._InterfaceClass__attrs['user_list'] = zs.List(title = u"List of Users in this group", required = False, value_type = zs.Object(IUser))
class Group(object):
implements(IGroup)
def __init__(self, name):
self.name = name
self.user_list = []
class User(object):
implements(IUser)
def __init__(self, name):
self.name = name
self.group_list = []
alice = User(u'Alice')
bob = User(u'Bob')
chuck = User(u'Chuck')
group_users = Group(u"Users")
group_auditors = Group(u"Auditors")
group_administrators = Group(u"Administrators")
def add_user_in_group(user, group):
user.group_list.append(group)
group.user_list.append(user)
add_user_in_group(alice, group_users)
add_user_in_group(bob, group_users)
add_user_in_group(chuck, group_users)
add_user_in_group(chuck, group_auditors)
add_user_in_group(chuck, group_administrators)
for x in [alice, bob, chuck]:
errors = schema.getValidationErrors(IUser, x)
if errors: print errors
print "User ", x.name, " is in groups ", [y.name for y in x.group_list]
for x in [group_users, group_auditors, group_administrators]:
errors = schema.getValidationErrors(IGroup, x)
if errors: print errors
print "Group ", x.name, " contains users ", [y.name for y in x.user_list]
我的问题是注释行。我无法使用IUser定义IGroup,因为那时IUser尚未定义。我在IUser的定义之后找到了完成IGroup定义的解决方法,但这根本不令人满意,因为IUser和IGroup在不同的源文件中定义,而IGroup的一部分在定义IUser的文件中定义。
使用zope.schema有没有正确的方法呢?
答案 0 :(得分:3)
修改定义后的字段:
#imports elided
class IFoo(Interface):
bar = schema.Object(schema=Interface)
class IBar(Interface):
foo = schema.Object(schema=IFoo)
IFoo['bar'].schema = IBar
Martijn的回答似乎更加优雅和自我记录,但这更简洁。两者都不完美(相比之下,Django使用字符串名称作为外键的解决方案) - 选择你的毒药。
恕我直言,为界面指定虚线名称而不是标识符会更好。如果您发现该方法有用,您可以非常轻松地为此创建一个schema.Object的子类供您自己使用。
答案 1 :(得分:2)
您可以为IUser定义基础或抽象接口:
class IAbstractUser(Interface):
name = schema.TextLine(title=u"User's name")
class IGroup(Interface):
name = schema.TextLine(title=u"Group's name")
user_list = schema.List(
title=u"List of Users in this group",
value_type=schema.Object(IAbstractUser))
class IUser(IAbstractUser):
group_list = schema.List(
title=u"List of Groups containing that user",
value_type=schema.Object(IGroup))
因为IUser
是IAbstractUser
的子类,所以实现前者的对象也满足后者的接口。
编辑:在定义IUser之后,您仍然可以始终应用sdupton的动态事后更改IGroup接口:
IGroup['user_list'].value_type.schema = IUser
我仍然使用Abstract
接口模式来促进更好的代码文档。