用户偏好框架
项目描述
此包提供创建和维护层次结构用户偏好的API。可以通过定义模式轻松创建偏好设置。
用户偏好
实现用户偏好通常是一个痛苦的任务,因为它需要大量的自定义编码,而不断变化的偏好使得维护数据和UI变得困难。这个preference包
>>> from zope.preference import preference
通过提供一个通用的用户偏好框架,该框架使用模式来分类和描述偏好来减轻这种痛苦。
偏好组
偏好被分组到偏好组中,组内的偏好通过偏好组模式指定
>>> import zope.interface >>> import zope.schema >>> class IZMIUserSettings(zope.interface.Interface): ... """Basic User Preferences""" ... ... email = zope.schema.TextLine( ... title=u"E-mail Address", ... description=u"E-mail Address used to send notifications") ... ... skin = zope.schema.Choice( ... title=u"Skin", ... description=u"The skin that should be used for the ZMI.", ... values=['Rotterdam', 'ZopeTop', 'Basic'], ... default='Rotterdam') ... ... showZopeLogo = zope.schema.Bool( ... title=u"Show Zope Logo", ... description=u"Specifies whether Zope logo should be displayed " ... u"at the top of the screen.", ... default=True)
现在我们可以实例化偏好组。每个偏好组都必须有一个ID,以便可以访问它,以及可选的标题和描述字段,用于UI目的
>>> settings = preference.PreferenceGroup( ... "ZMISettings", ... schema=IZMIUserSettings, ... title=u"ZMI User Settings", ... description=u"")
请注意,偏好组提供了它所代表的接口
>>> IZMIUserSettings.providedBy(settings) True
组和其ID、模式以及标题都是直接可用的
>>> settings.__id__ 'ZMISettings' >>> settings.__schema__ <InterfaceClass zope.preference.README.IZMIUserSettings> >>> settings.__title__ 'ZMI User Settings'
那么我们来请求偏好组中的皮肤设置
>>> settings.skin #doctest:+ELLIPSIS Traceback (most recent call last): ... zope.security.interfaces.NoInteraction
为什么查找失败了?因为我们还没有指定一个要查找偏好的主体,为此我们需要创建一个新的交互
>>> class Principal: ... def __init__(self, id): ... self.id = id >>> principal = Principal('zope.user')>>> class Participation: ... interaction = None ... def __init__(self, principal): ... self.principal = principal>>> participation = Participation(principal)>>> import zope.security.management >>> zope.security.management.newInteraction(participation)
我们还需要一个用于主体的IAnnotations适配器,以便我们能够存储设置
>>> from zope.annotation.interfaces import IAnnotations >>> @zope.interface.implementer(IAnnotations) ... class PrincipalAnnotations(dict): ... data = {} ... def __new__(class_, principal, context): ... try: ... annotations = class_.data[principal.id] ... except KeyError: ... annotations = dict.__new__(class_) ... class_.data[principal.id] = annotations ... return annotations ... def __init__(self, principal, context): ... pass>>> from zope.component import provideAdapter >>> provideAdapter(PrincipalAnnotations, ... (Principal, zope.interface.Interface), IAnnotations)
现在让我们再次尝试访问设置
>>> settings.skin 'Rotterdam'
这是默认值,因为我们还没有设置它。我们现在可以重新分配这个值
>>> settings.skin = 'Basic' >>> settings.skin 'Basic'
然而,你无法随意输入任何值,因为赋值之前会进行验证
>>> settings.skin = 'MySkin' Traceback (most recent call last): ... ConstraintNotSatisfied: MySkin
偏好组树
如果你可以创建完整的偏好,那么这些偏好将不会非常强大。因此,让我们为我们的ZMI用户设置创建一个子组,我们可以在这里调整文件夹内容视图的外观和感觉
>>> class IFolderSettings(zope.interface.Interface): ... """Basic User Preferences""" ... ... shownFields = zope.schema.Set( ... title=u"Shown Fields", ... description=u"Fields shown in the table.", ... value_type=zope.schema.Choice(['name', 'size', 'creator']), ... default=set(['name', 'size'])) ... ... sortedBy = zope.schema.Choice( ... title=u"Sorted By", ... description=u"Data field to sort by.", ... values=['name', 'size', 'creator'], ... default='name')>>> folderSettings = preference.PreferenceGroup( ... "ZMISettings.Folder", ... schema=IFolderSettings, ... title=u"Folder Content View Settings")
请注意,ID的选择是为了使父ID成为子ID的前缀。我们的新偏好子组现在应该作为父组的属性或项目可用……
>>> settings.Folder Traceback (most recent call last): ... AttributeError: 'Folder' is not a preference or sub-group. >>> settings['Folder'] Traceback (most recent call last): ... KeyError: 'Folder'
但在注册组为实用工具之前,它是不存在的
>>> from zope.preference import interfaces >>> from zope.component import provideUtility>>> provideUtility(settings, interfaces.IPreferenceGroup, ... name='ZMISettings') >>> provideUtility(folderSettings, interfaces.IPreferenceGroup, ... name='ZMISettings.Folder')
如果我们现在再次尝试查找子组,我们应该会成功
>>> settings.Folder #doctest:+ELLIPSIS <zope.preference.preference.PreferenceGroup object at ...>>>> settings['Folder'] #doctest:+ELLIPSIS <zope.preference.preference.PreferenceGroup object at ...> >>> 'Folder' in settings True >>> list(settings) [<zope.preference.preference.PreferenceGroup object at ...>]
虽然偏好组的注册是平面的,但id的谨慎命名允许我们有一个偏好树。请注意,这个模式与Python中处理模块的方式非常相似;它们存储在sys.modules中的平面字典中,但由于命名,它们看起来像是在命名空间树中。
同时,还有可以与Python包相比的偏好类别。它们基本上是用于UI更好地组织偏好的高级分组概念。可以通过简单地提供一个额外的接口将偏好组转换为类别
>>> zope.interface.alsoProvides(folderSettings, interfaces.IPreferenceCategory)>>> interfaces.IPreferenceCategory.providedBy(folderSettings) True
偏好组对象也可以持有任意属性,但鉴于它们不是持久的,因此必须谨慎使用
>>> settings.not_in_schema = 1 >>> settings.not_in_schema 1 >>> del settings.not_in_schema >>> settings.not_in_schema Traceback (most recent call last): ... AttributeError: 'not_in_schema' is not a preference or sub-group.
默认偏好
有时希望在站点的基础上定义默认设置,而不是仅仅使用模式中的默认值。偏好包提供了一个模块,实现了默认偏好提供者,可以将它作为未命名的实用工具添加到每个站点
>>> from zope.preference import default
我们将首先创建一个新的根站点
>>> from zope.site.folder import rootFolder >>> root = rootFolder() >>> from zope.site.site import LocalSiteManager >>> rsm = LocalSiteManager(root) >>> root.setSiteManager(rsm)
然后我们将新站点设置为当前站点
>>> zope.component.hooks.setSite(root)
现在我们可以将默认偏好提供者注册到根站点
>>> provider = addUtility( ... rsm, default.DefaultPreferenceProvider(), ... interfaces.IDefaultPreferenceProvider)
所以在我们为偏好设置一个显式的默认值之前,会使用schema字段中的默认值
>>> settings.Folder.sortedBy 'name'
但现在如果我们使用提供者设置一个新的默认值,
>>> defaultFolder = provider.getDefaultPreferenceGroup('ZMISettings.Folder') >>> defaultFolder.sortedBy = 'size'
那么设置的默认值就会改变
>>> settings.Folder.sortedBy 'size'
因为ZMISettings.Folder被声明为偏好类别,所以它的默认实现也是
>>> interfaces.IPreferenceCategory.providedBy(defaultFolder) True
默认偏好提供者还隐式地从父站点获取默认值。所以如果我们添加一个名为folder1的新子文件夹,将其设置为站点并设置为活动站点,
>>> from zope.site.folder import Folder >>> root['folder1'] = Folder() >>> folder1 = root['folder1']>>> from zope.site.site import LocalSiteManager >>> sm1 = LocalSiteManager(folder1) >>> folder1.setSiteManager(sm1) >>> zope.component.hooks.setSite(folder1)
并在那里添加一个默认提供者,
>>> provider1 = addUtility( ... sm1, default.DefaultPreferenceProvider(), ... interfaces.IDefaultPreferenceProvider)
那么我们仍然会得到根的默认值,因为我们没有在更高的默认提供者中定义任何值
>>> settings.Folder.sortedBy 'size'
但如果我们为sortedBy提供一个新提供者的默认值,
>>> defaultFolder1 = provider1.getDefaultPreferenceGroup('ZMISettings.Folder') >>> defaultFolder1.sortedBy = 'creator'
那么就会使用它
>>> settings.Folder.sortedBy 'creator'
当然,一旦根站点再次成为我们的活动站点,
>>> zope.component.hooks.setSite(root)
就会使用根提供者的默认值
>>> settings.Folder.sortedBy 'size'
当然,一旦用户提供了实际的值,那么所有的默认值就不再相关了
>>> settings.Folder.sortedBy = 'name' >>> settings.Folder.sortedBy 'name'
哦,我提到过输入的值总是要经过验证的吗?所以你无法随意分配任何旧值
>>> settings.Folder.sortedBy = 'foo' Traceback (most recent call last): ... ConstraintNotSatisfied: foo
最后,如果用户删除了他的/她的显式设置,我们将回到默认值
>>> del settings.Folder.sortedBy >>> settings.Folder.sortedBy 'size'
与常规偏好组一样,默认偏好组按照匹配的层次结构排列
>>> defaultSettings = provider.getDefaultPreferenceGroup('ZMISettings') >>> defaultSettings.get('Folder') <zope.preference.default.DefaultPreferenceGroup object at ...> >>> defaultSettings.Folder <zope.preference.default.DefaultPreferenceGroup object at ...>
它们还会报告有用的AttributeErrors,以处理错误的访问
>>> defaultSettings.not_in_schema Traceback (most recent call last): ... AttributeError: 'not_in_schema' is not a preference or sub-group.
使用ZCML创建偏好组
如果您正在使用Zope 3的用户偏好系统,您就不必像上面那样手动设置偏好组(当然)。我们将使用ZCML。首先,我们需要注册指令
>>> from zope.configuration import xmlconfig >>> import zope.preference >>> context = xmlconfig.file('meta.zcml', zope.preference)
然后系统会设置一个根偏好组
>>> context = xmlconfig.string(''' ... <configure ... xmlns="http://namespaces.zope.org/zope" ... i18n_domain="test"> ... ... <preferenceGroup ... id="" ... title="User Preferences" ... /> ... ... </configure>''', context)
现在我们可以按照预期的方式使用偏好系统。我们可以通过以下方式访问文件夹设置
>>> import zope.component >>> prefs = zope.component.getUtility(interfaces.IPreferenceGroup) >>> prefs.ZMISettings.Folder.sortedBy 'size'
让我们通过ZCML在新的名称下再次注册ZMI设置
>>> context = xmlconfig.string(''' ... <configure ... xmlns="http://namespaces.zope.org/zope" ... i18n_domain="test"> ... ... <preferenceGroup ... id="ZMISettings2" ... title="ZMI Settings NG" ... schema="zope.preference.README.IZMIUserSettings" ... category="true" ... /> ... ... </configure>''', context)>>> prefs.ZMISettings2 #doctest:+ELLIPSIS <zope.preference.preference.PreferenceGroup object at ...>>>> prefs.ZMISettings2.__title__ 'ZMI Settings NG'>>> IZMIUserSettings.providedBy(prefs.ZMISettings2) True >>> interfaces.IPreferenceCategory.providedBy(prefs.ZMISettings2) True
然后可以通过精心构造的id重新构建树
>>> context = xmlconfig.string(''' ... <configure ... xmlns="http://namespaces.zope.org/zope" ... i18n_domain="test"> ... ... <preferenceGroup ... id="ZMISettings2.Folder" ... title="Folder Settings" ... schema="zope.preference.README.IFolderSettings" ... /> ... ... </configure>''', context)>>> prefs.ZMISettings2 #doctest:+ELLIPSIS <zope.preference.preference.PreferenceGroup object at ...>>>> prefs.ZMISettings2.Folder.__title__ 'Folder Settings'>>> IFolderSettings.providedBy(prefs.ZMISettings2.Folder) True >>> interfaces.IPreferenceCategory.providedBy(prefs.ZMISettings2.Folder) False
简单的Python级别访问
如果设置了站点,获取用户偏好非常简单
>>> from zope.preference import UserPreferences >>> prefs2 = UserPreferences() >>> prefs2.ZMISettings.Folder.sortedBy 'size'
此功能通常也注册为适配器,
>>> from zope.location.interfaces import ILocation >>> provideAdapter(UserPreferences, [ILocation], interfaces.IUserPreferences)
这样您就可以将任何位置适配到用户偏好
>>> prefs3 = interfaces.IUserPreferences(folder1) >>> prefs3.ZMISettings.Folder.sortedBy 'creator'
遍历
好吧,所以所有这些对象都很好,但它们并没有让在页面模板中访问偏好变得更容易。因此,已创建一个特殊的遍历命名空间,这使得通过遍历路径访问偏好变得非常简单。但在我们可以使用路径表达式之前,我们必须注册所有必要的遍历组件和特殊的 偏好 命名空间
>>> import zope.traversing.interfaces >>> provideAdapter(preference.preferencesNamespace, [None], ... zope.traversing.interfaces.ITraversable, ... 'preferences')
现在我们可以这样访问偏好
>>> from zope.traversing.api import traverse >>> traverse(None, '++preferences++ZMISettings/skin') 'Basic' >>> traverse(None, '++preferences++/ZMISettings/skin') 'Basic'
安全
你可能已经想知道偏好在哪些权限下可用。实际上,它们是公开可用的(CheckerPublic),但这不是问题,因为可用的值是专门为当前用户查找的。用户为什么不能完全访问自己的偏好呢?
让我们使用安全机制实际使用的函数创建一个检查器
>>> checker = preference.PreferenceGroupChecker(settings) >>> checker.permission_id('skin') Global(CheckerPublic,zope.security.checker) >>> checker.setattr_permission_id('skin') Global(CheckerPublic,zope.security.checker)
id、标题、描述和模式对访问是公开的,但绝对不提供修改权限
>>> checker.permission_id('__id__') Global(CheckerPublic,zope.security.checker) >>> checker.setattr_permission_id('__id__') is None True
唯一可能被安全破坏的方式是当可以覆盖注释属性时。然而,这个属性根本不对外开放,包括读取访问
>>> checker.permission_id('annotation') is None True >>> checker.setattr_permission_id('annotation') is None True
变更记录
5.0 (2023-02-10)
停止支持Python 2.7、3.4、3.5、3.6。
添加对Python 3.8、3.9、3.10、3.11的支持。
4.1.0 (2018-09-27)
支持更新的zope.configuration和persistent。请参阅 问题2。
添加对Python 3.7和PyPy3的支持。
停止支持Python 3.3。
4.0.0 (2017-05-09)
添加对Python 3.4、3.5和3.6的支持。
添加对PyPy的支持。
停止支持Python 2.6。
4.0.0a1 (2013-02-24)
添加了对Python 3.3的支持。
将废弃的 zope.interface.implements 使用替换为等效的 zope.interface.implementer 装饰器。
停止支持Python 2.4和2.5。
重构测试,不再依赖于 zope.app.testing。
在访问偏好组的父级时修复了一个错误。
3.8.0 (2010-06-12)
从 zope.app.preference 中分离出来。
通过使用 zope.component.hooks 丢弃了对 zope.app.component.hooks 的依赖。
通过使用 zope.container 丢弃了对 zope.app.container 的依赖。
项目详情
下载文件
下载适合您平台的自定义文件。如果您不确定选择哪个,请了解有关 安装包 的更多信息。
源分发
构建分发
zope.preference-5.0.tar.gz 的哈希
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 6b3d63c75e4e0c051a89a6adfc67c9f0a9e7c884de2cd8054cc435dc37d7f3d8 |
|
MD5 | a53021e206dbb47306293a44b39ebda8 |
|
BLAKE2b-256 | d44d6fd771cc0be13581c8f0c2c362f6b692d7c4ed4a8acef9ac1414e76036a9 |
zope.preference-5.0-py3-none-any.whl 的哈希
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 99e50e3364983f8e31b7606c2fdabaef0e278a60d32a378c73862a8c481766ed |
|
MD5 | a8a3f95aa9ce89bc307632409263f97b |
|
BLAKE2b-256 | 26267858841ab4336347dc3e242bee34d504ae6b14aeac264989cb200e9c04b0 |