跳转到主要内容

用户偏好框架

项目描述

此包提供创建和维护层次结构用户偏好的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 (24.8 kB 查看哈希值)

上传于

构建分发

zope.preference-5.0-py3-none-any.whl (23.7 kB 查看哈希)

上传于 Python 3

由以下支持