diff --git a/loops/base.py b/loops/base.py index 02b9925..38d8469 100644 --- a/loops/base.py +++ b/loops/base.py @@ -1,30 +1,13 @@ -# -# Copyright (c) 2019 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.base -""" -Implementation of loops root object. +""" Implementation of loops root object. """ -from zope.app.container.btree import BTreeContainer -from zope.app.folder.folder import Folder -from zope.app.folder.interfaces import IFolder +from zope.container.btree import BTreeContainer +from zope.site.folder import Folder +from zope.site.interfaces import IFolder from zope.traversing.api import getPath, traverse -from zope.interface import implements +from zope.interface import implementer from cybertools.util.jeep import Jeep from loops.interfaces import ILoops @@ -32,17 +15,8 @@ from loops.interfaces import ILoops loopsPrefix = '.loops' +@implementer(ILoops) class Loops(Folder): -#class Loops(BTreeContainer): - - implements(ILoops) - - #def getSiteManager(self): - # return self.__parent__.getSiteManager() - - #@property - #def _SampleContainer__data(self): - # return self.data _skinName = '' def getSkinName(self): return self._skinName @@ -72,10 +46,7 @@ class Loops(Folder): return self.get('records') def getLoopsUri(self, obj): - #return str(loopsPrefix + getPath(obj)[len(getPath(self)):]) uri = loopsPrefix + getPath(obj)[len(getPath(self)):] - #if isinstance(uri, unicode): - # uri = uri.encode('UTF-8') return uri def loopsTraverse(self, uri): diff --git a/loops/browser/util.py b/loops/browser/util.py index cf015e2..a0ed39a 100644 --- a/loops/browser/util.py +++ b/loops/browser/util.py @@ -1,29 +1,12 @@ -# -# Copyright (c) 2011 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.util -""" -Utilities. +""" Utilities. """ import re, urllib -from zope.app.pagetemplate import ViewPageTemplateFile -from zope.app.publisher.browser.menu import BrowserMenu -from zope.app.publisher.interfaces.browser import IBrowserSubMenuItem +from zope.browserpage import ViewPageTemplateFile +from zope.browsermenu.menu import BrowserMenu +from zope.browsermenu.interfaces import IBrowserSubMenuItem from zope import component from zope.formlib.namedtemplate import NamedTemplateImplementation diff --git a/loops/common.py b/loops/common.py index 5fa2255..a1dbbe0 100644 --- a/loops/common.py +++ b/loops/common.py @@ -1,23 +1,6 @@ -# -# Copyright (c) 2013 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.common -""" -Common stuff. +""" Common stuff. """ from zope import component @@ -27,7 +10,7 @@ from zope.component import adapts from zope.dublincore.interfaces import IZopeDublinCore from zope.dublincore.annotatableadapter import ZDCAnnotatableAdapter from zope.dublincore.zopedublincore import ScalarProperty -from zope.interface import implements +from zope.interface import implementer from zope.interface.interface import InterfaceClass from zope.security.proxy import isinstance from zope.traversing.api import getName @@ -71,7 +54,7 @@ def baseObject(obj): def collectAttributeNames(lst, name): attrs = [] for arg in lst: - if isinstance(arg, basestring): + if isinstance(arg, str): attrs.append(arg) elif isinstance(arg, type): attrs.extend(list(getattr(arg, name))) @@ -182,9 +165,9 @@ class AdapterBase(object): return self.title +@implementer(IStorageInfo) class ResourceAdapterBase(AdapterBase): - implements(IStorageInfo) adapts(IResource) _adapterAttributes = adapterAttributes('storageName', 'storageParams', AdapterBase) @@ -200,9 +183,9 @@ class ResourceAdapterBase(AdapterBase): # other adapters +@implementer(IZopeDublinCore) class LoopsDCAdapter(ZDCAnnotatableAdapter): - implements(IZopeDublinCore) adapts(ILoopsObject) languageInfo = None @@ -541,11 +524,11 @@ class ParentRelation(object): # records/tracks +@implementer(ITracks) class Tracks(object): """ A tracking storage adapter managing tracks/records. """ - implements(ITracks) adapts(ITrackingStorage) def __init__(self, context): diff --git a/loops/concept.py b/loops/concept.py index 456fd4f..bf7ac28 100644 --- a/loops/concept.py +++ b/loops/concept.py @@ -1,35 +1,18 @@ -# -# Copyright (c) 2016 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.concept -""" -Definition of the Concept and related classes. +""" Definition of the Concept and related classes. """ from zope import component, schema -from zope.app.container.btree import BTreeContainer -from zope.app.container.contained import Contained -from zope.app.container.interfaces import IAdding -from zope.app.security.interfaces import IAuthentication, PrincipalLookupError +from zope.authentication.interfaces import IAuthentication, PrincipalLookupError +from zope.browser.interfaces import IAdding from zope.cachedescriptors.property import Lazy from zope.component import adapts +from zope.container.btree import BTreeContainer +from zope.container.contained import Contained from zope.dublincore.interfaces import IZopeDublinCore from zope.event import notify -from zope.interface import implements +from zope.interface import implementer from zope.interface import alsoProvides, directlyProvides, directlyProvidedBy from zope.interface.interfaces import ObjectEvent from zope.publisher.interfaces.browser import IBrowserRequest @@ -93,28 +76,25 @@ class BaseRelation(DyadicRelation): # So we patched zope.location.location, line 109... +@implementer(IConceptRelation) class ConceptRelation(BaseRelation): """ A relation between concept objects. """ - implements(IConceptRelation) - fallback = 'c*' +@implementer(IConceptRelation) class ResourceRelation(BaseRelation): """ A relation between a concept and a resource object. """ - implements(IConceptRelation) - fallback = 'r*' # concept +@implementer(IConcept, IConceptManagerContained, IRelatable) class Concept(Contained, Persistent): - implements(IConcept, IConceptManagerContained, IRelatable) - proxyInterface = IConceptView workspaceInformation = None @@ -362,10 +342,9 @@ class Concept(Contained, Persistent): # concept manager +@implementer(IConceptManager, ILoopsContained) class ConceptManager(BTreeContainer): - implements(IConceptManager, ILoopsContained) - typeConcept = None typePredicate = None defaultPredicate = None @@ -402,10 +381,9 @@ class ConceptManager(BTreeContainer): # adapters and similar components +@implementer(schema.interfaces.IIterableSource) class ConceptTypeSourceList(object): - implements(schema.interfaces.IIterableSource) - def __init__(self, context): if IBrowserRequest.providedBy(context): context = context.context @@ -428,10 +406,9 @@ class ConceptTypeSourceList(object): return len(self.conceptTypes) +@implementer(schema.interfaces.IIterableSource) class PredicateSourceList(object): - implements(schema.interfaces.IIterableSource) - def __init__(self, context): self.context = context self.concepts = self.context.getLoopsRoot().getConceptManager() @@ -458,9 +435,9 @@ class PredicateSourceList(object): return len(self.predicates) +@implementer(IIndexAttributes) class IndexAttributes(object): - implements(IIndexAttributes) adapts(IConcept) def __init__(self, context): @@ -537,19 +514,17 @@ class IndexAttributes(object): # events +@implementer(IAssignmentEvent) class AssignmentEvent(ObjectEvent): - implements(IAssignmentEvent) - def __init__(self, obj, relation): super(AssignmentEvent, self).__init__(obj) self.relation = relation +@implementer(IDeassignmentEvent) class DeassignmentEvent(ObjectEvent): - implements(IDeassignmentEvent) - def __init__(self, obj, relation): super(DeassignmentEvent, self).__init__(obj) self.relation = relation diff --git a/loops/i18n/common.py b/loops/i18n/common.py index a435aec..f4f4b87 100644 --- a/loops/i18n/common.py +++ b/loops/i18n/common.py @@ -1,30 +1,10 @@ -# -# Copyright (c) 2007 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.i18n.common -""" -Common stuff. - -$Id$ +""" Common i18n (internationalization) stuff. """ from zope import component from zope.component import adapts -from zope.interface import implements from zope.cachedescriptors.property import Lazy from zope.security.proxy import removeSecurityProxy from persistent.mapping import PersistentMapping diff --git a/loops/interfaces.py b/loops/interfaces.py index 71b1222..4715c09 100644 --- a/loops/interfaces.py +++ b/loops/interfaces.py @@ -1,23 +1,6 @@ -# -# Copyright (c) 2013 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.interfaces -""" -loops interface definitions. +""" loops interface definitions. """ from zope.interface import Interface, Attribute @@ -287,15 +270,15 @@ class IBaseResource(ILoopsObject): data = schema.Bytes( title=_(u'Data'), description=_(u'Resource raw data'), - default='', - missing_value='', + default=b'', + missing_value=b'', required=False) contentType = schema.BytesLine( title=_(u'Content Type'), description=_(u'Content type (format) of the data field'), - default='', - missing_value='', + default=b'', + missing_value=b'', required=False) metaInfo = Attribute('Optional additional information about the resource ' @@ -361,15 +344,15 @@ class IResourceSchema(Interface): data = schema.Bytes( title=_(u'Data'), description=_(u'Resource raw data'), - default='', - missing_value='', + default=b'', + missing_value=b'', required=False) contentType = schema.BytesLine( title=_(u'Content Type'), description=_(u'Content type (format) of the data field'), - default='', - missing_value='', + default=b'', + missing_value=b'', required=False) metaInfo = Attribute('Optional additional information about the resource ' @@ -720,14 +703,14 @@ class ITypeConcept(IConceptSchema, ILoopsAdapter, IOptions): title=_(u'Concept Manager Name'), description=_(u'Name of the concept manager in which objects of this ' u'type should be created.'), - default='', #determined at runtime: 'concepts' or 'resources' + default=b'', #determined at runtime: 'concepts' or 'resources' required=False) namePrefix = schema.BytesLine( title=_(u'Name Prefix'), description=_(u'String that will be prepended to the (generated) name ' u'of a newly created object of this type.'), - default='', + default=b'', required=False) viewName = schema.TextLine( @@ -821,8 +804,8 @@ class IFile(IResourceAdapter, IResourceSchema): data = schema.Bytes( title=_(u'Data'), description=_(u'Resource raw data'), - default='', - missing_value='', + default=b'', + missing_value=b'', required=False) localFilename = Attribute('Filename provided during upload.') @@ -854,16 +837,16 @@ class IStorageInfo(Interface): title=_(u'Storage Name'), description=_(u'The name of a storage utility used for this ' 'object.'), - default='', - missing_value='', + default=b'', + missing_value=b'', required=False) storageParams = schema.BytesLine( title=_(u'Storage Parameters'), description=_(u'Information used to address the external ' 'storage, e.g. a filename or path.'), - default='', - missing_value='', + default=b'', + missing_value=b'', required=False) externalAddress = ExternalAddressField( @@ -883,8 +866,8 @@ class IExternalFile(IFile): data = schema.Bytes( title=_(u'Data'), description=_(u'Resource raw data'), - default='', - missing_value='', + default=b'', + missing_value=b'', required=False) externalAddress = ExternalAddressField( diff --git a/loops/main.py b/loops/main.py index cf600bd..9ed0d0a 100644 --- a/loops/main.py +++ b/loops/main.py @@ -1,26 +1,6 @@ -#! /usr/bin/env python -# -# Copyright (c) 2008 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.main -""" -Entry point for the loops application. - -$Id$ +""" Entry point for the loops application. """ import os, sys @@ -68,7 +48,7 @@ def setInstanceHomeInZopeConf(): zc.close() command, key, value = [w.strip() for w in l1.split()] if command == '%define' and key == 'INSTANCE' and value != instanceHome: - print 'INSTANCE variable changed from %s to %s.' % (value, instanceHome) + print('INSTANCE variable changed from %s to %s.' % (value, instanceHome)) l1 = ' '.join((command, key, instanceHome)) + '\n' zc = open(configFile, 'w') zc.write(l1) @@ -86,7 +66,7 @@ def startZope(configFile): main(["-C", configFile]) except IOError, e: if str(e) == '[Errno 11] Resource temporarily unavailable': - print 'WARNING: Background process already running.' + print('WARNING: Background process already running.') #from startup import openBrowser #openBrowser(None) diff --git a/loops/security/common.py b/loops/security/common.py index 4c8cd1c..1ff6c88 100644 --- a/loops/security/common.py +++ b/loops/security/common.py @@ -1,32 +1,14 @@ -# -# 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.security.common -""" -Common functions and other stuff for working with permissions and roles. +""" Common functions and other stuff for working with permissions and roles. """ from persistent import Persistent from zope import component from zope.annotation.interfaces import IAttributeAnnotatable -from zope.app.container.interfaces import IObjectAddedEvent -from zope.app.security.settings import Allow, Deny, Unset from zope.cachedescriptors.property import Lazy -from zope.interface import implements +from zope.interface import implementer +from zope.lifecycleevent import IObjectAddedEvent from zope.lifecycleevent import IObjectCreatedEvent, IObjectModifiedEvent from zope.location.interfaces import IRoot, ILocation from zope.security import canAccess, canWrite @@ -34,6 +16,7 @@ from zope.security import checkPermission as baseCheckPermission from zope.security.management import getInteraction from zope.securitypolicy.interfaces import IPrincipalRoleManager from zope.securitypolicy.interfaces import IRolePermissionManager +from zope.securitypolicy.settings import Allow, Deny, Unset from zope.traversing.api import getName, getParents from zope.traversing.interfaces import IPhysicallyLocatable @@ -228,13 +211,12 @@ def revokeAcquiredSecurity(obj, event): # workspace handling +@implementer(IPhysicallyLocatable, IWorkspaceInformation) class WorkspaceInformation(Persistent): """ For storing security-related stuff pertaining to children and resources of the context (=parent) object. """ - implements(IPhysicallyLocatable, IWorkspaceInformation) - __name__ = u'workspace_information' #propagateRolePermissions = 'object' # or 'none' @@ -259,9 +241,9 @@ class WorkspaceInformation(Persistent): return [p] + getParents(p) +@implementer(ILocation) class LocationWSI(object): - implements(ILocation) component.adapts(WorkspaceInformation) def __init__(self, context): diff --git a/loops/tests/config.py b/loops/tests/config.py new file mode 100644 index 0000000..b910662 --- /dev/null +++ b/loops/tests/config.py @@ -0,0 +1,20 @@ +# py-scopes/demo/config.py + +from dotenv import load_dotenv +from os import getenv +from scopes.server.app import zope_app_factory + +load_dotenv() + +server_port = getenv('SERVER_PORT', '8099') + +app_factory = zope_app_factory + +# storage settings +from scopes.storage.db.postgres import StorageFactory +dbengine = 'postgresql+psycopg' +dbname = getenv('DBNAME', 'demo') +dbuser = getenv('DBUSER', 'demo') +dbpassword = getenv('DBPASSWORD', 'secret') +dbschema = getenv('DBSCHEMA', 'demo') + diff --git a/loops/util.py b/loops/util.py index 0b48693..449a485 100644 --- a/loops/util.py +++ b/loops/util.py @@ -9,7 +9,7 @@ from zope.publisher.browser import BrowserView from zope import component from zope.catalog.interfaces import ICatalog from zope.interface import Attribute, Interface -from zope.interface import directlyProvides, directlyProvidedBy, implements +from zope.interface import directlyProvides, directlyProvidedBy, implementer from zope.intid.interfaces import IIntIds from zope.i18nmessageid import MessageFactory from zope.publisher.interfaces.browser import IBrowserRequest @@ -53,9 +53,9 @@ MarkdownSourceFactory = SourceFactory( IMarkdownSource, _("Markdown(md))"), _("Markdown(md) Source")) +@implementer(IHTMLRenderer) class MarkdownToHTMLRenderer(BrowserView): - implements(IHTMLRenderer) component.adapts(IMarkdownSource, IBrowserRequest) def render(self, settings_overrides={}): @@ -94,7 +94,7 @@ def nl2br(text): return '
\n'.join(text.split('\r')) def toUnicode(value, encoding='UTF-8'): - if type(value) is not unicode: + if type(value) is not str: try: return value.decode(encoding) except UnicodeDecodeError: diff --git a/loops/view.py b/loops/view.py index d1c1cee..7e79df9 100644 --- a/loops/view.py +++ b/loops/view.py @@ -1,25 +1,6 @@ -# -# Copyright (c) 2008 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.view -""" -Definition of the View and related classses. - -$Id$ +""" Definition of the View and related classses. """ from zope import component @@ -28,7 +9,7 @@ from zope.app.container.contained import Contained from zope.app.container.ordered import OrderedContainer from zope.cachedescriptors.property import Lazy, readproperty from zope.component import adapts -from zope.interface import implements +from zope.interface import implementer from zope.interface import alsoProvides, directlyProvides, directlyProvidedBy from zope.intid.interfaces import IIntIds from zope.publisher.browser import applySkin @@ -55,10 +36,9 @@ from loops import util from loops.util import _ +@implementer(IView, INodeContained, IRelatable) class View(object): - implements(IView, INodeContained, IRelatable) - def __init__(self, title=u'', description=u''): self.title = title self.description = description @@ -86,8 +66,9 @@ class View(object): if len(rels) == 0: return None if len(rels) > 1: + targets = [getName(r.second) for r in rels] raise ValueError('There may be only one target for a View object: %s - %s' - % (getName(self), `[getName(r.second) for r in rels]`)) + % (getName(self), targets)) return list(rels)[0].second def setTarget(self, target): @@ -114,10 +95,9 @@ class View(object): return Jeep() +@implementer(INode) class Node(View, OrderedContainer): - implements(INode) - _nodeType = 'info' def getNodeType(self): return self._nodeType def setNodeType(self, nodeType): self._nodeType = nodeType @@ -180,10 +160,9 @@ class Node(View, OrderedContainer): return self.nodeType in ('page', 'menu') +@implementer(IViewManager, ILoopsContained) class ViewManager(OrderedContainer): - implements(IViewManager, ILoopsContained) - def getLoopsRoot(self): return getParent(self) @@ -194,21 +173,22 @@ class ViewManager(OrderedContainer): return Jeep() +@implementer(ITargetRelation) class TargetRelation(DyadicRelation): """ A relation between a view and its target. """ - implements(ITargetRelation) + pass # adapters +@implementer(INodeAdapter) class NodeAdapter(AdapterBase): """ Allows nodes to be adapted like concepts and resources, e.g. for i18n (needs derivation from I18NAdapterBase), specific capabilities or dynamic attributes. """ - implements(INodeAdapter) adapts(INode) _contextAttributes = ('title', 'description', 'body',) @@ -222,10 +202,9 @@ nodeTypes = [ ('raw', _(u'Raw')), # render body as is, viewName may contain content type ] +@implementer(schema.interfaces.IIterableSource) class NodeTypeSourceList(object): - implements(schema.interfaces.IIterableSource) - def __init__(self, context): self.context = context diff --git a/pyproject.toml b/pyproject.toml index 3464f46..80c7562 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,14 @@ authors = [{name = "Helmut Merz", email = "helmutm@cy55.de"}] dependencies = [ "cybertools", + "py-scopes", + "markdown", + "python-dotenv", + "zope.app.renderer", + "zope.browsermenu", + "zope.securitypolicy", + "zope.site", + "zope.thread", ] [project.optional-dependencies]