diff --git a/loops/README.txt b/loops/README.txt index f47ed36..1396cfc 100755 --- a/loops/README.txt +++ b/loops/README.txt @@ -253,7 +253,7 @@ For testing we use some simple files from the tests directory: >>> from loops import tests >>> import os >>> path = os.path.join(*tests.__path__) - >>> img.data = open(os.path.join(path, 'test_icon.png')).read() + >>> img.data = open(os.path.join(path, 'test_icon.png'), 'rb').read() >>> img.getSize() 381 >>> img.getImageSize() @@ -262,7 +262,7 @@ For testing we use some simple files from the tests directory: 'image/png' >>> pdf = Resource('A pdf File') - >>> pdf.data = open(os.path.join(path, 'test.pdf')).read() + >>> pdf.data = open(os.path.join(path, 'test.pdf'), 'rb').read() >>> pdf.getSize() 25862 >>> pdf.getImageSize() @@ -661,9 +661,9 @@ Breadcrumbs >>> view = NodeView(m114, request) >>> request.annotations.setdefault('loops.view', {})['nodeView'] = view >>> view.breadcrumbs() - [{'url': 'http://127.0.0.1/loops/views/m1', 'label': 'Menu'}, - {'url': 'http://127.0.0.1/loops/views/m1/m11', 'label': 'Zope'}, - {'url': 'http://127.0.0.1/loops/views/m1/m11/m114', 'label': ''}] + [{'label': 'Menu', 'url': 'http://127.0.0.1/loops/views/m1'}, + {'label': 'Zope', 'url': 'http://127.0.0.1/loops/views/m1/m11'}, + {'label': '', 'url': 'http://127.0.0.1/loops/views/m1/m11/m114'}] End-user Forms and Special Views @@ -714,7 +714,7 @@ been created during setup. >>> form = CreateObjectForm(m112, TestRequest()) >>> form.presetTypesForAssignment - [{'token': 'loops:concept:customer', 'title': 'Customer'}] + [{'title': 'Customer', 'token': 'loops:concept:customer'}] If the node's target is a type concept we don't get any assignments because it does not make much sense to assign resources or other concepts as diff --git a/loops/browser/auth.py b/loops/browser/auth.py index 7b9d084..111461a 100644 --- a/loops/browser/auth.py +++ b/loops/browser/auth.py @@ -1,35 +1,18 @@ -# -# Copyright (c) 2015 Helmut Merz helmutm@cy55.de -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -# +# loops.browser.auth -""" -Login, logout, unauthorized stuff. +""" Login, logout, unauthorized stuff. """ from zope.app.exception.browser.unauthorized import Unauthorized as DefaultUnauth -from zope.app.security.interfaces import IAuthentication -from zope.app.security.interfaces import ILogout, IUnauthenticatedPrincipal +from zope.authentication.interfaces import IAuthentication +from zope.authentication.interfaces import ILogout, IUnauthenticatedPrincipal +from zope.browserpage import ViewPageTemplateFile +from zope.cachedescriptors.property import Lazy from zope import component -from zope.interface import implements +from zope.interface import implementer from loops.browser.concept import ConceptView from loops.browser.node import NodeView -from zope.app.pagetemplate import ViewPageTemplateFile -from zope.cachedescriptors.property import Lazy template = ViewPageTemplateFile('auth.pt') @@ -57,9 +40,9 @@ class LoginForm(NodeView): return self +@implementer(ILogout) class Logout(object): - implements(ILogout) def __init__(self, context, request): self.context = context diff --git a/loops/browser/resource.py b/loops/browser/resource.py index 9802312..6c1076e 100644 --- a/loops/browser/resource.py +++ b/loops/browser/resource.py @@ -5,7 +5,7 @@ import os.path from zope.authentication.interfaces import IUnauthenticatedPrincipal -from zope.browserpagge import ViewPageTemplateFile +from zope.browserpage import ViewPageTemplateFile from zope.cachedescriptors.property import Lazy from zope import component from zope.catalog.interfaces import ICatalog diff --git a/loops/concept.py b/loops/concept.py index bf7ac28..2e57ea4 100644 --- a/loops/concept.py +++ b/loops/concept.py @@ -187,6 +187,8 @@ class Concept(Contained, Persistent): usePredicateIndex=usePredicateIndex) if canListObject(r.second, noSecurityCheck) and IConcept.providedBy(r.second)) + if sort is None: + return rels return sorted(rels, key=sort) def getChildren(self, predicates=None, sort='default', diff --git a/loops/expert/query.py b/loops/expert/query.py index a9e9b06..c6ab97e 100644 --- a/loops/expert/query.py +++ b/loops/expert/query.py @@ -1,23 +1,6 @@ -# -# Copyright (c) 2012 Helmut Merz helmutm@cy55.de -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -# +# loops.expert.query -""" -Generic query functionality for retrieving stuff from a loops database. +""" Generic query functionality for retrieving stuff from a loops database. """ from BTrees.IIBTree import IITreeSet @@ -25,7 +8,7 @@ from BTrees.IFBTree import IFBucket, IFBTree, IFTreeSet from BTrees.IOBTree import IOBucket, IOBTree from zope import interface, component from zope.component import adapts -from zope.interface import implements, implementer +from zope.interface import implementer from zope.cachedescriptors.property import Lazy from zope.intid.interfaces import IIntIds @@ -91,10 +74,9 @@ def AnyKeyword(value): # concept map queries +@implementer(IQuery) class ConceptMapTerm(Term): - implements(IQuery) - def __init__(self, concept, **kw): self.context = concept for k, v in kw.items(): diff --git a/loops/helpers.txt b/loops/helpers.txt index fd72818..03f5788 100755 --- a/loops/helpers.txt +++ b/loops/helpers.txt @@ -6,8 +6,6 @@ This file documents and tests a bunch of facilities that support the loops framework, mostly provided by sub-packages of the cybertools package. - ($Id$) - Let's first do some basic imports >>> from zope import interface, component @@ -22,48 +20,48 @@ Let's first do some basic imports >>> concepts, resources, views = t.setup() >>> concepts['hasType'].title - u'has Type' + 'has Type' >>> loopsRoot = site['loops'] We also add some example concepts, >>> from loops.concept import Concept - >>> cc1 = Concept(u'Zope') + >>> cc1 = Concept('Zope') >>> concepts['cc1'] = cc1 >>> cc1.title - u'Zope' + 'Zope' - >>> cc2 = Concept(u'Zope 3') + >>> cc2 = Concept('Zope 3') >>> concepts['cc2'] = cc2 >>> cc2.title - u'Zope 3' + 'Zope 3' resources, >>> from loops.resource import Resource - >>> file1 = resources['file1'] = Resource(u'A file') + >>> file1 = resources['file1'] = Resource('A file') >>> file1.resourceType = concepts['file'] (note: the use of Document is deprecated) >>> from loops.resource import Document - >>> doc1 = Document(u'Zope Info') + >>> doc1 = Document('Zope Info') >>> resources['doc1'] = doc1 >>> doc1.title - u'Zope Info' + 'Zope Info' - >>> img1 = resources['img1'] = Resource(u'An Image') + >>> img1 = resources['img1'] = Resource('An Image') >>> img1.resourceType = concepts['file'] and nodes (in view space): >>> from loops.view import Node - >>> m1 = Node(u'Home') + >>> m1 = Node('Home') >>> views['m1'] = m1 >>> m1.nodeType = 'menu' - >>> m1p1 = Node(u'Page') + >>> m1p1 = Node('Page') >>> m1['p1'] = m1p1 >>> m1p1.nodeType = 'page' @@ -85,7 +83,7 @@ As we have not yet associated a type with one of our content objects we get >>> cc1_type.typeProvider is None True >>> cc1_type.title - u'Unknown Type' + 'Unknown Type' >>> cc1_type.token '.unknown' @@ -107,9 +105,9 @@ So let's check the type of the type object: >>> type_type.typeProvider is typeObject True >>> type_type.title - u'Type' + 'Type' >>> type_type.token - u'.loops/concepts/type' + '.loops/concepts/type' >>> type_type.tokenForSearch 'loops:concept:type' >>> type_type.qualifiers @@ -117,7 +115,7 @@ So let's check the type of the type object: Now we register another type ('topic') and assign it to cc1: - >>> concepts['topic'] = Concept(u'Topic') + >>> concepts['topic'] = Concept('Topic') >>> topic = concepts['topic'] >>> topic.conceptType = typeObject >>> cc1.conceptType = topic @@ -147,9 +145,9 @@ Now let's have a look at resources. >>> component.provideAdapter(LoopsType, (IResource,), IType) >>> file1_type = IType(file1) >>> file1_type.title - u'File' + 'File' >>> file1_type.token - u'.loops/concepts/file' + '.loops/concepts/file' >>> file1_type.tokenForSearch 'loops:resource:file' >>> file1_type.qualifiers @@ -167,7 +165,7 @@ Now let's have a look at resources. >>> doc1_type = IType(doc1) >>> doc1_type.title - u'Document' + 'Document' >>> doc1_type.token 'loops.resource.Document' >>> doc1_type.tokenForSearch @@ -181,11 +179,11 @@ Now let's have a look at resources. >>> img1_type = IType(img1) >>> img1_type.token - u'.loops/concepts/file' + '.loops/concepts/file' >>> img1_type.tokenForSearch 'loops:resource:file' >>> img1_type.title - u'File' + 'File' Using the type machinery we can also specify options that may be used for controlling e.g. storage for external files. @@ -194,8 +192,8 @@ for controlling e.g. storage for external files. >>> from loops.resource import FileAdapter >>> component.provideAdapter(FileAdapter, provides=IFile) - >>> extfile = concepts['extfile'] = Concept(u'External File') - >>> ef1 = resources['ef1'] = Resource(u'Extfile #1') + >>> extfile = concepts['extfile'] = Concept('External File') + >>> ef1 = resources['ef1'] = Resource('Extfile #1') >>> ef1.resourceType = extfile >>> ef1_type = IType(ef1) >>> IType(ef1).options @@ -208,7 +206,7 @@ for controlling e.g. storage for external files. >>> IType(ef1).options ['dummy', 'storage:varsubdir', 'storage_parameters:extfiles'] >>> IType(ef1).optionsDict - {'default': ['dummy'], 'storage_parameters': 'extfiles', 'storage': 'varsubdir'} + {'default': ['dummy'], 'storage': 'varsubdir', 'storage_parameters': 'extfiles'} Can we find out somehow which types are available? This is the time to look for a type manager. This could be a utility; but in the loops package it @@ -265,10 +263,10 @@ This is done by assigning this interface to topic_type.typeProvider, i.e. the 'topic' concept, via an adapter: >>> class ITopic(Interface): pass - >>> from zope.interface import implements + >>> from zope.interface import implementer >>> class Topic(object): - ... implements(ITopic) ... def __init__(self, context): pass + >>> Topic = implementer(ITopic)(Topic) >>> from loops.interfaces import IConcept >>> component.provideAdapter(Topic, (IConcept,), ITopic) @@ -299,7 +297,7 @@ context object's type: >>> from loops.browser.common import BaseView >>> view = BaseView(cc1, TestRequest()) >>> view.typeTitle - u'Topic' + 'Topic' >>> view.typeInterface >>> view.typeAdapter @@ -321,7 +319,7 @@ been provided by the setup, but we have to register a corresponding adapter. Next we need a concept of this type: - >>> simpleQuery = concepts['simpleQuery'] = Concept(u'Simple query') + >>> simpleQuery = concepts['simpleQuery'] = Concept('Simple query') >>> simpleQuery.conceptType = query >>> sq_type = IType(simpleQuery) >>> sq_adapter = sq_type.typeInterface(simpleQuery) @@ -426,7 +424,7 @@ Folders map. >>> tFolder = addAndConfigureObject(concepts, Concept, 'folder', - ... title=u'Folder', conceptType=typeObject) + ... title='Folder', conceptType=typeObject) Usually we want to create folders only in objects of a certain type, e.g. in a domain. So we activate the folder creation action by providing @@ -444,7 +442,7 @@ If we now create a domain and set up a view on it it will provide the folder creation action. >>> general = addAndConfigureObject(concepts, Concept, 'general', - ... title=u'General', conceptType=tDomain) + ... title='General', conceptType=tDomain) >>> from loops.browser.concept import ConceptView >>> view = ConceptView(general, TestRequest()) @@ -454,7 +452,7 @@ folder creation action. Let's now create a folder. >>> f01 = addAndConfigureObject(concepts, Concept, 'f01', - ... title=u'Test Folder', conceptType=tFolder) + ... title='Test Folder', conceptType=tFolder) A folder should be associated with a FolderView that provides two actions for editing the folder and for creating a new subfolder. diff --git a/loops/tests/test_loops.py b/loops/tests/test_loops.py index 7b1b0f3..e2df9b2 100755 --- a/loops/tests/test_loops.py +++ b/loops/tests/test_loops.py @@ -1,5 +1,6 @@ import unittest, doctest +import warnings from zope.interface.verify import verifyClass from zope.intid.interfaces import IIntIds @@ -22,6 +23,7 @@ class Test(unittest.TestCase): "Basic tests for the loops package." def testInterfaces(self): + warnings.filterwarnings('ignore', category=ResourceWarning) verifyClass(ILoops, Loops) self.assertTrue(ILoops.providedBy(Loops())) verifyClass(IConcept, Concept) @@ -45,7 +47,7 @@ def test_suite(): return unittest.TestSuite(( unittest.makeSuite(Test), doctest.DocFileSuite('../README.txt', optionflags=flags), - #doctest.DocFileSuite('../helpers.txt', optionflags=flags), + doctest.DocFileSuite('../helpers.txt', optionflags=flags), )) if __name__ == '__main__': diff --git a/loops/type.py b/loops/type.py index 5afc3ab..8f02798 100644 --- a/loops/type.py +++ b/loops/type.py @@ -165,7 +165,7 @@ class ResourceType(LoopsType): @Lazy def title(self): cn = self.className - return self.typeTitles.get(cn, unicode(cn)) + return self.typeTitles.get(cn, cn) @Lazy def token(self): diff --git a/loops/versioning/browser.py b/loops/versioning/browser.py index 3021513..a8b7122 100644 --- a/loops/versioning/browser.py +++ b/loops/versioning/browser.py @@ -1,28 +1,11 @@ -# -# Copyright (c) 2015 Helmut Merz helmutm@cy55.de -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -# +# loops.versioning.browser -""" -View classes for versioning. +""" View classes for versioning. """ from zope import interface, component -from zope.app.pagetemplate import ViewPageTemplateFile -from zope.app.security.interfaces import IUnauthenticatedPrincipal +from zope.authentication.interfaces import IUnauthenticatedPrincipal +from zope.browserpage import ViewPageTemplateFile from zope.cachedescriptors.property import Lazy from loops.browser.common import BaseView diff --git a/pyproject.toml b/pyproject.toml index 379d977..2f3231e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,8 +16,9 @@ dependencies = [ "py-scopes", "markdown", "python-dotenv", - "zope.app.renderer", "zope.app.authentication", + "zope.app.exception", + "zope.app.renderer", "zope.browsermenu", "zope.i18n", "zope.pluggableauth",