collective.behavior.talcondition 1.0a2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- collective/behavior/talcondition/__init__.py +14 -0
- collective/behavior/talcondition/behavior.py +89 -0
- collective/behavior/talcondition/configure.zcml +55 -0
- collective/behavior/talcondition/extender.py +74 -0
- collective/behavior/talcondition/interfaces.py +15 -0
- collective/behavior/talcondition/locales/es/LC_MESSAGES/collective.behavior.talcondition.po +35 -0
- collective/behavior/talcondition/locales/fr/LC_MESSAGES/collective.behavior.talcondition.po +31 -0
- collective/behavior/talcondition/profiles/default/browserlayer.xml +7 -0
- collective/behavior/talcondition/profiles/default/collectivebehaviortalcondition_marker.txt +0 -0
- collective/behavior/talcondition/profiles/default/metadata.xml +4 -0
- collective/behavior/talcondition/profiles/testing/metadata.xml +7 -0
- collective/behavior/talcondition/profiles/testing/types/testtype.xml +41 -0
- collective/behavior/talcondition/profiles/testing/types.xml +5 -0
- collective/behavior/talcondition/profiles/uninstall/browserlayer.xml +7 -0
- collective/behavior/talcondition/setuphandlers.py +11 -0
- collective/behavior/talcondition/testing.py +95 -0
- collective/behavior/talcondition/testing.zcml +21 -0
- collective/behavior/talcondition/tests/__init__.py +0 -0
- collective/behavior/talcondition/tests/robot/.gitkeep +0 -0
- collective/behavior/talcondition/tests/test_behavior.py +51 -0
- collective/behavior/talcondition/tests/test_extender.py +30 -0
- collective/behavior/talcondition/tests/test_robot.py +24 -0
- collective/behavior/talcondition/tests/test_setup.py +63 -0
- collective/behavior/talcondition/tests/test_utils.py +111 -0
- collective/behavior/talcondition/utils.py +173 -0
- collective.behavior.talcondition-1.0a2-py3.11-nspkg.pth +2 -0
- collective.behavior.talcondition-1.0a2.dist-info/METADATA +228 -0
- collective.behavior.talcondition-1.0a2.dist-info/RECORD +32 -0
- collective.behavior.talcondition-1.0a2.dist-info/WHEEL +5 -0
- collective.behavior.talcondition-1.0a2.dist-info/entry_points.txt +2 -0
- collective.behavior.talcondition-1.0a2.dist-info/namespace_packages.txt +2 -0
- collective.behavior.talcondition-1.0a2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Init and utils."""
|
|
3
|
+
|
|
4
|
+
from plone import api
|
|
5
|
+
from zope.i18nmessageid import MessageFactory
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
_ = MessageFactory('collective.behavior.talcondition')
|
|
9
|
+
|
|
10
|
+
PLONE_VERSION = int(api.env.plone_version()[0])
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def initialize(context):
|
|
14
|
+
"""Initializer called when used as a Zope 2 product."""
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from collective.behavior.talcondition import _
|
|
4
|
+
from collective.behavior.talcondition.utils import evaluateExpressionFor
|
|
5
|
+
from plone.autoform import directives as form
|
|
6
|
+
from plone.autoform.interfaces import IFormFieldProvider
|
|
7
|
+
from plone.dexterity.interfaces import IDexterityContent
|
|
8
|
+
from plone.supermodel import model
|
|
9
|
+
from z3c.form.browser.checkbox import CheckBoxFieldWidget
|
|
10
|
+
from zope import schema
|
|
11
|
+
from zope.component import adapts
|
|
12
|
+
from zope.interface import alsoProvides
|
|
13
|
+
from zope.interface import implementer
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ITALCondition(model.Schema):
|
|
17
|
+
|
|
18
|
+
form.widget("tal_condition", size=80)
|
|
19
|
+
tal_condition = schema.TextLine(
|
|
20
|
+
title=_(u"TAL condition expression"),
|
|
21
|
+
description=_(
|
|
22
|
+
u"Enter a TAL expression that once evaluated "
|
|
23
|
+
"will return 'True' if content should be "
|
|
24
|
+
"available. Elements 'member', 'context' "
|
|
25
|
+
"and 'portal' are available for the "
|
|
26
|
+
"expression."
|
|
27
|
+
),
|
|
28
|
+
required=False,
|
|
29
|
+
default=u"",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
form.widget(
|
|
33
|
+
"roles_bypassing_talcondition",
|
|
34
|
+
CheckBoxFieldWidget,
|
|
35
|
+
multiple="multiple",
|
|
36
|
+
size=15,
|
|
37
|
+
)
|
|
38
|
+
roles_bypassing_talcondition = schema.Set(
|
|
39
|
+
title=_(u"Roles that will bypass the TAL condition"),
|
|
40
|
+
description=_(
|
|
41
|
+
u"Choose the different roles for which the TAL "
|
|
42
|
+
"condition will not be evaluated and always "
|
|
43
|
+
"considered 'True'."
|
|
44
|
+
),
|
|
45
|
+
required=False,
|
|
46
|
+
value_type=schema.Choice(vocabulary="plone.app.vocabularies.Roles"),
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
def evaluate(self):
|
|
50
|
+
"""Evaluate the condition and returns True or False."""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
alsoProvides(ITALCondition, IFormFieldProvider)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@implementer(ITALCondition)
|
|
57
|
+
class TALCondition(object):
|
|
58
|
+
""" """
|
|
59
|
+
|
|
60
|
+
adapts(IDexterityContent)
|
|
61
|
+
|
|
62
|
+
def __init__(self, context):
|
|
63
|
+
self.context = context
|
|
64
|
+
|
|
65
|
+
def get_tal_condition(self):
|
|
66
|
+
return getattr(self.context, "tal_condition", "")
|
|
67
|
+
|
|
68
|
+
def set_tal_condition(self, value):
|
|
69
|
+
self.context.tal_condition = value
|
|
70
|
+
|
|
71
|
+
tal_condition = property(get_tal_condition, set_tal_condition)
|
|
72
|
+
|
|
73
|
+
def get_roles_bypassing_talcondition(self):
|
|
74
|
+
return getattr(self.context, "roles_bypassing_talcondition", [])
|
|
75
|
+
|
|
76
|
+
def set_roles_bypassing_talcondition(self, value):
|
|
77
|
+
self.context.roles_bypassing_talcondition = value
|
|
78
|
+
|
|
79
|
+
roles_bypassing_talcondition = property(
|
|
80
|
+
get_roles_bypassing_talcondition, set_roles_bypassing_talcondition
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def complete_extra_expr_ctx(self, extra_expr_ctx):
|
|
84
|
+
"""Complete extra_expr_ctx, this is made to be overrided."""
|
|
85
|
+
return extra_expr_ctx
|
|
86
|
+
|
|
87
|
+
def evaluate(self, extra_expr_ctx={}):
|
|
88
|
+
extra_expr_ctx = self.complete_extra_expr_ctx(extra_expr_ctx)
|
|
89
|
+
return evaluateExpressionFor(self, extra_expr_ctx=extra_expr_ctx)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<configure
|
|
2
|
+
xmlns="http://namespaces.zope.org/zope"
|
|
3
|
+
xmlns:five="http://namespaces.zope.org/five"
|
|
4
|
+
xmlns:i18n="http://namespaces.zope.org/i18n"
|
|
5
|
+
xmlns:plone="http://namespaces.plone.org/plone"
|
|
6
|
+
xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
|
|
7
|
+
xmlns:zcml="http://namespaces.zope.org/zcml"
|
|
8
|
+
i18n_domain="collective.behavior.talcondition">
|
|
9
|
+
|
|
10
|
+
<i18n:registerTranslations directory="locales" />
|
|
11
|
+
|
|
12
|
+
<five:registerPackage package="." initialize=".initialize" />
|
|
13
|
+
|
|
14
|
+
<include package="plone.behavior" file="meta.zcml" />
|
|
15
|
+
<include package="plone.app.dexterity" />
|
|
16
|
+
|
|
17
|
+
<plone:behavior
|
|
18
|
+
title="TALCondition"
|
|
19
|
+
description="Add a TAL condition field useable to check if content should be available."
|
|
20
|
+
provides=".behavior.ITALCondition"
|
|
21
|
+
for="plone.dexterity.interfaces.IDexterityContent"
|
|
22
|
+
factory=".behavior.TALCondition"
|
|
23
|
+
marker=".interfaces.ITALConditionable"
|
|
24
|
+
/>
|
|
25
|
+
|
|
26
|
+
<adapter zcml:condition="not-have plone-5"
|
|
27
|
+
factory=".extender.TALConditionExtender"
|
|
28
|
+
for=".interfaces.ITALConditionable"
|
|
29
|
+
provides="archetypes.schemaextender.interfaces.ISchemaExtender"
|
|
30
|
+
name="collective.behavior.talcondition.extender" />
|
|
31
|
+
|
|
32
|
+
<genericsetup:registerProfile
|
|
33
|
+
name="default"
|
|
34
|
+
title="collective.behavior.talcondition"
|
|
35
|
+
directory="profiles/default"
|
|
36
|
+
description="Installs the collective.behavior.talcondition add-on."
|
|
37
|
+
provides="Products.GenericSetup.interfaces.EXTENSION"
|
|
38
|
+
/>
|
|
39
|
+
|
|
40
|
+
<genericsetup:registerProfile
|
|
41
|
+
name="uninstall"
|
|
42
|
+
title="collective.behavior.talcondition"
|
|
43
|
+
directory="profiles/uninstall"
|
|
44
|
+
description="Uninstalls the collective.behavior.talcondition add-on."
|
|
45
|
+
provides="Products.GenericSetup.interfaces.EXTENSION"
|
|
46
|
+
/>
|
|
47
|
+
|
|
48
|
+
<genericsetup:importStep
|
|
49
|
+
name="collective.behavior.talcondition-postInstall"
|
|
50
|
+
title="collective.behavior.talcondition post_install import step"
|
|
51
|
+
description="Post install import step from collective.behavior.talcondition"
|
|
52
|
+
handler=".setuphandlers.post_install">
|
|
53
|
+
</genericsetup:importStep>
|
|
54
|
+
|
|
55
|
+
</configure>
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from archetypes.schemaextender.field import ExtensionField
|
|
3
|
+
from archetypes.schemaextender.interfaces import IBrowserLayerAwareExtender
|
|
4
|
+
from archetypes.schemaextender.interfaces import ISchemaExtender
|
|
5
|
+
from collective.behavior.talcondition.interfaces import ICollectiveBehaviorTalconditionLayer
|
|
6
|
+
from collective.behavior.talcondition.interfaces import ITALConditionable
|
|
7
|
+
from Products.Archetypes.public import LinesField
|
|
8
|
+
from Products.Archetypes.public import MultiSelectionWidget
|
|
9
|
+
from Products.Archetypes.public import StringField
|
|
10
|
+
from Products.Archetypes.public import StringWidget
|
|
11
|
+
from zope.component import adapts
|
|
12
|
+
from zope.interface import implementer
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TALConditionStringField(ExtensionField, StringField):
|
|
16
|
+
"""A string field that will contain an eventual TAL condition expression."""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TALConditionLinesField(ExtensionField, LinesField):
|
|
20
|
+
"""A Lines field that will contain all roles
|
|
21
|
+
that will bypass the tal condition."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@implementer(ISchemaExtender, IBrowserLayerAwareExtender)
|
|
25
|
+
class TALConditionExtender(object):
|
|
26
|
+
"""TALCondition class"""
|
|
27
|
+
|
|
28
|
+
adapts(ITALConditionable)
|
|
29
|
+
|
|
30
|
+
layer = ICollectiveBehaviorTalconditionLayer
|
|
31
|
+
|
|
32
|
+
fields = [
|
|
33
|
+
TALConditionStringField(
|
|
34
|
+
'tal_condition',
|
|
35
|
+
required=False,
|
|
36
|
+
default='',
|
|
37
|
+
searchable=False,
|
|
38
|
+
languageIndependent=True,
|
|
39
|
+
widget=StringWidget(
|
|
40
|
+
label=(u"TAL condition expression"),
|
|
41
|
+
description=(u'Enter a TAL expression that once evaluated '
|
|
42
|
+
'will return \'True\' if content should be '
|
|
43
|
+
'available. Elements \'member\', \'context\' '
|
|
44
|
+
'and \'portal\' are available for the '
|
|
45
|
+
'expression.'),
|
|
46
|
+
i18n_domain='collective.behavior.talcondition',
|
|
47
|
+
size="80",
|
|
48
|
+
),
|
|
49
|
+
),
|
|
50
|
+
TALConditionLinesField(
|
|
51
|
+
'roles_bypassing_talcondition',
|
|
52
|
+
required=False,
|
|
53
|
+
searchable=False,
|
|
54
|
+
languageIndependent=True,
|
|
55
|
+
widget=MultiSelectionWidget(
|
|
56
|
+
size=10,
|
|
57
|
+
label=(u'Roles that will bypass the TAL condition'),
|
|
58
|
+
description=(u'Choose the different roles for which the TAL '
|
|
59
|
+
'condition will not be evaluated and always '
|
|
60
|
+
'considered \'True\'.'
|
|
61
|
+
),
|
|
62
|
+
i18n_domain='collective.behavior.talcondition',
|
|
63
|
+
),
|
|
64
|
+
enforceVocabulary=True,
|
|
65
|
+
multiValued=1,
|
|
66
|
+
vocabulary_factory='plone.app.vocabularies.Roles',
|
|
67
|
+
),
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
def __init__(self, context):
|
|
71
|
+
self.context = context
|
|
72
|
+
|
|
73
|
+
def getFields(self):
|
|
74
|
+
return self.fields
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Module where all interfaces, events and exceptions live."""
|
|
3
|
+
|
|
4
|
+
from zope.interface import Interface
|
|
5
|
+
from zope.publisher.interfaces.browser import IDefaultBrowserLayer
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ICollectiveBehaviorTalconditionLayer(IDefaultBrowserLayer):
|
|
9
|
+
"""Marker interface that defines a browser layer."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ITALConditionable(Interface):
|
|
13
|
+
"""
|
|
14
|
+
Marker interface for tal_condition field schema extender
|
|
15
|
+
"""
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Translation of collective.behavior.talcondition.pot to Spanish
|
|
2
|
+
# Leonardo J. Caballero G. <leonardocaballero@gmail.com>, 2020.
|
|
3
|
+
#
|
|
4
|
+
msgid ""
|
|
5
|
+
msgstr ""
|
|
6
|
+
"Project-Id-Version: collective.behavior.talcondition\n"
|
|
7
|
+
"POT-Creation-Date: 2015-06-05 07:17+0000\n"
|
|
8
|
+
"PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n"
|
|
9
|
+
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
10
|
+
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
11
|
+
"MIME-Version: 1.0\n"
|
|
12
|
+
"Content-Type: text/plain; charset=utf-8\n"
|
|
13
|
+
"Content-Transfer-Encoding: 8bit\n"
|
|
14
|
+
"Plural-Forms: nplurals=1; plural=0\n"
|
|
15
|
+
"Language-Code: es\n"
|
|
16
|
+
"Language-Name: Spanish\n"
|
|
17
|
+
"Preferred-Encodings: utf-8 latin1\n"
|
|
18
|
+
"Domain: collective.behavior.talcondition\n"
|
|
19
|
+
"X-Is-Fallback-For: es-ar es-bo es-cl es-co es-cr es-do es-ec es-es es-sv es-gt es-hn es-mx es-ni es-pa es-py es-pe es-pr es-us es-uy es-ve\n"
|
|
20
|
+
|
|
21
|
+
#: ../behavior.py:28
|
|
22
|
+
msgid "Choose the different roles for which the TAL condition will not be evaluated and always considered 'True'."
|
|
23
|
+
msgstr "Seleccione los roles para los cuales la condición TAL no se evaluará y siempre será 'True'."
|
|
24
|
+
|
|
25
|
+
#: ../behavior.py:17
|
|
26
|
+
msgid "Enter a TAL expression that once evaluated will return 'True' if content should be available. Elements 'member', 'context' and 'portal' are available for the expression."
|
|
27
|
+
msgstr "Ingrese una expresión TAL que, cuando se evalúe, devolverá 'True' si el elemento va a estar disponible. Los elementos 'member', 'context' y 'portal' están disponibles en la expresión."
|
|
28
|
+
|
|
29
|
+
#: ../behavior.py:27
|
|
30
|
+
msgid "Roles that will bypass the TAL condition"
|
|
31
|
+
msgstr "Roles que omiten la expresión TAL"
|
|
32
|
+
|
|
33
|
+
#: ../behavior.py:16
|
|
34
|
+
msgid "TAL condition expression"
|
|
35
|
+
msgstr "Condición en formato TAL"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
msgid ""
|
|
2
|
+
msgstr ""
|
|
3
|
+
"Project-Id-Version: PACKAGE VERSION\n"
|
|
4
|
+
"POT-Creation-Date: 2015-06-05 07:17+0000\n"
|
|
5
|
+
"PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n"
|
|
6
|
+
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
7
|
+
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
8
|
+
"MIME-Version: 1.0\n"
|
|
9
|
+
"Content-Type: text/plain; charset=utf-8\n"
|
|
10
|
+
"Content-Transfer-Encoding: 8bit\n"
|
|
11
|
+
"Plural-Forms: nplurals=1; plural=0\n"
|
|
12
|
+
"Language-Code: en\n"
|
|
13
|
+
"Language-Name: English\n"
|
|
14
|
+
"Preferred-Encodings: utf-8 latin1\n"
|
|
15
|
+
"Domain: DOMAIN\n"
|
|
16
|
+
|
|
17
|
+
#: ../behavior.py:28
|
|
18
|
+
msgid "Choose the different roles for which the TAL condition will not be evaluated and always considered 'True'."
|
|
19
|
+
msgstr "Sélectionnez les rôles pour lesquels la condition TAL ne sera pas évaluée et sera toujours 'vraie'."
|
|
20
|
+
|
|
21
|
+
#: ../behavior.py:17
|
|
22
|
+
msgid "Enter a TAL expression that once evaluated will return 'True' if content should be available. Elements 'member', 'context' and 'portal' are available for the expression."
|
|
23
|
+
msgstr "Entrez une expression TAL qui une fois évaluée, retournera 'Vrai' si l'élément doit être disponible. Les éléments 'member', 'context' et 'portal' sont disponibles dans l'expression."
|
|
24
|
+
|
|
25
|
+
#: ../behavior.py:27
|
|
26
|
+
msgid "Roles that will bypass the TAL condition"
|
|
27
|
+
msgstr "Rôles qui by-passent l'expression TAL"
|
|
28
|
+
|
|
29
|
+
#: ../behavior.py:16
|
|
30
|
+
msgid "TAL condition expression"
|
|
31
|
+
msgstr "Condition au format TAL"
|
|
File without changes
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<?xml version="1.0"?>
|
|
2
|
+
<object name="testtype" meta_type="Dexterity FTI" i18n:domain="plone"
|
|
3
|
+
xmlns:i18n="http://xml.zope.org/namespaces/i18n">
|
|
4
|
+
<property name="title" i18n:translate="">Test type</property>
|
|
5
|
+
<property name="description" i18n:translate="">None</property>
|
|
6
|
+
<property name="icon_expr">string:${portal_url}/folder_icon.png</property>
|
|
7
|
+
<property name="factory">testtype</property>
|
|
8
|
+
<property name="add_view_expr">string:${folder_url}/++add++testtype</property>
|
|
9
|
+
<property name="link_target"></property>
|
|
10
|
+
<property name="immediate_view">view</property>
|
|
11
|
+
<property name="global_allow">True</property>
|
|
12
|
+
<property name="filter_content_types">True</property>
|
|
13
|
+
<property name="allowed_content_types" />
|
|
14
|
+
<property name="allow_discussion">False</property>
|
|
15
|
+
<property name="default_view">view</property>
|
|
16
|
+
<property name="view_methods">
|
|
17
|
+
<element value="view"/>
|
|
18
|
+
</property>
|
|
19
|
+
<property name="default_view_fallback">False</property>
|
|
20
|
+
<property name="add_permission">cmf.AddPortalContent</property>
|
|
21
|
+
<property name="behaviors">
|
|
22
|
+
<element value="collective.behavior.talcondition.behavior.ITALCondition" />
|
|
23
|
+
</property>
|
|
24
|
+
<property name="schema" />
|
|
25
|
+
<!-- DO NOT use a model_source or it removes manually added fields while reapplying the profile -->
|
|
26
|
+
<!--property name="model_source" /-->
|
|
27
|
+
<alias from="(Default)" to="(dynamic view)"/>
|
|
28
|
+
<alias from="edit" to="@@edit"/>
|
|
29
|
+
<alias from="sharing" to="@@sharing"/>
|
|
30
|
+
<alias from="view" to="(selected layout)"/>
|
|
31
|
+
<action title="View" action_id="view" category="object" condition_expr=""
|
|
32
|
+
description="" icon_expr="" link_target="" url_expr="string:${object_url}"
|
|
33
|
+
visible="True">
|
|
34
|
+
<permission value="View"/>
|
|
35
|
+
</action>
|
|
36
|
+
<action title="Edit" action_id="edit" category="object" condition_expr=""
|
|
37
|
+
description="" icon_expr="" link_target=""
|
|
38
|
+
url_expr="string:${object_url}/edit" visible="True">
|
|
39
|
+
<permission value="Modify portal content"/>
|
|
40
|
+
</action>
|
|
41
|
+
</object>
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Base module for unittesting."""
|
|
3
|
+
|
|
4
|
+
from collective.behavior.talcondition import PLONE_VERSION
|
|
5
|
+
from plone import api
|
|
6
|
+
from plone.app.robotframework.testing import AUTOLOGIN_LIBRARY_FIXTURE
|
|
7
|
+
from plone.app.testing import applyProfile
|
|
8
|
+
from plone.app.testing import FunctionalTesting
|
|
9
|
+
from plone.app.testing import IntegrationTesting
|
|
10
|
+
from plone.app.testing import login
|
|
11
|
+
from plone.app.testing import PLONE_FIXTURE
|
|
12
|
+
from plone.app.testing import PloneSandboxLayer
|
|
13
|
+
from plone.app.testing import setRoles
|
|
14
|
+
from plone.app.testing import TEST_USER_ID
|
|
15
|
+
from plone.app.testing import TEST_USER_NAME
|
|
16
|
+
from plone.testing import z2
|
|
17
|
+
|
|
18
|
+
import collective.behavior.talcondition
|
|
19
|
+
import unittest
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class CollectiveBehaviorTalconditionLayer(PloneSandboxLayer):
|
|
23
|
+
|
|
24
|
+
defaultBases = (PLONE_FIXTURE,)
|
|
25
|
+
products = ("collective.behavior.talcondition",)
|
|
26
|
+
|
|
27
|
+
def setUpZope(self, app, configurationContext):
|
|
28
|
+
"""Set up Zope."""
|
|
29
|
+
# Load ZCML
|
|
30
|
+
self.loadZCML(package=collective.behavior.talcondition, name="testing.zcml")
|
|
31
|
+
for p in self.products:
|
|
32
|
+
z2.installProduct(app, p)
|
|
33
|
+
|
|
34
|
+
def setUpPloneSite(self, portal):
|
|
35
|
+
"""Set up Plone."""
|
|
36
|
+
# Set default chain for plone.app.contenttypes
|
|
37
|
+
wftool = portal["portal_workflow"]
|
|
38
|
+
wftool.setDefaultChain("simple_publication_workflow")
|
|
39
|
+
|
|
40
|
+
# Install into Plone
|
|
41
|
+
if PLONE_VERSION < 5:
|
|
42
|
+
installer = portal["portal_quickinstaller"]
|
|
43
|
+
installer.installProduct("collective.behavior.talcondition")
|
|
44
|
+
applyProfile(portal, "collective.behavior.talcondition:testing")
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
applyProfile(portal, "plone.app.contenttypes:plone-content")
|
|
48
|
+
except KeyError:
|
|
49
|
+
# BBB Plone 4
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
# Login and create some test content
|
|
53
|
+
setRoles(portal, TEST_USER_ID, ["Manager"])
|
|
54
|
+
login(portal, TEST_USER_NAME)
|
|
55
|
+
api.content.create(container=portal, type="Folder", id="folder")
|
|
56
|
+
|
|
57
|
+
# Commit so that the test browser sees these objects
|
|
58
|
+
import transaction
|
|
59
|
+
|
|
60
|
+
transaction.commit()
|
|
61
|
+
|
|
62
|
+
def tearDownZope(self, app):
|
|
63
|
+
"""Tear down Zope."""
|
|
64
|
+
for p in reversed(self.products):
|
|
65
|
+
z2.uninstallProduct(app, p)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
FIXTURE = CollectiveBehaviorTalconditionLayer(name="FIXTURE")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
INTEGRATION = IntegrationTesting(bases=(FIXTURE,), name="INTEGRATION")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
FUNCTIONAL = FunctionalTesting(bases=(FIXTURE,), name="FUNCTIONAL")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
ACCEPTANCE = FunctionalTesting(
|
|
78
|
+
bases=(FIXTURE, AUTOLOGIN_LIBRARY_FIXTURE, z2.ZSERVER_FIXTURE), name="ACCEPTANCE"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class IntegrationTestCase(unittest.TestCase):
|
|
83
|
+
"""Base class for integration tests."""
|
|
84
|
+
|
|
85
|
+
layer = INTEGRATION
|
|
86
|
+
|
|
87
|
+
def setUp(self):
|
|
88
|
+
super(IntegrationTestCase, self).setUp()
|
|
89
|
+
self.portal = self.layer["portal"]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class FunctionalTestCase(unittest.TestCase):
|
|
93
|
+
"""Base class for functional tests."""
|
|
94
|
+
|
|
95
|
+
layer = FUNCTIONAL
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<configure
|
|
2
|
+
xmlns="http://namespaces.zope.org/zope"
|
|
3
|
+
xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
|
|
4
|
+
xmlns:zcml="http://namespaces.zope.org/zcml"
|
|
5
|
+
i18n_domain="collective.behavior.talcondition">
|
|
6
|
+
|
|
7
|
+
<include file="configure.zcml" />
|
|
8
|
+
|
|
9
|
+
<class zcml:condition="not-have plone-5"
|
|
10
|
+
class="Products.ATContentTypes.content.document.ATDocument">
|
|
11
|
+
<implements interface="collective.behavior.talcondition.interfaces.ITALConditionable" />
|
|
12
|
+
</class>
|
|
13
|
+
|
|
14
|
+
<genericsetup:registerProfile
|
|
15
|
+
name="testing"
|
|
16
|
+
title="collective.behavior.talcondition tests"
|
|
17
|
+
directory="profiles/testing"
|
|
18
|
+
description="Steps to ease tests of collective.behavior.talcondition"
|
|
19
|
+
provides="Products.GenericSetup.interfaces.EXTENSION" />
|
|
20
|
+
|
|
21
|
+
</configure>
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from collective.behavior.talcondition import PLONE_VERSION
|
|
3
|
+
from collective.behavior.talcondition.behavior import ITALCondition
|
|
4
|
+
from collective.behavior.talcondition.interfaces import ITALConditionable
|
|
5
|
+
from collective.behavior.talcondition.testing import IntegrationTestCase
|
|
6
|
+
from plone.app.testing import login
|
|
7
|
+
from plone.app.testing import TEST_USER_NAME
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestBehavior(IntegrationTestCase):
|
|
11
|
+
|
|
12
|
+
def setUp(self):
|
|
13
|
+
""" """
|
|
14
|
+
super(TestBehavior, self).setUp()
|
|
15
|
+
# create a testitem
|
|
16
|
+
login(self.portal, TEST_USER_NAME)
|
|
17
|
+
self.portal.invokeFactory(id='testitem',
|
|
18
|
+
type_name='testtype',
|
|
19
|
+
title='Test type')
|
|
20
|
+
self.testitem = self.portal.testitem
|
|
21
|
+
self.adapted = ITALCondition(self.testitem)
|
|
22
|
+
|
|
23
|
+
def test_behavior(self):
|
|
24
|
+
"""Test that once enabled, the behavior do the job."""
|
|
25
|
+
if PLONE_VERSION < 5:
|
|
26
|
+
# in Plone 4, a behavior attribute is not set (until saved ?)
|
|
27
|
+
self.assertFalse(hasattr(self.testitem, 'tal_condition'))
|
|
28
|
+
# it has a 'tal_condition' attribute
|
|
29
|
+
self.assertTrue(hasattr(self.adapted, 'tal_condition'))
|
|
30
|
+
self.assertTrue(ITALConditionable.providedBy(self.portal.testitem))
|
|
31
|
+
# set a tal_condition and evaluate
|
|
32
|
+
# this is True
|
|
33
|
+
self.adapted.tal_condition = u"python:context.portal_type=='testtype'"
|
|
34
|
+
self.assertTrue(self.adapted.evaluate())
|
|
35
|
+
# this is False
|
|
36
|
+
self.adapted.tal_condition = u"python:context.portal_type=='unexisting_portal_type'"
|
|
37
|
+
self.assertFalse(self.adapted.evaluate())
|
|
38
|
+
|
|
39
|
+
def test_wrong_condition(self):
|
|
40
|
+
"""In case the condition is wrong, it just returns False
|
|
41
|
+
and a message is added to the Zope log."""
|
|
42
|
+
# using a wrong expression does not break anything
|
|
43
|
+
self.adapted.tal_condition = u'python: context.some_unexisting_method()'
|
|
44
|
+
self.assertFalse(self.adapted.evaluate())
|
|
45
|
+
|
|
46
|
+
def test_evaluate_extra_expr_ctx(self):
|
|
47
|
+
"""The 'evaluate' method can receive a 'extra_expr_ctx' dict
|
|
48
|
+
that will extend the TAL expression context."""
|
|
49
|
+
self.adapted.tal_condition = "python: value == '122'"
|
|
50
|
+
self.assertFalse(self.adapted.evaluate())
|
|
51
|
+
self.assertTrue(self.adapted.evaluate(extra_expr_ctx={'value': '122'}))
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from collective.behavior.talcondition import PLONE_VERSION
|
|
3
|
+
from collective.behavior.talcondition.interfaces import ITALConditionable
|
|
4
|
+
from collective.behavior.talcondition.testing import IntegrationTestCase
|
|
5
|
+
from collective.behavior.talcondition.utils import evaluateExpressionFor
|
|
6
|
+
from plone.app.testing import login
|
|
7
|
+
from plone.app.testing import TEST_USER_NAME
|
|
8
|
+
|
|
9
|
+
import unittest
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestExtender(IntegrationTestCase):
|
|
13
|
+
|
|
14
|
+
@unittest.skipIf(PLONE_VERSION >= 5, 'Archetypes extender test skipped in Plone 5')
|
|
15
|
+
def test_extender(self):
|
|
16
|
+
"""The extender is enabled on ATDocument in testing.zcml.
|
|
17
|
+
Check that 'tal_condition' is available."""
|
|
18
|
+
login(self.portal, TEST_USER_NAME)
|
|
19
|
+
self.portal.invokeFactory(id='doc',
|
|
20
|
+
type_name='Document',
|
|
21
|
+
title='Test document')
|
|
22
|
+
doc = self.portal.doc
|
|
23
|
+
self.assertTrue(ITALConditionable.providedBy(doc))
|
|
24
|
+
# set a tal_condition and evaluate
|
|
25
|
+
# this is True
|
|
26
|
+
doc.tal_condition = u"python:context.portal_type=='Document'"
|
|
27
|
+
self.assertTrue(evaluateExpressionFor(doc))
|
|
28
|
+
# this is False
|
|
29
|
+
doc.tal_condition = u"python:context.portal_type=='unexisting_portal_type'"
|
|
30
|
+
self.assertFalse(evaluateExpressionFor(doc))
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from ..testing import ACCEPTANCE
|
|
2
|
+
from plone.testing import layered
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import robotsuite
|
|
6
|
+
import unittest
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_suite():
|
|
10
|
+
suite = unittest.TestSuite()
|
|
11
|
+
current_dir = os.path.abspath(os.path.dirname(__file__))
|
|
12
|
+
robot_dir = os.path.join(current_dir, 'robot')
|
|
13
|
+
robot_tests = [
|
|
14
|
+
os.path.join('robot', doc) for doc in os.listdir(robot_dir)
|
|
15
|
+
if doc.endswith('.robot') and doc.startswith('test_')
|
|
16
|
+
]
|
|
17
|
+
for test in robot_tests:
|
|
18
|
+
suite.addTests([
|
|
19
|
+
layered(
|
|
20
|
+
robotsuite.RobotTestSuite(test),
|
|
21
|
+
layer=ACCEPTANCE
|
|
22
|
+
),
|
|
23
|
+
])
|
|
24
|
+
return suite
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Setup/installation tests for this package."""
|
|
3
|
+
from collective.behavior.talcondition import PLONE_VERSION
|
|
4
|
+
from collective.behavior.talcondition.testing import IntegrationTestCase
|
|
5
|
+
from plone import api
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
if PLONE_VERSION >= 5:
|
|
9
|
+
from Products.CMFPlone.utils import get_installer
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestInstall(IntegrationTestCase):
|
|
13
|
+
"""Test installation of collective.behavior.talcondition into Plone."""
|
|
14
|
+
|
|
15
|
+
def setUp(self):
|
|
16
|
+
"""Custom shared utility setup for tests."""
|
|
17
|
+
self.portal = self.layer["portal"]
|
|
18
|
+
if PLONE_VERSION < 5:
|
|
19
|
+
self.installer = api.portal.get_tool("portal_quickinstaller")
|
|
20
|
+
else:
|
|
21
|
+
self.installer = get_installer(self.portal, self.layer["request"])
|
|
22
|
+
|
|
23
|
+
def test_product_installed(self):
|
|
24
|
+
"""Test if collective.behavior.talcondition is installed with portal_quickinstaller."""
|
|
25
|
+
if PLONE_VERSION < 5:
|
|
26
|
+
self.assertTrue(
|
|
27
|
+
self.installer.isProductInstalled("collective.behavior.talcondition")
|
|
28
|
+
)
|
|
29
|
+
else:
|
|
30
|
+
self.assertTrue(
|
|
31
|
+
self.installer.is_product_installed("collective.behavior.talcondition")
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# browserlayer.xml
|
|
35
|
+
def test_browserlayer(self):
|
|
36
|
+
"""Test that ICollectiveBehaviorTalconditionLayer is registered."""
|
|
37
|
+
from collective.behavior.talcondition.interfaces import ICollectiveBehaviorTalconditionLayer
|
|
38
|
+
from plone.browserlayer import utils
|
|
39
|
+
|
|
40
|
+
self.assertIn(ICollectiveBehaviorTalconditionLayer, utils.registered_layers())
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class TestUninstall(IntegrationTestCase):
|
|
44
|
+
def setUp(self):
|
|
45
|
+
"""Custom shared utility setup for tests."""
|
|
46
|
+
self.portal = self.layer["portal"]
|
|
47
|
+
if PLONE_VERSION < 5:
|
|
48
|
+
self.installer = api.portal.get_tool("portal_quickinstaller")
|
|
49
|
+
self.installer.uninstallProducts(["collective.behavior.talcondition"])
|
|
50
|
+
else:
|
|
51
|
+
self.installer = get_installer(self.portal, self.layer["request"])
|
|
52
|
+
self.installer.uninstall_product("collective.behavior.talcondition")
|
|
53
|
+
|
|
54
|
+
def test_uninstall(self):
|
|
55
|
+
"""Test if collective.behavior.talcondition is cleanly uninstalled."""
|
|
56
|
+
if PLONE_VERSION < 5:
|
|
57
|
+
self.assertFalse(
|
|
58
|
+
self.installer.isProductInstalled("collective.behavior.talcondition")
|
|
59
|
+
)
|
|
60
|
+
else:
|
|
61
|
+
self.assertFalse(
|
|
62
|
+
self.installer.is_product_installed("collective.behavior.talcondition")
|
|
63
|
+
)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from AccessControl import Unauthorized
|
|
4
|
+
from collective.behavior.talcondition import PLONE_VERSION
|
|
5
|
+
from collective.behavior.talcondition.behavior import ITALCondition
|
|
6
|
+
from collective.behavior.talcondition.interfaces import ITALConditionable
|
|
7
|
+
from collective.behavior.talcondition.testing import IntegrationTestCase
|
|
8
|
+
from collective.behavior.talcondition.utils import _evaluateExpression
|
|
9
|
+
from collective.behavior.talcondition.utils import applyExtender
|
|
10
|
+
from collective.behavior.talcondition.utils import evaluateExpressionFor
|
|
11
|
+
from plone.app.testing import login
|
|
12
|
+
from plone.app.testing import TEST_USER_NAME
|
|
13
|
+
from zope.interface import alsoProvides
|
|
14
|
+
|
|
15
|
+
import unittest
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TestUtils(IntegrationTestCase):
|
|
19
|
+
|
|
20
|
+
def setUp(self):
|
|
21
|
+
""" """
|
|
22
|
+
super(TestUtils, self).setUp()
|
|
23
|
+
# create a testitem
|
|
24
|
+
login(self.portal, TEST_USER_NAME)
|
|
25
|
+
self.portal.invokeFactory(id='testitem',
|
|
26
|
+
type_name='testtype',
|
|
27
|
+
title='Test type')
|
|
28
|
+
self.adapted = ITALCondition(self.portal.testitem)
|
|
29
|
+
|
|
30
|
+
def test_wrong_condition(self):
|
|
31
|
+
"""In case the condition is wrong, it just returns False
|
|
32
|
+
and a message is added to the Zope log."""
|
|
33
|
+
# using a wrong expression does not break anything
|
|
34
|
+
self.adapted.tal_condition = u'python: context.some_unexisting_method()'
|
|
35
|
+
self.assertFalse(self.adapted.evaluate())
|
|
36
|
+
|
|
37
|
+
@unittest.skipIf(PLONE_VERSION >= 5, 'Archetypes extender test skipped in Plone 5')
|
|
38
|
+
def test_apply_extender(self):
|
|
39
|
+
"""Test that existing objects are correctly updated
|
|
40
|
+
after enabling extender for their meta_type."""
|
|
41
|
+
# the extender is not enabled for "Folder"
|
|
42
|
+
login(self.portal, TEST_USER_NAME)
|
|
43
|
+
self.portal.invokeFactory(id='testfolder',
|
|
44
|
+
type_name='Folder',
|
|
45
|
+
title='Test folder')
|
|
46
|
+
testfolder = self.portal.testfolder
|
|
47
|
+
self.assertFalse(hasattr(testfolder, 'tal_condition'))
|
|
48
|
+
self.assertFalse(ITALConditionable.providedBy(testfolder))
|
|
49
|
+
# enable the extender for testfolder
|
|
50
|
+
alsoProvides(testfolder, ITALConditionable)
|
|
51
|
+
# the schema is not updated until we do it
|
|
52
|
+
self.assertFalse(hasattr(testfolder, 'tal_condition'))
|
|
53
|
+
applyExtender(self.portal, meta_types=('ATFolder', ))
|
|
54
|
+
# now the field is available
|
|
55
|
+
self.assertTrue(hasattr(testfolder, 'tal_condition'))
|
|
56
|
+
|
|
57
|
+
def test_empty_condition(self):
|
|
58
|
+
# using an empty expression is considered True
|
|
59
|
+
self.adapted.tal_condition = None
|
|
60
|
+
self.assertTrue(self.adapted.evaluate())
|
|
61
|
+
|
|
62
|
+
def test_bypass_for_manager(self):
|
|
63
|
+
"""In this case, no matter the expression is False,
|
|
64
|
+
it will return True if current user is 'Manager'."""
|
|
65
|
+
# using a wrong expression does not break anything
|
|
66
|
+
self.adapted.tal_condition = "python:False"
|
|
67
|
+
self.assertFalse(evaluateExpressionFor(self.adapted))
|
|
68
|
+
self.adapted.roles_bypassing_talcondition = [u'Manager']
|
|
69
|
+
# as current user is Manager, he can bypass the expression result
|
|
70
|
+
self.assertTrue(evaluateExpressionFor(self.adapted))
|
|
71
|
+
|
|
72
|
+
def test_extra_expr_ctx(self):
|
|
73
|
+
"""It is possible to pass extra values that will be available
|
|
74
|
+
in the context of the expression."""
|
|
75
|
+
self.adapted.tal_condition = "python: value == '122'"
|
|
76
|
+
self.assertFalse(evaluateExpressionFor(self.adapted))
|
|
77
|
+
self.assertTrue(evaluateExpressionFor(self.adapted, {'value': '122'}))
|
|
78
|
+
|
|
79
|
+
def test_empty_expr_is_true(self):
|
|
80
|
+
"""Test parameter used by utils._evaluateExpression making an empty
|
|
81
|
+
expression to be considered True or False."""
|
|
82
|
+
# True by default
|
|
83
|
+
self.assertTrue(_evaluateExpression(self.portal,
|
|
84
|
+
expression=''))
|
|
85
|
+
self.assertTrue(_evaluateExpression(self.portal,
|
|
86
|
+
expression=None))
|
|
87
|
+
self.assertFalse(_evaluateExpression(self.portal,
|
|
88
|
+
expression='',
|
|
89
|
+
empty_expr_is_true=False))
|
|
90
|
+
self.assertFalse(_evaluateExpression(self.portal,
|
|
91
|
+
expression=None,
|
|
92
|
+
empty_expr_is_true=False))
|
|
93
|
+
|
|
94
|
+
def test_raise_on_error(self):
|
|
95
|
+
"""By default, a wrong expression will return False, except if raise_on_error=True,
|
|
96
|
+
in this case the exception will be raised."""
|
|
97
|
+
self.adapted.tal_condition = u'python: context.some_unexisting_method()'
|
|
98
|
+
self.assertFalse(evaluateExpressionFor(self.adapted))
|
|
99
|
+
self.assertRaises(AttributeError, evaluateExpressionFor, self.adapted, raise_on_error=True)
|
|
100
|
+
|
|
101
|
+
def test_trusted(self):
|
|
102
|
+
self.assertRaises(Unauthorized,
|
|
103
|
+
_evaluateExpression,
|
|
104
|
+
self.portal,
|
|
105
|
+
expression='python: context.unrestrictedTraverse("view")',
|
|
106
|
+
raise_on_error=True)
|
|
107
|
+
self.assertTrue(_evaluateExpression(
|
|
108
|
+
self.portal,
|
|
109
|
+
expression='python: context.unrestrictedTraverse("view")',
|
|
110
|
+
raise_on_error=True,
|
|
111
|
+
trusted=True))
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from AccessControl.class_init import InitializeClass
|
|
4
|
+
from collective.behavior.talcondition import PLONE_VERSION
|
|
5
|
+
from plone import api
|
|
6
|
+
from Products.CMFCore.Expression import createExprContext
|
|
7
|
+
from Products.CMFCore.Expression import Expression
|
|
8
|
+
from Products.PageTemplates.Expressions import createTrustedZopeEngine
|
|
9
|
+
from Products.PageTemplates.Expressions import SecureModuleImporter
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
import unittest
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger("collective.behavior.talcondition")
|
|
16
|
+
WRONG_TAL_CONDITION = (
|
|
17
|
+
"The TAL expression '{0}' for element at '{1}' is wrong. Original exception : {2}"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def evaluateExpressionFor(
|
|
22
|
+
obj,
|
|
23
|
+
extra_expr_ctx={},
|
|
24
|
+
error_pattern=WRONG_TAL_CONDITION,
|
|
25
|
+
raise_on_error=False,
|
|
26
|
+
trusted=False,
|
|
27
|
+
):
|
|
28
|
+
"""Evaluate the expression stored in 'tal_condition' of given p_obj."""
|
|
29
|
+
# get tal_condition
|
|
30
|
+
tal_condition = obj.tal_condition and obj.tal_condition.strip() or ""
|
|
31
|
+
|
|
32
|
+
roles_bypassing_talcondition = obj.roles_bypassing_talcondition
|
|
33
|
+
|
|
34
|
+
if hasattr(obj, "context"):
|
|
35
|
+
obj = obj.context
|
|
36
|
+
return _evaluateExpression(
|
|
37
|
+
obj,
|
|
38
|
+
expression=tal_condition,
|
|
39
|
+
roles_bypassing_expression=roles_bypassing_talcondition,
|
|
40
|
+
extra_expr_ctx=extra_expr_ctx,
|
|
41
|
+
error_pattern=error_pattern,
|
|
42
|
+
raise_on_error=raise_on_error,
|
|
43
|
+
trusted=trusted,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _evaluateExpression(
|
|
48
|
+
obj,
|
|
49
|
+
expression,
|
|
50
|
+
roles_bypassing_expression=[],
|
|
51
|
+
extra_expr_ctx={},
|
|
52
|
+
empty_expr_is_true=True,
|
|
53
|
+
error_pattern=WRONG_TAL_CONDITION,
|
|
54
|
+
raise_on_error=False,
|
|
55
|
+
trusted=False,
|
|
56
|
+
):
|
|
57
|
+
"""Evaluate given p_expression extending expression context with p_extra_expr_ctx."""
|
|
58
|
+
if not expression or not expression.strip():
|
|
59
|
+
return empty_expr_is_true
|
|
60
|
+
|
|
61
|
+
res = True
|
|
62
|
+
member = api.user.get_current()
|
|
63
|
+
for role in roles_bypassing_expression or []:
|
|
64
|
+
if member.has_role(str(role), obj):
|
|
65
|
+
return res
|
|
66
|
+
portal = api.portal.get()
|
|
67
|
+
if trusted:
|
|
68
|
+
ctx = createTrustedExprContext(obj.aq_inner.aq_parent, portal, obj)
|
|
69
|
+
expr_handler = TrustedExpression
|
|
70
|
+
else:
|
|
71
|
+
ctx = createExprContext(obj.aq_inner.aq_parent, portal, obj)
|
|
72
|
+
expr_handler = Expression
|
|
73
|
+
ctx.setGlobal("member", member)
|
|
74
|
+
ctx.setGlobal("context", obj)
|
|
75
|
+
ctx.setGlobal("portal", portal)
|
|
76
|
+
for extra_key, extra_value in list(extra_expr_ctx.items()):
|
|
77
|
+
ctx.setGlobal(extra_key, extra_value)
|
|
78
|
+
|
|
79
|
+
if raise_on_error:
|
|
80
|
+
res = expr_handler(expression)(ctx)
|
|
81
|
+
else:
|
|
82
|
+
try:
|
|
83
|
+
res = expr_handler(expression)(ctx)
|
|
84
|
+
except Exception as e:
|
|
85
|
+
logger.warn(error_pattern.format(expression, obj.absolute_url(), str(e)))
|
|
86
|
+
res = False
|
|
87
|
+
return res
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def createTrustedExprContext(folder, portal, object):
|
|
91
|
+
"""
|
|
92
|
+
Expression evaluator trusted (not restricted python)
|
|
93
|
+
Same as createExprContext but use the trusted engine.
|
|
94
|
+
"""
|
|
95
|
+
pm = api.portal.get_tool("portal_membership")
|
|
96
|
+
if object is None:
|
|
97
|
+
object_url = ""
|
|
98
|
+
else:
|
|
99
|
+
object_url = object.absolute_url()
|
|
100
|
+
if pm.isAnonymousUser():
|
|
101
|
+
member = None
|
|
102
|
+
else:
|
|
103
|
+
member = pm.getAuthenticatedMember()
|
|
104
|
+
data = {
|
|
105
|
+
"object_url": object_url,
|
|
106
|
+
"folder_url": folder.absolute_url(),
|
|
107
|
+
"portal_url": portal.absolute_url(),
|
|
108
|
+
"object": object,
|
|
109
|
+
"folder": folder,
|
|
110
|
+
"portal": portal,
|
|
111
|
+
"nothing": None,
|
|
112
|
+
"request": getattr(portal, "REQUEST", None),
|
|
113
|
+
"modules": SecureModuleImporter,
|
|
114
|
+
"member": member,
|
|
115
|
+
"here": object,
|
|
116
|
+
}
|
|
117
|
+
return getTrustedEngine().getContext(data)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
_trusted_engine = createTrustedZopeEngine()
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def getTrustedEngine():
|
|
124
|
+
""" """
|
|
125
|
+
return _trusted_engine
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class TrustedExpression(Expression):
|
|
129
|
+
""" """
|
|
130
|
+
|
|
131
|
+
text = ""
|
|
132
|
+
_v_compiled = None
|
|
133
|
+
|
|
134
|
+
def __init__(self, text):
|
|
135
|
+
self.text = text
|
|
136
|
+
if text.strip():
|
|
137
|
+
self._v_compiled = getTrustedEngine().compile(text)
|
|
138
|
+
|
|
139
|
+
def __call__(self, econtext):
|
|
140
|
+
if not self.text.strip():
|
|
141
|
+
return ""
|
|
142
|
+
compiled = self._v_compiled
|
|
143
|
+
if compiled is None:
|
|
144
|
+
compiled = self._v_compiled = getTrustedEngine().compile(self.text)
|
|
145
|
+
# ?? Maybe expressions should manipulate the security
|
|
146
|
+
# context stack.
|
|
147
|
+
res = compiled(econtext)
|
|
148
|
+
if isinstance(res, Exception):
|
|
149
|
+
raise res
|
|
150
|
+
return res
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
InitializeClass(Expression)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@unittest.skipIf(PLONE_VERSION >= 5, "Archetypes extender skipped in Plone 5")
|
|
157
|
+
def applyExtender(portal, meta_types):
|
|
158
|
+
"""
|
|
159
|
+
We add some fields using archetypes.schemaextender to every given p_meta_types.
|
|
160
|
+
"""
|
|
161
|
+
logger.info(
|
|
162
|
+
"Adding talcondition fields : updating the schema for meta_types %s"
|
|
163
|
+
% ",".join(meta_types)
|
|
164
|
+
)
|
|
165
|
+
at_tool = api.portal.get_tool("archetype_tool")
|
|
166
|
+
catalog = api.portal.get_tool("portal_catalog")
|
|
167
|
+
catalog.ZopeFindAndApply(
|
|
168
|
+
portal,
|
|
169
|
+
obj_metatypes=meta_types,
|
|
170
|
+
search_sub=True,
|
|
171
|
+
apply_func=at_tool._updateObject,
|
|
172
|
+
)
|
|
173
|
+
logger.info("Done!")
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import sys, types, os;p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('collective',));importlib = __import__('importlib.util');__import__('importlib.machinery');m = sys.modules.setdefault('collective', importlib.util.module_from_spec(importlib.machinery.PathFinder.find_spec('collective', [os.path.dirname(p)])));m = m or sys.modules.setdefault('collective', types.ModuleType('collective'));mp = (m or []) and m.__dict__.setdefault('__path__',[]);(p not in mp) and mp.append(p)
|
|
2
|
+
import sys, types, os;p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('collective', 'behavior'));importlib = __import__('importlib.util');__import__('importlib.machinery');m = sys.modules.setdefault('collective.behavior', importlib.util.module_from_spec(importlib.machinery.PathFinder.find_spec('collective.behavior', [os.path.dirname(p)])));m = m or sys.modules.setdefault('collective.behavior', types.ModuleType('collective.behavior'));mp = (m or []) and m.__dict__.setdefault('__path__',[]);(p not in mp) and mp.append(p);m and setattr(sys.modules['collective'], 'behavior', m)
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: collective.behavior.talcondition
|
|
3
|
+
Version: 1.0a2
|
|
4
|
+
Summary: This package contains a Dexterity behavior and AT schemaextender to add a TAL condition on a content type.
|
|
5
|
+
Home-page: http://pypi.python.org/pypi/collective.behavior.talcondition
|
|
6
|
+
Author: IMIO
|
|
7
|
+
Author-email: dev@imio.be
|
|
8
|
+
License: GPL V2
|
|
9
|
+
Keywords: Python Zope Plone
|
|
10
|
+
Classifier: Environment :: Web Environment
|
|
11
|
+
Classifier: Framework :: Plone
|
|
12
|
+
Classifier: Framework :: Plone :: Addon
|
|
13
|
+
Classifier: Framework :: Plone :: 4.3
|
|
14
|
+
Classifier: Framework :: Plone :: 5.0
|
|
15
|
+
Classifier: Framework :: Plone :: 5.1
|
|
16
|
+
Classifier: Framework :: Plone :: 5.2
|
|
17
|
+
Classifier: Framework :: Plone :: 6.0
|
|
18
|
+
Classifier: Programming Language :: Python
|
|
19
|
+
Classifier: Programming Language :: Python :: 2.7
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Operating System :: OS Independent
|
|
23
|
+
Classifier: License :: OSI Approved :: GNU General Public License v2 (GPLv2)
|
|
24
|
+
Requires-Dist: plone.api
|
|
25
|
+
Requires-Dist: setuptools
|
|
26
|
+
Requires-Dist: plone.app.dexterity
|
|
27
|
+
Provides-Extra: test
|
|
28
|
+
Requires-Dist: plone.app.testing ; extra == 'test'
|
|
29
|
+
Requires-Dist: plone.app.robotframework ; extra == 'test'
|
|
30
|
+
|
|
31
|
+
.. image:: https://travis-ci.org/collective/collective.behavior.talcondition.svg?branch=master
|
|
32
|
+
:target: https://travis-ci.org/collective/collective.behavior.talcondition
|
|
33
|
+
|
|
34
|
+
.. image:: https://coveralls.io/repos/collective/collective.behavior.talcondition/badge.png
|
|
35
|
+
:target: https://coveralls.io/r/collective/collective.behavior.talcondition
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
==========================================================================
|
|
39
|
+
collective.behavior.talcondition
|
|
40
|
+
==========================================================================
|
|
41
|
+
|
|
42
|
+
This package works for dexterity (behavior) and archetypes (schema extender).
|
|
43
|
+
|
|
44
|
+
It adds two fields on a content type or class:
|
|
45
|
+
|
|
46
|
+
* tal_condition : enter a `TAL expression <http://docs.zope.org/zope2/zope2book/AppendixC.html>`_ that once evaluated will return 'True' if content should be available. By default, elements 'member', 'context' and 'portal' are available for the expression but the TAL expression context may be extended using the 'extra_expr_ctx' parameter.
|
|
47
|
+
|
|
48
|
+
* roles_bypassing_talcondition : choose the different roles for which the TAL condition will not be evaluated and always considered \'True\'
|
|
49
|
+
|
|
50
|
+
It's then possible to use the 'evaluate' method to test the TAL condition.
|
|
51
|
+
|
|
52
|
+
How to use it
|
|
53
|
+
=============
|
|
54
|
+
|
|
55
|
+
For AT you have to provide the ITALConditionable on your class (see testing.zcml).
|
|
56
|
+
|
|
57
|
+
For DX you just have to activate the behavior on your content type.
|
|
58
|
+
|
|
59
|
+
Plone versions
|
|
60
|
+
==============
|
|
61
|
+
It has been developed and tested for Plone 4 and 5.
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
Translations
|
|
65
|
+
============
|
|
66
|
+
|
|
67
|
+
This product has been translated into
|
|
68
|
+
|
|
69
|
+
- French.
|
|
70
|
+
|
|
71
|
+
- Spanish.
|
|
72
|
+
|
|
73
|
+
You can contribute for any message missing or other new languages, join us at `Plone Collective Team <https://www.transifex.com/plone/plone-collective/>`_ into *Transifex.net* service with all world Plone translators community.
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
Changelog
|
|
78
|
+
=========
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
1.0a2 (2024-09-16)
|
|
82
|
+
------------------
|
|
83
|
+
|
|
84
|
+
- Fix dict iteration for Python 3
|
|
85
|
+
[laulaz]
|
|
86
|
+
- Updated Makefile
|
|
87
|
+
[sgeulette]
|
|
88
|
+
- Used pyenv in gha
|
|
89
|
+
[sgeulette]
|
|
90
|
+
|
|
91
|
+
1.0a1 (2023-06-21)
|
|
92
|
+
------------------
|
|
93
|
+
|
|
94
|
+
- Fix deprecated import AccessControl.class_init instead of App.class_init
|
|
95
|
+
(Plone6 compatibility)
|
|
96
|
+
[boulch]
|
|
97
|
+
- Set simplier setup with Makefile
|
|
98
|
+
[sgeulette]
|
|
99
|
+
|
|
100
|
+
0.14 (2021-06-29)
|
|
101
|
+
-----------------
|
|
102
|
+
|
|
103
|
+
- Fix pypi broken package
|
|
104
|
+
[boulch]
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
0.13 (2021-06-29)
|
|
108
|
+
-----------------
|
|
109
|
+
|
|
110
|
+
- Add uninstall profile
|
|
111
|
+
[boulch]
|
|
112
|
+
- Add Plone6 compatibily
|
|
113
|
+
[boulch]
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
0.12 (2021-04-20)
|
|
117
|
+
-----------------
|
|
118
|
+
|
|
119
|
+
- Add Transifex.net service integration to manage the translation process.
|
|
120
|
+
[macagua]
|
|
121
|
+
- Add Spanish translation
|
|
122
|
+
[macagua]
|
|
123
|
+
- Do not consider the `archetypes.schemaextender` on Plone5.
|
|
124
|
+
[gbastien]
|
|
125
|
+
- Adapted code (except, implementer) to be Python3 compatible.
|
|
126
|
+
[gbastien]
|
|
127
|
+
- Added parameter `trusted=False` to `utils._evaluateExpression`, this will use
|
|
128
|
+
a trusted expression handler instead the restricted python default.
|
|
129
|
+
[gbastien]
|
|
130
|
+
|
|
131
|
+
0.11 (2019-05-16)
|
|
132
|
+
-----------------
|
|
133
|
+
|
|
134
|
+
- Added parameter `raise_on_error` to `utils.evaluateExpressionFor` to raise an
|
|
135
|
+
error when an exception occurs instead returning False.
|
|
136
|
+
[gbastien]
|
|
137
|
+
- Added method `TALCondition.complete_extra_expr_ctx` to the behavior to
|
|
138
|
+
formalize the way to get `extra_expr_ctx` to avoid the `evaluate` method
|
|
139
|
+
to be overrided.
|
|
140
|
+
[gbastien]
|
|
141
|
+
|
|
142
|
+
0.10 (2018-11-20)
|
|
143
|
+
-----------------
|
|
144
|
+
|
|
145
|
+
- Do not break if parameter `expression` passed to
|
|
146
|
+
`utils._evaluateExpression` is None.
|
|
147
|
+
[gbastien]
|
|
148
|
+
|
|
149
|
+
0.9 (2018-10-12)
|
|
150
|
+
----------------
|
|
151
|
+
|
|
152
|
+
- Added new parameter `error_pattern=WRONG_TAL_CONDITION` to
|
|
153
|
+
`utils.evaluateExpressionFor` and underlying `utils._evaluateExpression` to
|
|
154
|
+
be able to log a custom message in case an error occurs during
|
|
155
|
+
expression evaluation.
|
|
156
|
+
[gbastien]
|
|
157
|
+
|
|
158
|
+
0.8 (2018-06-12)
|
|
159
|
+
----------------
|
|
160
|
+
|
|
161
|
+
- Mark elements using behavior with `ITALConditionable` interface so it behaves
|
|
162
|
+
like element using the AT extender.
|
|
163
|
+
[gbastien]
|
|
164
|
+
|
|
165
|
+
0.7 (2017-03-22)
|
|
166
|
+
----------------
|
|
167
|
+
|
|
168
|
+
- Use CheckBoxWidget for `ITALCondition.roles_bypassing_talcondition` to ease
|
|
169
|
+
selection when displaying several elements.
|
|
170
|
+
[gbastien]
|
|
171
|
+
|
|
172
|
+
0.6 (2016-01-12)
|
|
173
|
+
----------------
|
|
174
|
+
|
|
175
|
+
- Added parameter `empty_expr_is_true` to utils._evaluateExpression than may be True
|
|
176
|
+
or False depending that we want an empty expression to be considered True or False.
|
|
177
|
+
Previous behavior is kept in utils.evaluateExpressionFor where an empty expression
|
|
178
|
+
is considered True. This avoid managing an empty expression in the caller method
|
|
179
|
+
[gbastien]
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
0.5 (2015-12-17)
|
|
183
|
+
----------------
|
|
184
|
+
|
|
185
|
+
- Added method utils._evaluateExpression that receives an expression
|
|
186
|
+
to evaluate, it is called by utils.evaluateExpressionFor. This way, this
|
|
187
|
+
method may evaluate a TAL expression without getting it from the `tal_condition`
|
|
188
|
+
attribute on the context, in case we want to evaluate arbitrary expression
|
|
189
|
+
[gbastien]
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
0.4 (2015-09-16)
|
|
193
|
+
----------------
|
|
194
|
+
|
|
195
|
+
- Make the tal_condition field larger (from 30 to 80) for the
|
|
196
|
+
AT extender as well as for the DX behavior
|
|
197
|
+
[gbastien]
|
|
198
|
+
- Added possibility to extend TAL expression context by passing
|
|
199
|
+
an `extra_expr_ctx` dict to utils.evaluateExpressionFor, also
|
|
200
|
+
integrated to the `evaluate` method of the DX behavior
|
|
201
|
+
[gbastien]
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
0.3 (2015-07-14)
|
|
205
|
+
----------------
|
|
206
|
+
|
|
207
|
+
- Corrected default value
|
|
208
|
+
[sgeulette]
|
|
209
|
+
- Little optimization
|
|
210
|
+
[sgeulette]
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
0.2 (2015-06-18)
|
|
214
|
+
----------------
|
|
215
|
+
|
|
216
|
+
- Added field `role_bypassing_talcondition` to define who can bypass the condition
|
|
217
|
+
[anuyens]
|
|
218
|
+
- Added translations for new field
|
|
219
|
+
[gbastien]
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
0.1 (2015-06-01)
|
|
223
|
+
----------------
|
|
224
|
+
|
|
225
|
+
- Initial release.
|
|
226
|
+
[IMIO]
|
|
227
|
+
|
|
228
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
collective.behavior.talcondition-1.0a2-py3.11-nspkg.pth,sha256=8ssgqEuqS0ZiVuimfRty8lTM8d_9gCEdbC3ovq8TMBE,1077
|
|
2
|
+
collective/behavior/talcondition/__init__.py,sha256=LcX1iez83P4ovdTlWnrAMFGE8tDnoYVhLbUc-LSCruA,308
|
|
3
|
+
collective/behavior/talcondition/behavior.py,sha256=d4bvIeo21YD2sk5NGO-mJJQgYNHwyehHo9CQOJRjAnU,2824
|
|
4
|
+
collective/behavior/talcondition/configure.zcml,sha256=RkFMsZNh5h9HrFwZ9wc6nWZG8LefVd3FJiEc1pX3RIc,2172
|
|
5
|
+
collective/behavior/talcondition/extender.py,sha256=mVM0-nCgdyuAbwM7p0EBwo41lUdbqbKjtCF0hIGJX_s,2806
|
|
6
|
+
collective/behavior/talcondition/interfaces.py,sha256=553o_qz9KihxgWwHQPayjbKmic-1brFRFJX3V02s58w,432
|
|
7
|
+
collective/behavior/talcondition/setuphandlers.py,sha256=yOHG-PwzJ9w2XRmxz40QvBSd9lo84Rve7JCpKlxCe_E,256
|
|
8
|
+
collective/behavior/talcondition/testing.py,sha256=9s_Nd5BOVCDN2tufG9T45n80DM3b6gqHokKdrSr3Vxo,2894
|
|
9
|
+
collective/behavior/talcondition/testing.zcml,sha256=A8ITPtLBVw7dji7tX8-saImpAjj1Rz3f8De9TlNYeak,771
|
|
10
|
+
collective/behavior/talcondition/utils.py,sha256=SzK5ZEgUFj6IhFLniw_yLpFTRQ8cHp49i-hRPU_tz50,5037
|
|
11
|
+
collective/behavior/talcondition/locales/es/LC_MESSAGES/collective.behavior.talcondition.po,sha256=RZGb3WMN_9e9vEmQXx2VW7Y_6BLY-25uqjsMmZcZFE4,1644
|
|
12
|
+
collective/behavior/talcondition/locales/fr/LC_MESSAGES/collective.behavior.talcondition.po,sha256=dhk-pCBMIbVyrXC1rCWV73t1meM3GHUlonLUpEbqIq4,1339
|
|
13
|
+
collective/behavior/talcondition/profiles/default/browserlayer.xml,sha256=sKQd1RtLVWeUrOGsXGoDiyBNao4lPJf_-Z9eLQz3cas,196
|
|
14
|
+
collective/behavior/talcondition/profiles/default/collectivebehaviortalcondition_marker.txt,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
collective/behavior/talcondition/profiles/default/metadata.xml,sha256=grYUSMlCKb0kl1ivG9K842y6xncDXoJQcYJ96YhfvTU,67
|
|
16
|
+
collective/behavior/talcondition/profiles/testing/metadata.xml,sha256=pcOw5lpsd7qgUxIDpPhMtaiviIU5jOLUn4wOXa-GabI,181
|
|
17
|
+
collective/behavior/talcondition/profiles/testing/types.xml,sha256=Cxf94scAK_n0a29rLkRBO2TjCMDHCwDqOH1MYl95_BA,227
|
|
18
|
+
collective/behavior/talcondition/profiles/testing/types/testtype.xml,sha256=vnK75Tr-j4hnz8zUxD0BNVJbLcmra60k7EbPwkWujBg,1916
|
|
19
|
+
collective/behavior/talcondition/profiles/uninstall/browserlayer.xml,sha256=pi4OhQBeCcEUhbuz5z4EadAJE-SrA3ZIIvB4_dxl7ao,210
|
|
20
|
+
collective/behavior/talcondition/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
|
+
collective/behavior/talcondition/tests/test_behavior.py,sha256=uXjHPp2NCC7ECBrgQnj_jfMb_ZdlUNxtY_CvcA8nIhc,2352
|
|
22
|
+
collective/behavior/talcondition/tests/test_extender.py,sha256=goXwqPBMODNob_Qtif8svd1vJ0fBNEhQWvUJiPtkdiI,1321
|
|
23
|
+
collective/behavior/talcondition/tests/test_robot.py,sha256=4bSKzSOaLMuirgdQ_kzZiMh02THRvzrAhik79h73h8k,633
|
|
24
|
+
collective/behavior/talcondition/tests/test_setup.py,sha256=vvObzrvsPmLzSelqHWwEYDLifoFaSD-s4daSQvf5VUk,2483
|
|
25
|
+
collective/behavior/talcondition/tests/test_utils.py,sha256=bsEy_BL2HqEtXdXiTBrZIGhSH03aBF4Sf4Oettukmt4,5289
|
|
26
|
+
collective/behavior/talcondition/tests/robot/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
|
+
collective.behavior.talcondition-1.0a2.dist-info/METADATA,sha256=oSY1vaAac-SLWE3kAYJ_4eY6vVVvx6Zzu1Uu3mIwHZY,6612
|
|
28
|
+
collective.behavior.talcondition-1.0a2.dist-info/WHEEL,sha256=cpQTJ5IWu9CdaPViMhC9YzF8gZuS5-vlfoFihTBC86A,91
|
|
29
|
+
collective.behavior.talcondition-1.0a2.dist-info/entry_points.txt,sha256=b5cHz9FINNyhunUZ6BJOQMhgeAi2x9UcnAkA9l8KEC4,40
|
|
30
|
+
collective.behavior.talcondition-1.0a2.dist-info/namespace_packages.txt,sha256=NmZRn6gW7dnyJ88Ktn-wkFv_xKxDhHkifpxxdnroaaA,31
|
|
31
|
+
collective.behavior.talcondition-1.0a2.dist-info/top_level.txt,sha256=FyC0xnd95fkjCaKazR3nfIgNqhWMpB0mYBlzALyXKTg,11
|
|
32
|
+
collective.behavior.talcondition-1.0a2.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
collective
|