Compare commits

..

2 commits

Author SHA1 Message Date
eeab12f6d9 backport catalog import 2016-05-05 16:06:30 +02:00
89b9b33b60 backport shell utilities and repair routines 2016-05-05 16:01:34 +02:00
562 changed files with 5687 additions and 6067 deletions

14
.gitignore vendored
View file

@ -1,14 +1,4 @@
*.pyc
*.pyo
*.swp
dist/
var/
*.egg-info
*.project
*.pydevproject
*.ropeproject
*.sublime-project
*.sublime-workspace
.env
.project
.pydevproject
.settings
adminuser.zcml

View file

@ -6,13 +6,8 @@ $Id$
1.1
---
<<<<<<< HEAD
- provide controlling of rendering macro via field instance method getRenderer()
- add getRelations() method to child relation set
=======
- use targetView entry in request annotations for calling views declared
for adapted concept map objects (i.e. type interfaces)
>>>>>>> master
- Lobo layout: provide new part: image grid; make sure image is not repeated if
it already appears in header part
- new special view 'listsubobjects' for nodes

21
LICENSE
View file

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (C) 2023 cyberconcepts.org team
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

222
LICENSE.GPL Normal file
View file

@ -0,0 +1,222 @@
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS

View file

@ -1,11 +0,0 @@
global-include *.cfg
global-include *.css *.js
global-include *.gif *.jpg *.png
global-include *.dmp
global-include *.md *.txt
global-include *.mo *.po *.pot
global-include *.pdf
global-include *.pt
global-include *.zcml
graft loops/integrator/testdata

View file

@ -1,7 +0,0 @@
# Introduction
This is the main part of the code of the semantic
web application platform *loops*, based on
Zope 3 / bluebream.
More information: see https://www.cyberconcepts.org.

View file

@ -23,7 +23,7 @@ with lower-level aspects like type or state management.
Let's also import some common stuff needed later.
>>> from loops.common import adapted, baseObject
>>> from loops.common import adapted
>>> from loops.setup import addAndConfigureObject
@ -48,14 +48,14 @@ top-level loops container and a concept manager:
>>> cc1 = Concept()
>>> concepts['cc1'] = cc1
>>> cc1.title
''
u''
>>> loopsRoot.getLoopsUri(cc1)
'.loops/concepts/cc1'
>>> cc2 = Concept('Zope 3')
>>> cc2 = Concept(u'Zope 3')
>>> concepts['cc2'] = cc2
>>> cc2.title
'Zope 3'
u'Zope 3'
Now we want to relate the second concept to the first one.
@ -73,11 +73,11 @@ ConceptRelation):
We can now ask our concepts for their related child and parent concepts:
>>> [getName(c) for c in cc1.getChildren()]
['cc2']
[u'cc2']
>>> len(cc1.getParents())
0
>>> [getName(p) for p in cc2.getParents()]
['cc1']
[u'cc1']
>>> len(cc2.getChildren())
0
@ -90,24 +90,24 @@ a special predicate 'hasType'.
>>> typeObject = concepts['type']
>>> typeObject.setConceptType(typeObject)
>>> typeObject.getConceptType().title
'Type'
u'Type'
>>> concepts['unknown'] = Concept('Unknown Type')
>>> concepts['unknown'] = Concept(u'Unknown Type')
>>> unknown = concepts['unknown']
>>> unknown.setConceptType(typeObject)
>>> unknown.getConceptType().title
'Type'
u'Type'
>>> cc1.setConceptType(unknown)
>>> cc1.getConceptType().title
'Unknown Type'
u'Unknown Type'
>>> concepts['topic'] = Concept('Topic')
>>> concepts['topic'] = Concept(u'Topic')
>>> topic = concepts['topic']
>>> topic.setConceptType(typeObject)
>>> cc1.setConceptType(topic)
>>> cc1.getConceptType().title
'Topic'
u'Topic'
We get a list of types using the ConceptTypeSourceList.
In order for the type machinery to work we first have to provide a
@ -124,7 +124,7 @@ type manager.
>>> from loops.concept import ConceptTypeSourceList
>>> types = ConceptTypeSourceList(cc1)
>>> sorted(t.title for t in types)
['Customer', 'Domain', 'Predicate', 'Topic', 'Type', 'Unknown Type']
[u'Customer', u'Domain', u'Predicate', u'Topic', u'Type', u'Unknown Type']
Using a PredicateSourceList we can retrieve a list of the available
predicates.
@ -136,7 +136,7 @@ Note that the 'hasType' predicate is suppressed from this list as the
corresponding relation is only assigned via the conceptType attribute:
>>> sorted(t.title for t in predicates)
['subobject']
[u'subobject']
Concept Views
-------------
@ -146,7 +146,7 @@ Concept Views
>>> children = list(view.children())
>>> [c.title for c in children]
['Zope 3']
[u'Zope 3']
The token attribute provided with the items returned by the children() and
parents() methods identifies identifies not only the item itself but
@ -159,14 +159,14 @@ of URIs to item and the predicate of the relationship:
There is also a concept configuration view that allows updating the
underlying context object:
>>> cc3 = Concept('loops for Zope 3')
>>> cc3 = Concept(u'loops for Zope 3')
>>> concepts['cc3'] = cc3
>>> view = ConceptConfigureView(cc1,
... TestRequest(action='assign', tokens=['.loops/concepts/cc3']))
>>> view.update()
True
>>> sorted(c.title for c in cc1.getChildren())
['Zope 3', 'loops for Zope 3']
[u'Zope 3', u'loops for Zope 3']
>>> input = {'action': 'remove', 'qualifier': 'children',
... 'form.button.submit': 'Remove Chiildren',
@ -175,18 +175,18 @@ underlying context object:
>>> view.update()
True
>>> sorted(c.title for c in cc1.getChildren())
['loops for Zope 3']
[u'loops for Zope 3']
We can also create a new concept and assign it.
>>> params = {'action': 'create', 'create.name': 'cc4',
... 'create.title': 'New concept',
... 'create.title': u'New concept',
... 'create.type': '.loops/concepts/topic'}
>>> view = ConceptConfigureView(cc1, TestRequest(**params))
>>> view.update()
True
>>> sorted(c.title for c in cc1.getChildren())
['New concept', 'loops for Zope 3']
[u'New concept', u'loops for Zope 3']
The concept configuration view provides methods for displaying concept
types and predicates.
@ -198,15 +198,15 @@ types and predicates.
>>> component.provideAdapter(LoopsTerms, (IIterableSource, IBrowserRequest), ITerms)
>>> sorted((t.title, t.token) for t in view.conceptTypes())
[('Customer', '.loops/concepts/customer'),
('Domain', '.loops/concepts/domain'),
('Predicate', '.loops/concepts/predicate'),
('Topic', '.loops/concepts/topic'),
('Type', '.loops/concepts/type'),
('Unknown Type', '.loops/concepts/unknown')]
[(u'Customer', '.loops/concepts/customer'),
(u'Domain', '.loops/concepts/domain'),
(u'Predicate', '.loops/concepts/predicate'),
(u'Topic', '.loops/concepts/topic'),
(u'Type', '.loops/concepts/type'),
(u'Unknown Type', '.loops/concepts/unknown')]
>>> sorted((t.title, t.token) for t in view.predicates())
[('subobject', '.loops/concepts/standard')]
[(u'subobject', '.loops/concepts/standard')]
Index attributes adapter
------------------------
@ -214,10 +214,10 @@ Index attributes adapter
>>> from loops.concept import IndexAttributes
>>> idx = IndexAttributes(cc2)
>>> idx.text()
'cc2 Zope 3'
u'cc2 Zope 3'
>>> idx.title()
'cc2 Zope 3'
u'cc2 Zope 3'
Resources and what they have to do with Concepts
@ -233,27 +233,27 @@ A common type of resource is a document:
>>> from loops.interfaces import IDocument
>>> from loops.resource import Document
>>> doc1 = Document('Zope Info')
>>> doc1 = Document(u'Zope Info')
>>> resources['doc1'] = doc1
>>> doc1.title
'Zope Info'
u'Zope Info'
>>> doc1.data
''
u''
>>> doc1.contentType
''
u''
We can also directly use Resource objects; these behave like files.
In fact, by using resource types we can explicitly assign a resource
the 'file' type, but we will use this feature later:
>>> img = Resource('A png Image')
>>> img = Resource(u'A png Image')
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'), 'rb').read()
>>> img.data = open(os.path.join(path, 'test_icon.png')).read()
>>> img.getSize()
381
>>> img.getImageSize()
@ -261,8 +261,8 @@ For testing we use some simple files from the tests directory:
>>> img.contentType
'image/png'
>>> pdf = Resource('A pdf File')
>>> pdf.data = open(os.path.join(path, 'test.pdf'), 'rb').read()
>>> pdf = Resource(u'A pdf File')
>>> pdf.data = open(os.path.join(path, 'test.pdf')).read()
>>> pdf.getSize()
25862
>>> pdf.getImageSize()
@ -287,7 +287,7 @@ from concepts to resources:
... 'tokens': ['.loops/resources/doc1:.loops/concepts/standard']}
>>> view = ConceptConfigureView(cc1, TestRequest(form=form))
>>> [getName(r.context) for r in view.resources()]
['doc1']
[u'doc1']
>>> view.update()
True
>>> len(cc1.getResources())
@ -316,10 +316,10 @@ Index attributes adapter
>>> component.provideAdapter(FileAdapter, provides=IFile)
>>> idx = IndexAttributes(doc1)
>>> idx.text()
''
u''
>>> idx.title()
'doc1 Zope Info'
u'doc1 Zope Info'
Views/Nodes: Menus, Menu Items, Listings, Pages, etc
@ -343,14 +343,14 @@ The view manager has already been created during setup.
The view space is typically built up with nodes; a node may be a top-level
menu that may contain other nodes as menu or content items:
>>> m1 = views['m1'] = Node('Menu')
>>> m11 = m1['m11'] = Node('Zope')
>>> m111 = m11['m111'] = Node('Zope in General')
>>> m112 = m11['m112'] = Node('Zope 3')
>>> m1 = views['m1'] = Node(u'Menu')
>>> m11 = m1['m11'] = Node(u'Zope')
>>> m111 = m11['m111'] = Node(u'Zope in General')
>>> m112 = m11['m112'] = Node(u'Zope 3')
>>> m112.title
'Zope 3'
u'Zope 3'
>>> m112.description
''
u''
There are a few convienence methods for accessing parent and child nodes:
@ -359,7 +359,7 @@ There are a few convienence methods for accessing parent and child nodes:
>>> m11.getParentNode() is m1
True
>>> [getName(child) for child in m11.getChildNodes()]
['m111', 'm112']
[u'm111', u'm112']
What is returned by these may be controlled by the nodeType attribute:
@ -444,13 +444,13 @@ Node Views
>>> page = view.page
>>> items = page.textItems
>>> for item in items:
... print(item.url, item.editable)
... print item.url, item.editable
http://127.0.0.1/loops/views/m1/m11/m112 False
>>> menu = view.menu
>>> items = menu.menuItems
>>> for item in items:
... print(item.url, view.selected(item))
... print item.url, view.selected(item)
http://127.0.0.1/loops/views/m1/m11 True
A NodeView provides an itemNum attribute that may be used to count elements
@ -493,11 +493,11 @@ view; these views we have to provide as multi-adapters:
>>> len(tt)
9
>>> sorted((t.token, t.title) for t in view.targetTypes())[1]
('.loops/concepts/domain', 'Domain')
('.loops/concepts/domain', u'Domain')
>>> view.update()
True
>>> sorted(resources.keys())
['d001.txt', 'd002.txt', 'd003.txt', 'doc1', 'm1.m11.m111']
[u'd001.txt', u'd002.txt', u'd003.txt', u'doc1', u'm1.m11.m111']
>>> view.target.title, view.target.token
('New Resource', '.loops/resources/m1.m11.m111')
@ -537,28 +537,28 @@ view for rendering.)
>>> component.provideAdapter(LoopsType)
>>> view = NodeView(m112, TestRequest())
>>> view.renderTarget()
'<pre></pre>'
>>> doc1.data = 'Test data\n\nAnother paragraph'
u'<pre></pre>'
>>> doc1.data = u'Test data\n\nAnother paragraph'
>>> view.renderTarget()
'<pre>Test data\n\nAnother paragraph</pre>'
u'<pre>Test data\n\nAnother paragraph</pre>'
>>> doc1.contentType = 'text/restructured'
>>> doc1.data = 'Test data\n\nAnother `paragraph <para>`_'
>>> doc1.data = u'Test data\n\nAnother `paragraph <para>`_'
>>> from loops.wiki.base import wikiLinksActive
>>> wikiLinksActive(loopsRoot)
False
>>> view.renderTarget()
'<p>Test data</p>\n<p>Another <a class="reference external" href="para">paragraph</a></p>\n'
u'<p>Test data</p>\n<p>Another <a class="reference" href="para">paragraph</a></p>\n'
'<p>Test data</p>\n<p>Another <a class="reference create"
u'<p>Test data</p>\n<p>Another <a class="reference create"
href="http://127.0.0.1/loops/wiki/create.html?linkid=0000001">?paragraph</a></p>\n'
>>> #links = loopsRoot.getRecordManager()['links']
>>> #links['0000001']
<Link ['42', 1, '', '... ...', 'para', None]: {}>
<Link ['42', 1, '', '... ...', u'para', None]: {}>
If the target object is removed from its container all references
to it are removed as well. (To make this work we have to handle
@ -661,9 +661,9 @@ Breadcrumbs
>>> view = NodeView(m114, request)
>>> request.annotations.setdefault('loops.view', {})['nodeView'] = view
>>> view.breadcrumbs()
[{'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'}]
[{'url': 'http://127.0.0.1/loops/views/m1', 'label': u'Menu'},
{'url': 'http://127.0.0.1/loops/views/m1/m11', 'label': u'Zope'},
{'url': 'http://127.0.0.1/loops/views/m1/m11/m114', 'label': u''}]
End-user Forms and Special Views
@ -705,8 +705,8 @@ been created during setup.
>>> custType = TypeConcept(customer)
>>> custType.options
[]
>>> cust1 = concepts['cust1'] = Concept('Zope Corporation')
>>> cust2 = concepts['cust2'] = Concept('cyberconcepts')
>>> cust1 = concepts['cust1'] = Concept(u'Zope Corporation')
>>> cust2 = concepts['cust2'] = Concept(u'cyberconcepts')
>>> for c in (cust1, cust2): c.conceptType = customer
>>> custType.options = ('qualifier:assign',)
>>> ConceptType(cust1).qualifiers
@ -714,7 +714,7 @@ been created during setup.
>>> form = CreateObjectForm(m112, TestRequest())
>>> form.presetTypesForAssignment
[{'title': 'Customer', 'token': 'loops:concept:customer'}]
[{'token': 'loops:concept:customer', 'title': u'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
@ -736,18 +736,18 @@ on data provided in this form:
>>> note_tc = concepts['note']
>>> component.provideAdapter(NameChooser)
>>> request = TestRequest(form={'title': 'Test Note',
... 'form.type': '.loops/concepts/note',
... 'contentType': 'text/restructured',
... 'linkUrl': 'http://'})
>>> request = TestRequest(form={'title': u'Test Note',
... 'form.type': u'.loops/concepts/note',
... 'contentType': u'text/restructured',
... 'linkUrl': u'http://'})
>>> view = NodeView(m112, request)
>>> cont = CreateObject(view, request)
>>> cont.update()
False
>>> sorted(resources.keys())
[...'test_note'...]
[...u'test_note'...]
>>> resources['test_note'].title
'Test Note'
u'Test Note'
If there is a concept selected in the combo box we assign this to the newly
created object:
@ -755,8 +755,8 @@ created object:
>>> from loops import util
>>> topicUid = util.getUidForObject(topic)
>>> predicateUid = util.getUidForObject(concepts.getDefaultPredicate())
>>> request = TestRequest(form={'title': 'Test Note',
... 'form.type': '.loops/concepts/note',
>>> request = TestRequest(form={'title': u'Test Note',
... 'form.type': u'.loops/concepts/note',
... 'form.assignments.selected':
... [':'.join((topicUid, predicateUid))]})
>>> view = NodeView(m112, request)
@ -764,22 +764,22 @@ created object:
>>> cont.update()
False
>>> sorted(resources.keys())
[...'test_note-2'...]
[...u'test_note-2'...]
>>> note = resources['test_note-2']
>>> sorted(t.__name__ for t in note.getConcepts())
['note', 'topic']
[u'note', u'topic']
When creating an object its name may be automatically generated using the title
of the object. Let's make sure that the name chooser also handles special
and possibly critcal cases:
>>> nc = NameChooser(resources)
>>> nc.chooseName('', Resource('abc: (cde)'))
'abc__cde'
>>> nc.chooseName('', Resource('\xdcml\xe4ut'))
'uemlaeut'
>>> nc.chooseName('', Resource('A very very loooooong title'))
'a_title'
>>> nc.chooseName(u'', Resource(u'abc: (cde)'))
u'abc__cde'
>>> nc.chooseName(u'', Resource(u'\xdcml\xe4ut'))
u'uemlaeut'
>>> nc.chooseName(u'', Resource(u'A very very loooooong title'))
u'a_title'
Editing an Object
-----------------
@ -796,7 +796,7 @@ that in turns calls formlibs ``setUpWidgets()``.
The new technique uses the ``fields`` and ``data`` attributes...
>>> for f in view.fields:
... print(f.name, f.fieldType, f.required, f.vocabulary)
... print f.name, f.fieldType, f.required, f.vocabulary
title textline True None
data textarea False None
contentType dropdown True <...SimpleVocabulary object...>
@ -804,22 +804,22 @@ The new technique uses the ``fields`` and ``data`` attributes...
linkText textline False None
>>> view.data
{'title': 'Test Note', 'data': '', 'contentType': 'text/restructured',
'linkUrl': 'http://', 'linkText': ''}
{'linkUrl': u'http://', 'contentType': u'text/restructured', 'data': u'',
'linkText': u'', 'title': u'Test Note'}
The object is changed via a FormController adapter created for
a NodeView.
>>> form = dict(
... title='Test Note - changed',
... contentType='text/plain',)
... title=u'Test Note - changed',
... contentType=u'text/plain',)
>>> request = TestRequest(form=form)
>>> view = NodeView(m112, request)
>>> cont = EditObject(view, request)
>>> cont.update()
False
>>> resources['test_note'].title
'Test Note - changed'
u'Test Note - changed'
Virtual Targets
---------------
@ -883,13 +883,13 @@ informations about all parents of an object.
>>> parents = m113.getAllParents()
>>> for p in parents:
... print(p.object.title)
... print p.object.title
Zope
Menu
>>> parents = resources['test_note'].getAllParents()
>>> for p in parents:
... print(p.object.title, len(p.relations))
... print p.object.title, len(p.relations)
Note 1
Type 2
@ -916,30 +916,10 @@ relates ISO country codes with the full name of the country.
[('at', ['Austria']), ('de', ['Germany'])]
>>> countries.dataAsRecords()
[{'key': 'at', 'value': 'Austria'}, {'key': 'de', 'value': 'Germany'}]
[{'value': 'Austria', 'key': 'at'}, {'value': 'Germany', 'key': 'de'}]
>>> countries.getRowsByValue('value', 'Germany')
[{'key': 'de', 'value': 'Germany'}]
The ``recordstable`` type is a variation of this datable type that contains
a simple list of records - without a key column. A record in this type is a
dictionary with the field name as key and the field value as value.
>>> from loops.table import IRecordsTable, RecordsTable
>>> component.provideAdapter(RecordsTable, provides=IRecordsTable)
>>> drType = addAndConfigureObject(concepts, Concept, 'recordstable',
... title='Records Table', conceptType=concepts['type'],
... typeInterface=IRecordsTable)
We just reuse the existing ``countries`` table and convert it to a records table.
>>> baseObject(countries).setType(drType)
>>> countries = adapted(concepts['countries'])
>>> countries.data
[{'key': 'at', 'value': 'Austria'}, {'key': 'de', 'value': 'Germany'}]
[{'value': 'Germany', 'key': 'de'}]
Caching
@ -951,7 +931,7 @@ To be done...
>>> obj = resources['test_note']
>>> cxObj = cached(obj)
>>> [p.object.title for p in cxObj.getAllParents()]
['Note', 'Type']
[u'Note', u'Type']
Security
@ -960,12 +940,6 @@ Security
>>> from loops.security.browser import admin, audit
Paster Shell Utilities - Repair Scripts
=======================================
>>> from loops.repair.base import removeRecords
Import/Export
=============

22
__init__.py Normal file
View file

@ -0,0 +1,22 @@
#
# 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
#
"""
$Id$
"""

View file

@ -1,13 +1,33 @@
# loops.base
# -*- coding: UTF-8 -*-
# -*- Mode: Python; py-indent-offset: 4 -*-
#
# Copyright (c) 2006 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
#
""" Implementation of loops root object.
"""
from zope.container.btree import BTreeContainer
from zope.site.folder import Folder
from zope.site.interfaces import IFolder
$Id$
"""
from zope.app.container.btree import BTreeContainer
from zope.app.folder.folder import Folder
from zope.app.folder.interfaces import IFolder
from zope.traversing.api import getPath, traverse
from zope.interface import implementer
from zope.interface import implements
from cybertools.util.jeep import Jeep
from loops.interfaces import ILoops
@ -15,8 +35,17 @@ 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
@ -46,8 +75,7 @@ class Loops(Folder):
return self.get('records')
def getLoopsUri(self, obj):
uri = loopsPrefix + getPath(obj)[len(getPath(self)):]
return uri
return str(loopsPrefix + getPath(obj)[len(getPath(self)):])
def loopsTraverse(self, uri):
prefix = loopsPrefix + '/'

View file

@ -1,9 +1,26 @@
# loops.browser.action
#
# 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
#
""" Base classes (sort of views) for action portlet items.
"""
Base classes (sort of views) for action portlet items.
"""
from urllib.parse import urlencode
from urllib import urlencode
from zope import component
from zope.app.pagetemplate import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy
@ -108,15 +125,6 @@ actions.register('external_edit', 'object', TargetAction,
cssClass='icon-action',
)
actions.register('create_object', 'portlet', DialogAction,
title=_('Create Resource...'),
description=_('Create a new resource object.'),
viewName='create_object.html',
dialogName='edit',
prerequisites=['registerDojoEditor'],
permission='zope.ManageContent',
)
actions.register('edit_object', 'portlet', DialogAction,
title=_(u'Edit Resource...'),
description=_(u'Modify resource object.'),

61
browser/auth.py Normal file
View file

@ -0,0 +1,61 @@
#
# 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
#
"""
$Id$
"""
from zope.app.security.interfaces import IAuthentication
from zope.app.security.interfaces import ILogout, IUnauthenticatedPrincipal
from zope import component
from zope.interface import implements
from loops.browser.node import NodeView
from zope.app.pagetemplate import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy
class LoginForm(NodeView):
template = ViewPageTemplateFile('auth.pt')
@Lazy
def macro(self):
return self.template.macros['login_form']
@Lazy
def item(self):
return self
class Logout(object):
implements(ILogout)
def __init__(self, context, request):
self.context = context
self.request = request
def __call__(self):
nextUrl = self.request.get('nextURL') or self.request.URL[-1]
if not IUnauthenticatedPrincipal.providedBy(self.request.principal):
auth = component.getUtility(IAuthentication)
ILogout(auth).logout(self.request)
return self.request.response.redirect(nextUrl)

94
loops/browser/common.py → browser/common.py Executable file → Normal file
View file

@ -1,28 +1,45 @@
# loops.browser.common
#
# 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
#
""" Common base class for loops browser view classes.
"""
Common base class for loops browser view classes.
"""
from cgi import parse_qs, parse_qsl
#import mimetypes # use more specific assignments from cybertools.text
from datetime import date, datetime
from logging import getLogger
import re
from time import strptime
from urllib.parse import parse_qs, parse_qsl, urlencode
from urllib import urlencode
from zope import component
from zope.authentication.interfaces import IAuthentication, IUnauthenticatedPrincipal
from zope.authentication.interfaces import IUnauthenticatedPrincipal
from zope.authentication.interfaces import PrincipalLookupError
from zope.browser.interfaces import ITerms
from zope.browserpage import ViewPageTemplateFile
from zope.app.form.browser.interfaces import ITerms
from zope.app.i18n.interfaces import ITranslationDomain
from zope.app.security.interfaces import IAuthentication, IUnauthenticatedPrincipal
from zope.app.pagetemplate import ViewPageTemplateFile
from zope.app.security.interfaces import IUnauthenticatedPrincipal
from zope.app.security.interfaces import PrincipalLookupError
from zope.cachedescriptors.property import Lazy
from zope.dottedname.resolve import resolve
from zope.dublincore.interfaces import IZopeDublinCore
from zope.formlib import form
from zope.formlib.form import FormFields
from zope.formlib.namedtemplate import NamedTemplate
from zope.i18n.interfaces import ITranslationDomain
from zope.interface import Interface, implementer
from zope.interface import Interface, implements
from zope.proxy import removeAllProxies
from zope.publisher.browser import applySkin
from zope.publisher.interfaces.browser import IBrowserSkinType, IBrowserView
@ -81,20 +98,16 @@ class NameField(schema.ASCIILine):
class ViewMode(object):
def __init__(self, name='view', title=None, url=None, active=False,
description=u'', subViewModes=Jeep()):
description=u''):
self.name = name
self.title = title
self.url = url
self.active = active
self.description = description
self.subViewModes = subViewModes
@property
def cssClass(self):
result = self.active and u'active' or u'inactive'
if self.subViewModes:
result += u' sub-modes'
return result
return self.active and u'active' or u'inactive'
class IAddForm(Interface):
@ -179,11 +192,9 @@ class BaseView(GenericView, I18NView, SortableMixin):
actions = {}
portlet_actions = []
parts = ()
subparts = ()
icon = None
modeName = 'view'
isToplevel = False
isVisible = True
def __init__(self, context, request):
context = baseObject(context)
@ -192,10 +203,6 @@ class BaseView(GenericView, I18NView, SortableMixin):
self.context = removeSecurityProxy(context)
try:
if not self.checkPermissions():
logger = getLogger('loops.browser.common-153')
principal = request.principal and request.principal.id
msg = 'Unauthorized: %s, %s' % (self.contextInfo, principal)
logger.warn(msg)
raise Unauthorized(str(self.contextInfo))
except ForbiddenAttribute: # ignore when testing
pass
@ -208,11 +215,6 @@ class BaseView(GenericView, I18NView, SortableMixin):
def checkPermissions(self):
return canAccessObject(self.context)
def translate(self, text, msgFactory=_):
if msgFactory is None:
return text
return msgFactory(text)
@Lazy
def contextInfo(self):
return dict(view=self, context=getName(self.context))
@ -359,9 +361,6 @@ class BaseView(GenericView, I18NView, SortableMixin):
for c in cr:
try:
principal = pau.getPrincipal(c)
if principal is None:
creators.append(c)
else:
creators.append(principal.title)
except PrincipalLookupError:
creators.append(c)
@ -451,10 +450,6 @@ class BaseView(GenericView, I18NView, SortableMixin):
def description(self):
return self.adapted.description
@Lazy
def tabTitle(self):
return u'Info'
@Lazy
def additionalInfos(self):
return []
@ -679,11 +674,6 @@ class BaseView(GenericView, I18NView, SortableMixin):
def typeOptions(self):
if self.typeProvider is None:
return DummyOptions()
if getattr(self.adapted, '__is_dummy__', None):
typeToken = getattr(self, 'typeToken', None)
if typeToken is not None:
typeProvider = self.loopsRoot.loopsTraverse(typeToken)
return IOptions(adapted(typeProvider))
return IOptions(adapted(self.typeProvider))
@Lazy
@ -757,9 +747,6 @@ class BaseView(GenericView, I18NView, SortableMixin):
if not self.useVersioning:
return None
target = self.target
if not IResource.providedBy(target):
# no standard versioning yet for concepts
return None
versionable = IVersionable(target, None)
if versionable is None:
return ''
@ -874,8 +861,6 @@ class BaseView(GenericView, I18NView, SortableMixin):
def checkAction(self, name, category, target):
if name in ('create_resource',):
if target is not None and target.options.showCreateResource:
return True
return not self.globalOptions('hideCreateResource')
return True
@ -895,7 +880,6 @@ class BaseView(GenericView, I18NView, SortableMixin):
@Lazy
def xeditable(self):
return False
if self.typeOptions('no_external_edit'):
return False
ct = getattr(self.context, 'contentType', '')
@ -944,9 +928,9 @@ class BaseView(GenericView, I18NView, SortableMixin):
djConfig='parseOnLoad: true, usePlainJson: true, '
#'isDebug: true, '
'locale: "%s"' % self.languageInfo.language)
jsCall = ('dojo.require("dojo.parser"); ')
#'dojo.registerModulePath("jocy", "/@@/cybertools.jocy"); '
#'dojo.require("jocy.data");')
jsCall = ('dojo.require("dojo.parser"); '
'dojo.registerModulePath("jocy", "/@@/cybertools.jocy"); '
'dojo.require("jocy.data");')
cm.register('js-execute', 'dojo_registration', jsCall=jsCall)
cm.register('css', identifier='Lightbox.css', position=0,
resourceName='ajax.dojo/dojox/image/resources/Lightbox.css',
@ -1074,32 +1058,33 @@ class LoggedIn(object):
# vocabulary stuff
@implementer(ITerms)
class SimpleTerms(object):
""" Provide the ITerms interface, e.g. for usage in selection
lists.
"""
implements(ITerms)
def __init__(self, source, request):
# the source parameter is a list of tuples (token, title).
self.source = source
self.terms = dict(source)
def getTerm(self, value):
token = value[0]
title = len(value) > 1 and value[1] or token
token, title = value
return SimpleTerm(token, token, title)
def getValue(self, token):
return (token, self.terms[token])
@implementer(ITerms)
class LoopsTerms(object):
""" Provide the ITerms interface, e.g. for usage in selection
lists.
"""
implements(ITerms)
def __init__(self, source, request):
# the source parameter is a view or adapter of a real context object:
self.source = source
@ -1121,11 +1106,12 @@ class LoopsTerms(object):
return self.loopsRoot.loopsTraverse(token)
@implementer(ITerms)
class InterfaceTerms(object):
""" Provide the ITerms interface for source list of interfaces.
"""
implements(ITerms)
def __init__(self, source, request):
self.source = source
self.request = request

View file

@ -1,22 +1,40 @@
# loops.browser.concept
#
# 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
#
""" Definition of the concept view classes.
"""
Definition of the concept view classes.
"""
from itertools import groupby
from zope import interface, component, schema
from zope.authentication.interfaces import IUnauthenticatedPrincipal
from zope.browser.interfaces import ITerms
from zope.browserpage import ViewPageTemplateFile
from zope.app.catalog.interfaces import ICatalog
from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
from zope.app.container.contained import ObjectRemovedEvent
from zope.app.form.browser.interfaces import ITerms
from zope.app.form.interfaces import IDisplayWidget
from zope.app.pagetemplate import ViewPageTemplateFile
from zope.app.security.interfaces import IUnauthenticatedPrincipal
from zope.cachedescriptors.property import Lazy
from zope.catalog.interfaces import ICatalog
from zope.container.contained import ObjectRemovedEvent
from zope.dottedname.resolve import resolve
from zope.event import notify
from zope.formlib.form import EditForm, FormFields, setUpEditWidgets
from zope.formlib.interfaces import IDisplayWidget
from zope.formlib.namedtemplate import NamedTemplate
from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
from zope.interface import implements
from zope.publisher.interfaces import BadRequest
from zope.publisher.interfaces.browser import IBrowserRequest
from zope.schema.interfaces import IIterableSource
@ -236,35 +254,18 @@ class ConceptView(BaseView):
result.append(view)
return result
def viewModes(self):
modes = Jeep()
current = self.request.form.get('loops.viewName')
parts = (self.options('view_tabs') or
self.typeOptions('view_tabs') or [])
if not parts:
return modes
activeMode = None
for p in parts:
view = component.queryMultiAdapter(
(self.adapted, self.request), name=p)
if view is None:
view = component.queryMultiAdapter(
(self.context, self.request), name=p)
if view is None:
continue
active = (activeMode is None and p == current)
if active:
activeMode = p
url = '%s?loops.viewName=%s' % (self.targetUrl, p)
modes.append(ViewMode(p, view.tabTitle, url, active))
if activeMode is None:
modes[0].active = True
return modes
@Lazy
def adapted(self):
return adapted(self.context, self.languageInfo)
@Lazy
def title(self):
return self.adapted.title or getName(self.context)
@Lazy
def description(self):
return self.adapted.description
@Lazy
def targetUrl(self):
return self.nodeView.getUrlForTarget(self.context)
@ -290,7 +291,7 @@ class ConceptView(BaseView):
def breadcrumbsParent(self):
for p in self.context.getParents([self.defaultPredicate]):
view = self.nodeView.getViewForTarget(p)
if view is not None and view.showInBreadcrumbs:
if view.showInBreadcrumbs:
return view
return None
@ -397,8 +398,7 @@ class ConceptView(BaseView):
children = getChildren
def childrenAlphaGroups(self, predicates=None):
#result = Jeep()
result = {}
result = Jeep()
rels = self.getChildren(predicates=predicates or [self.defaultPredicate],
topLevelOnly=False, sort=False)
rels = sorted(rels, key=lambda r: r.title.lower())
@ -445,7 +445,7 @@ class ConceptView(BaseView):
def parents(self):
rels = sorted(self.context.getParentRelations(),
key=(lambda x: x.first.title and x.first.title.lower() or ''))
key=(lambda x: x.first.title and x.first.title.lower()))
for r in rels:
yield self.childViewFactory(r, self.request)

View file

@ -10,9 +10,7 @@
<metal:data define-macro="conceptdata">
<div tal:attributes="class string:content-$level;">
<tal:block condition="not:title_shown|python:False">
<metal:block use-macro="view/concept_macros/concepttitle" />
</tal:block>
<metal:slot define-slot="fields">
<metal:block use-macro="view/concept_macros/conceptfields" />
</metal:slot>
@ -53,7 +51,7 @@
<h1 tal:define="tabview item/tabview|nothing"
tal:attributes="ondblclick item/openEditWindow">
<a tal:omit-tag="python: level > 1"
tal:attributes="href string:${view/requestUrl}${item/urlParamString}"
tal:attributes="href string:${request/URL}${item/urlParamString}"
tal:content="item/title">Title</a>
<a title="Show tabular view"
i18n:attributes="title"
@ -89,10 +87,9 @@
fieldInstance python:field.getFieldInstance(
context=context, request=request);
rendererName field/displayRenderer;
renderer python:
fieldInstance.getRenderer(rendererName) or
renderer python:fieldInstance.getRenderer(rendererName) or
view.concept_macros[rendererName]"
tal:condition="python:field.visible and (value or field.showEmpty)">
tal:condition="nocall:value">
<td><b tal:content="field/title" i18n:translate="" />:</td>
<td><span metal:use-macro="renderer" /></td>
</tr>
@ -118,6 +115,15 @@
</metal:email>
<metal:email define-macro="display_relationset">
<span tal:repeat="elem value">
<a tal:attributes="href elem/url"
tal:content="elem/label"></a>
<tal:separator condition="not:repeat/elem/end"> | </tal:separator>
</span>
</metal:email>
<metal:parents define-macro="conceptparents">
<div tal:attributes="class string:content-$level;
ondblclick python: item.openEditWindow('configure.html')">
@ -185,17 +191,14 @@
tal:attributes="value related/uidToken" />
</td>
</tr>
<tal:nested condition="list_nested">
<tr tal:define="children python:
list(related.unique(related.children()));
<tr tal:define="children python:list(related.unique(related.children()));
resources python:list(related.resources())"
tal:condition="python:children or resources">
tal:condition="python:list_nested and (children or resources)">
<td tal:condition="item/showCheckboxes|nothing" />
<td colspan="5">
<metal:list use-macro="item/template/macros/list_nested" />
</td>
</tr>
</tal:nested>
</tal:item>
</tal:items>
</tbody>

View file

@ -34,18 +34,16 @@
<!-- login/logout -->
<!--<page for="loops.interfaces.INode"
<page for="loops.interfaces.INode"
name="login.html"
class="loops.browser.auth.LoginForm"
permission="zope.View" />-->
permission="zope.View" />
<page for="loops.interfaces.INode"
name="logout.html"
class="loops.browser.auth.Logout"
permission="zope.View" />
<!-- see also view/adapter "login.html" in section "query views" -->
<!-- macros -->
<page
@ -113,7 +111,7 @@
name="AddLoopsContainer.html"
for="zope.app.container.interfaces.IAdding"
class="loops.browser.manager.LoopsAddForm"
permission="zope.ManageSite"
permission="zope.ManageApplication"
/>
<addMenuItem
@ -125,7 +123,7 @@
<containerViews
for="loops.interfaces.ILoops"
index="zope.ManageSite"
index="zope.View"
contents="loops.ManageSite"
add="loops.ManageSite" />
@ -365,7 +363,7 @@
<containerViews
for="loops.interfaces.IViewManager"
index="zope.ManageSite"
index="zope.View"
add="loops.ManageSite" />
<menuItem
@ -539,14 +537,6 @@
<!-- query views -->
<!--<zope:adapter
name="login.html"
for="loops.interfaces.IConcept
zope.publisher.interfaces.browser.IBrowserRequest"
provides="zope.interface.Interface"
factory="loops.browser.auth.LoginConcept"
permission="zope.View" />-->
<zope:adapter
name="list_children.html"
for="loops.interfaces.IConcept
@ -681,13 +671,6 @@
factory="loops.browser.form.EditConcept"
permission="zope.View" />
<page
name="test.html"
for="loops.interfaces.INode"
class="loops.browser.node.TestView"
permission="zope.View" />
<!-- inner HTML views -->
<page

View file

@ -1,8 +1,28 @@
# loops.browser.external
#
# 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
#
""" view class(es) for import/export.
"""
view class(es) for import/export.
$Id$
"""
from zope.interface import Interface, implements
from zope.app import zapi
from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy

View file

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -1,18 +1,35 @@
# loops.browser.form
#
# Copyright (c) 2014 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
#
""" Classes for form presentation and processing.
"""
Classes for form presentation and processing.
"""
from urllib.parse import urlencode, unquote_plus
from urllib import urlencode
from zope.app.container.contained import ObjectRemovedEvent
from zope import component, interface, schema
from zope.component import adapts
from zope.container.contained import ObjectRemovedEvent
from zope.event import notify
from zope.interface import Interface
from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
from zope.container.interfaces import INameChooser
from zope.lifecycleevent import ObjectAddedEvent
from zope.browserpage import ViewPageTemplateFile
from zope.app.container.interfaces import INameChooser
from zope.app.container.contained import ObjectAddedEvent
from zope.app.pagetemplate import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy
from zope.contenttype import guess_content_type
from zope.publisher.browser import FileUpload
@ -162,14 +179,7 @@ class ObjectForm(NodeView):
for k, v in data.items():
#overwrite data with values from request.form
if k in self.request.form:
field = self.schema.fields.get(k)
if field:
fi = field.getFieldInstance(self.instance)
input = form[k]
if isinstance(input, str):
input = unquote_plus(input)
data[k] = fi.marshall(fi.unmarshall(input))
#data[k] = toUnicode(form[k])
data[k] = toUnicode(form[k])
return data
@Lazy
@ -305,7 +315,6 @@ class CreateObjectForm(ObjectForm):
defaultTitle = u'Create Resource, Type = '
form_action = 'create_resource'
dialog_name = 'create'
setupTarget = False
@property
def macro(self): return self.template.macros['create']
@ -349,7 +358,6 @@ class CreateObjectForm(ObjectForm):
def adapted(self):
ad = self.typeInterface(Resource())
ad.storageName = 'unknown' # hack for file objects: don't try to retrieve data
ad.__is_dummy__ = True
ad.__type__ = adapted(self.typeConcept)
return ad
@ -717,20 +725,11 @@ class CreateObject(EditObject):
factory = Resource
defaultTypeToken = '.loops/concepts/textdocument'
namePrefix = u''
@Lazy
def container(self):
return self.loopsRoot.getResourceManager()
@Lazy
def objectType(self):
tc = self.request.form.get('form.type') or self.defaultTypeToken
return self.loopsRoot.loopsTraverse(tc)
def getName(self):
return None
def getNameFromData(self):
data = self.request.form.get('data')
if data and isinstance(data, FileUpload):
@ -749,14 +748,12 @@ class CreateObject(EditObject):
if not title:
raise BadRequest('Title field is empty')
obj = self.factory(title)
name = self.getName()
if name is None:
name = self.getNameFromData()
nc = INameChooser(container)
nc.prefix = self.namePrefix
name = nc.chooseName(name, obj)
# TODO: validate fields
name = INameChooser(container).chooseName(name, obj)
container[name] = obj
obj.setType(self.objectType)
tc = form.get('form.type') or self.defaultTypeToken
obj.setType(self.loopsRoot.loopsTraverse(tc))
notify(ObjectCreatedEvent(obj))
#notify(ObjectAddedEvent(obj))
self.object = self.view.object = obj
@ -811,15 +808,8 @@ class CreateConcept(EditConcept, CreateObject):
@Lazy
def container(self):
cmName = adapted(self.objectType).conceptManager
if cmName:
return self.loopsRoot[cmName]
return self.loopsRoot.getConceptManager()
@Lazy
def namePrefix(self):
return adapted(self.objectType).namePrefix or u''
def getNameFromData(self):
return None

18
loops/browser/form_macros.pt → browser/form_macros.pt Executable file → Normal file
View file

@ -264,10 +264,9 @@
<label style="display: inline"
for="version.create"><span i18n:translate="">
New version</span></label>
<tal:level define="versionLevels python:list(view.versionLevels)"
condition="python: len(versionLevels) > 1">:
<tal:level condition="python: len(view.versionLevels) > 1">:
<select name="version.level">
<option tal:repeat="level versionLevels"
<option tal:repeat="level view/versionLevels"
i18n:translate=""
tal:attributes="value level/token"
tal:content="level/label" />
@ -282,18 +281,14 @@
tal:define="states view/states"
tal:condition="states">
<tr><td colspan="5" i18n:translate="" class="headline">States</td></tr>
<metal:states define-macro="states_info">
<tr tal:repeat="st states">
<tal:state define="stObj st/getStateObject;
stDef st/statesDefinition;
stTrans st/getAvailableTransitionsForUser">
<td colspan="2">
<b i18n:translate=""
tal:content="python:view.translate(stDef, st.msgFactory)">
loops.simple_publishing</b>:&nbsp;</td>
<td>
<span i18n:translate=""
tal:content="stObj/title">draft</span>&nbsp;</td>
<td colspan="2" i18n:translate=""
tal:content="stDef">loops.simple_publishing</td>
<td i18n:translate=""
tal:content="stObj/title">draft</td>
<td>
<select name="state.loops.simple_publishing"
tal:condition="stTrans"
@ -309,7 +304,6 @@
</tal:state>
</tr>
</metal:states>
</metal:states>
<div metal:define-macro="buttons" class="buttons">

View file

@ -2,7 +2,7 @@
<metal:info define-macro="object_info"
tal:define="item nocall:view/targetItem">
tal:define="item nocall:view/item">
<table class="object_info" width="400">
<tr>
<td colspan="2"><h2 i18n:translate="">Object Information</h2><br /></td>
@ -52,7 +52,7 @@
<metal:info define-macro="meta_info"
tal:define="item nocall:view/targetItem">
tal:define="item nocall:view/item">
<table class="object_info" width="400">
<tr>
<td colspan="2">

View file

@ -1,9 +1,26 @@
# loops.browser.lobo.standard
#
# 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
#
""" View classes for lobo (blueprint-based) layouts.
"""
View classes for lobo (blueprint-based) layouts.
"""
from urllib.parse import parse_qs
from cgi import parse_qs
from zope import interface, component
from zope.app.pagetemplate import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy

View file

@ -1,6 +1,7 @@
# loops.browser.lobo.tests
# $Id$
import unittest, doctest
from zope.testing.doctestunit import DocFileSuite
from zope.interface.verify import verifyClass
class Test(unittest.TestCase):
@ -13,8 +14,8 @@ class Test(unittest.TestCase):
def test_suite():
flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
return unittest.TestSuite((
unittest.TestLoader().loadTestsFromTestCase(Test),
doctest.DocFileSuite('README.txt', optionflags=flags),
unittest.makeSuite(Test),
DocFileSuite('README.txt', optionflags=flags),
))
if __name__ == '__main__':

26
loops/browser/loops.js → browser/loops.js Executable file → Normal file
View file

@ -42,7 +42,7 @@ function showIf(node, value, targetName) {
function showIfIn(node, conditions) {
dojo.forEach(conditions, function(cond) {
var display = (node.value == cond[0]) ? '' : 'none';
var display = (node.value == cond[0]) ? 'inline' : 'none';
dojo.byId(cond[1]).style.display = display;
})
}
@ -56,26 +56,6 @@ function setIfIn(node, conditions) {
})
}
function setIf(node, cond, acts) {
if (node.value == cond) {
dojo.forEach(acts, function(act) {
target = dijit.byId(act[0]);
target.setValue(act[1]);
})
}
}
function setIfN(node, conds, acts) {
dojo.forEach(conds, function(cond) {
if (node.value == cond) {
dojo.forEach(acts, function(act) {
target = dijit.byId(act[0]);
target.setValue(act[1]);
})
}
})
}
function destroyWidgets(node) {
dojo.forEach(dojo.query('[widgetId]', node), function(n) {
w = dijit.byNode(n);
@ -132,7 +112,7 @@ function submitReplacing(targetId, formId, url) {
mimetype: "text/html",
load: function(response, ioArgs) {
replaceNode(response, targetId);
return response;
return resonse;
}
})
}
@ -144,7 +124,7 @@ function xhrSubmitPopup(formId, url) {
mimetype: "text/html",
load: function(response, ioArgs) {
window.close();
return response;
return resonse;
}
});
}

View file

Before

Width:  |  Height:  |  Size: 942 B

After

Width:  |  Height:  |  Size: 942 B

View file

@ -1,5 +1,5 @@
#
# Copyright (c) 2011 Helmut Merz helmutm@cy55.de
# Copyright (c) 2006 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
@ -22,6 +22,7 @@ Definition of view classes for the top-level loops container.
$Id$
"""
from zope.app import zapi
from zope import component
from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
from zope.event import notify

View file

@ -1,11 +1,31 @@
# loops.browser.mobile.default
#
# 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
#
""" Default layouts for the loops mobile skin.
"""
Default layouts for the loops mobile skin.
$Id$
"""
from zope.app.pagetemplate import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy
from zope import component
from zope.interface import implements
from cybertools.browser.renderer import RendererFactory
from cybertools.composer.layout.base import Layout

168
loops/browser/node.py → browser/node.py Executable file → Normal file
View file

@ -1,28 +1,42 @@
# loops.browser.node
#
# 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
#
""" View class for Node objects.
"""
View class for Node objects.
"""
from logging import getLogger
from urllib.parse import urlencode, urlparse, urlunparse
#from urlparse import urlparse, urlunparse
from urlparse import urlparse, urlunparse
from zope import component, interface, schema
from zope.cachedescriptors.property import Lazy
from zope.annotation.interfaces import IAnnotations
from zope.app.catalog.interfaces import ICatalog
from zope.app.container.browser.contents import JustContents
from zope.app.container.browser.adding import Adding
from zope import component, interface, schema
from zope.annotation.interfaces import IAnnotations
from zope.authentication.interfaces import IUnauthenticatedPrincipal
from zope.browserpage import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy
from zope.catalog.interfaces import ICatalog
from zope.container.traversal import ItemTraverser
from zope.app.container.traversal import ItemTraverser
from zope.app.pagetemplate import ViewPageTemplateFile
from zope.app.security.interfaces import IUnauthenticatedPrincipal
from zope.dottedname.resolve import resolve
from zope.event import notify
from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
from zope.lifecycleevent import Attributes
from zope.formlib.form import Form, FormFields
from zope.proxy import removeAllProxies
from zope.publisher.defaultview import getDefaultViewName
from zope.publisher.interfaces import NotFound
from zope.app.publisher.browser import getDefaultViewName
from zope.security import canAccess, canWrite, checkPermission
from zope.security.proxy import removeSecurityProxy
from zope.traversing.api import getParent, getParents, getPath
@ -51,7 +65,6 @@ from loops import util
from loops.util import _
from loops.versioning.util import getVersion
logger = getLogger('loops.browser.node')
node_macros = ViewPageTemplateFile('node_macros.pt')
info_macros = ViewPageTemplateFile('info.pt')
@ -63,71 +76,26 @@ class NodeView(BaseView):
_itemNum = 0
template = node_macros
nextUrl = None
setupTarget = True
def __init__(self, context, request):
super(NodeView, self).__init__(context, request)
self.viewAnnotations.setdefault('nodeView', self)
self.viewAnnotations.setdefault('node', self.context)
self.setSkin(self.viewConfig.get('skinName'))
viewConfig = getViewConfiguration(context, request)
self.setSkin(viewConfig.get('skinName'))
def __call__(self, *args, **kw):
if self.nodeType == 'raw':
vn = self.context.viewName
if vn:
self.request.response.setHeader('content-type', vn)
return self.context.body
tv = self.viewAnnotations.get('targetView')
if tv is not None:
if tv.isToplevel:
return tv(*args, **kw)
if self.controller is not None:
self.controller.setMainPage()
return super(NodeView, self).__call__(*args, **kw)
@Lazy
def viewConfig(self):
return getViewConfiguration(self.context, self.request)
@Lazy
def viewConfigOptions(self):
result = {}
for opt in self.viewConfig.get('options') or []:
if ':' in opt:
k, v = opt.split(':', 1)
result[k] = v.split(',')
else:
result[opt] = True
return result
@Lazy
def copyright(self):
cr = self.viewConfigOptions.get('copyright')
if cr:
return cr[0]
cr = self.globalOptions('copyright')
return cr and cr[0] or 'cyberconcepts.org team'
@Lazy
def macro(self):
return self.template.macros['content']
@Lazy
def subparts(self):
def getParts(n):
t = n.targetObjectView
if t is None:
return []
return t.subparts
parts = getParts(self)
#return parts
for n in self.textItems:
parts.extend(getParts(n))
return parts
def update(self, topLevel=True):
if topLevel and self.view != self:
return self.view.update(False)
def update(self):
result = super(NodeView, self).update()
self.recordAccess()
return result
@ -381,10 +349,6 @@ class NodeView(BaseView):
def editable(self):
return canWrite(self.context, 'body')
def hasTopPage(self, name):
page = self.topMenu.context.get(name)
return page is not None
# menu stuff
@Lazy
@ -430,9 +394,8 @@ class NodeView(BaseView):
@Lazy
def menuItems(self):
items = [NodeView(child, self.request).view
return [NodeView(child, self.request)
for child in self.context.getMenuItems()]
return [item for item in items if item.isVisible]
@Lazy
def parents(self):
@ -460,15 +423,6 @@ class NodeView(BaseView):
def active(self, item):
return item.context == self.context or item.context in self.parents
@Lazy
def logoutUrl(self):
nextUrl = urlencode(dict(nextUrl=self.menu.url))
return '%s/logout.html?%s' % (self.menu.url, nextUrl)
@Lazy
def authenticationMethod(self):
return self.viewAnnotations.get('auth_method') or 'standard'
# virtual target support
@Lazy
@ -544,8 +498,7 @@ class NodeView(BaseView):
if tv is not None:
tv.setupController()
return tv
setup = self.setupTarget
return self.getViewForTarget(self.virtualTargetObject, setup=setup)
return self.getViewForTarget(self.virtualTargetObject)
@Lazy
def targetId(self):
@ -581,7 +534,7 @@ class NodeView(BaseView):
@Lazy
def typeProvider(self):
if self.virtualTargetObject is not None:
return IType(baseObject(self.virtualTargetObject)).typeProvider
return IType(self.virtualTargetObject).typeProvider
return None
# target viewing and editing support
@ -590,38 +543,24 @@ class NodeView(BaseView):
""" Return URL of given target view given as .XXX URL.
"""
if isinstance(target, BaseView):
miu = self.getMenuItemUrlForTarget(target.context)
if miu is not None:
return miu
return self.makeTargetUrl(self.url, target.uniqueId, target.title)
else:
target = baseObject(target)
return self.makeTargetUrl(self.url, util.getUidForObject(target),
target.title)
def getMenuItemUrlForTarget(self, tobj):
for node in tobj.getClients():
if node.nodeType == 'page' and node.getMenu() == self.menuObject:
return absoluteURL(node, self.request)
def getActions(self, category='object', page=None, target=None):
actions = []
#self.registerDojo()
self.registerDojoFormAll()
if target is None:
target = self.virtualTarget
#target = self.getViewForTarget(self.virtualTargetObject,
# setup=False)
if category in self.actions:
actions.extend(self.actions[category](self, target=target))
if target is not None and self.setupTarget:
actions.extend(target.getActions(
category, page=self, target=target))
if target is not None and target.context != self.virtualTargetObject:
# self view must be used directly for target
actions.extend(self.view.getAdditionalActions(
category, self, target))
if target is not None:
actions.extend(target.getActions(category, page=self, target=target))
if target != self.virtualTarget: # self view must be used directly for target
actions.extend(self.view.getAdditionalActions(category, self, target))
return actions
def getPortletActions(self, target=None):
@ -643,6 +582,7 @@ class NodeView(BaseView):
actions = dict(portlet=getPortletActions)
def checkAction(self, name, category, target):
#if name in ('create_resource',) and target is not None:
if target is not None:
return target.checkAction(name, category, target)
return super(NodeView, self).checkAction(name, category, target)
@ -769,11 +709,11 @@ class InlineEdit(NodeView):
if ti is not None:
target = ti(target)
data = self.request.form['editorContent']
if not isinstance(data, str):
if type(data) != unicode:
try:
data = data.decode('ISO-8859-15') # IE hack
except UnicodeDecodeError:
print('loops.browser.node.InlineEdit.save():', data)
print 'loops.browser.node.InlineEdit.save():', data
return
# data = data.decode('UTF-8')
target.data = data
@ -945,9 +885,9 @@ class NodeAdding(Adding):
return info
@interface.implementer(IViewConfiguratorSchema)
class ViewPropertiesConfigurator(object):
interface.implements(IViewConfiguratorSchema)
component.adapts(INode)
def __init__(self, context):
@ -1033,12 +973,7 @@ class NodeTraverser(ItemTraverser):
viewAnnotations['targetView'] = view
view.logInfo('NodeTraverser:targetView = %r' % view)
return self.context
try:
obj = super(NodeTraverser, self).publishTraverse(request, name)
except NotFound:
logger.warn('NodeTraverser: NotFound: URL = %s, name = %r' %
(request.URL, name))
raise
return obj
def getTargetUid(self, request):
@ -1086,9 +1021,7 @@ def setViewConfiguration(context, request):
config = IViewConfiguratorSchema(context)
skinName = config.skinName
if not skinName:
root = removeSecurityProxy(context.getLoopsRoot())
skinName = root.skinName
#skinName = context.getLoopsRoot().skinName
skinName = context.getLoopsRoot().skinName
if skinName:
viewAnnotations['skinName'] = skinName
if config.options:
@ -1101,18 +1034,3 @@ def getViewConfiguration(context, request):
viewAnnotations = request.annotations.get('loops.view', {})
return dict(skinName=viewAnnotations.get('skinName'),
options=viewAnnotations.get('options'))
class TestView(NodeView):
def __call__(self):
print( '*** begin')
for i in range(500):
#x = util.getObjectForUid('1994729849')
x = util.getObjectForUid('2018653366')
self.c = list(x.getChildren())
#self.c = list(x.getChildren([self.defaultPredicate]))
print('*** end', len(self.c))
return 'done'

View file

Before

Width:  |  Height:  |  Size: 942 B

After

Width:  |  Height:  |  Size: 942 B

View file

@ -276,17 +276,6 @@
title mode/description"
tal:content="mode/title"
i18n:translate="" />
<ul class="sub-view-modes"
tal:define="subModes mode/subViewModes|nothing"
tal:condition="subModes">
<li tal:repeat="mode subModes"
tal:attributes="class mode/cssClass">
<a tal:attributes="href mode/url;
title mode/description;"
tal:content="mode/title"
i18n:translate=""></a>
</li>
</ul>
</li>
</ul>
</metal:actions>
@ -298,8 +287,7 @@
tal:condition="python: 'print' in pageActions"
title="Print this page"
i18n:attributes="title">
<a href="#"
onclick="print(); return false;">
<a href="javascript:print()">
<img tal:attributes="src string:$resourceBase/cybertools.icons/printer.png"
i18n:attributes="alt"
alt="Print" />
@ -333,18 +321,18 @@
<metal:login define-macro="login">
<div>
<a href="login.html"
tal:attributes="href string:${view/topMenu/url}/login.html"
i18n:translate="">Log in</a></div>
<div tal:define="register python:view.globalOptions('provideLogin')"
tal:condition="python:register and register != True">
<a tal:define="reg python:register[0]"
tal:attributes="href string:${view/topMenu/url}/$reg"
tal:condition="register">
<a tal:condition="python:register != True"
tal:attributes="href python:register[0]"
i18n:translate="">Register new member</a></div>
</metal:login>
<metal:actions define-macro="personal">
<div><a tal:attributes="href view/logoutUrl"
<div><a href="logout.html?nextURL=login.html"
tal:attributes="href string:logout.html?nextURL=${view/menu/url}"
i18n:translate="">Log out</a></div>
<tal:actions repeat="action python:view.getAllowedActions('personal')">
<metal:action use-macro="action/macro" />

View file

@ -18,6 +18,10 @@
<a href="#"
tal:attributes="href string:${target/url}/@@configure.html"
tal:content="target/title">Document xy</a>
<tal:xedit define="xeditObjectUrl target/url"
condition="target/xeditable">
<metal:xedit use-macro="views/xedit_macros/editLink" />
</tal:xedit>
</tal:target>
</div>

View file

@ -52,7 +52,7 @@
tal:attributes="value item/token" />
</td>
<td>
<a tal:content="item/title|string:???"
<a tal:content="item/title"
tal:attributes="href string:${item/url}/@@SelectedManagementView.html">
Title
</a>

View file

@ -1,18 +1,36 @@
# loops.browser.resource
#
# 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
#
""" View class for resource objects.
"""
View class for resource objects.
"""
import os.path
from zope.authentication.interfaces import IUnauthenticatedPrincipal
from zope.browserpage import ViewPageTemplateFile
import urllib
from zope.cachedescriptors.property import Lazy
from zope import component
from zope.catalog.interfaces import ICatalog
from zope.container.interfaces import INameChooser
from zope.app.catalog.interfaces import ICatalog
from zope.app.container.interfaces import INameChooser
from zope.app.form.browser.textwidgets import FileWidget
from zope.app.pagetemplate import ViewPageTemplateFile
from zope.app.security.interfaces import IUnauthenticatedPrincipal
from zope.formlib.form import FormFields
from zope.formlib.interfaces import DISPLAY_UNWRITEABLE
from zope.formlib.textwidgets import FileWidget
from zope.proxy import removeAllProxies
from zope.schema.interfaces import IBytes
from zope.security import canAccess, canWrite
@ -171,7 +189,7 @@ class ResourceView(BaseView):
return DocumentView(context, self.request)
return self
def show(self, useAttachment=False, filename=None):
def show(self, useAttachment=False):
""" show means: "download"..."""
# TODO: control access, e.g. to protected images
# if self.adapted.isProtected():
@ -179,9 +197,6 @@ class ResourceView(BaseView):
context = self.context
ct = context.contentType
response = self.request.response
if self.typeOptions('x_robots_tag_header', None) is not None:
tagVal = ', '.join(self.typeOptions('x_robots_tag_header'))
response.setHeader('X-Robots-Tag', tagVal)
self.recordAccess('show', target=self.uniqueId)
if ct.startswith('image/'):
#response.setHeader('Cache-Control', 'public,max-age=86400')
@ -191,17 +206,12 @@ class ResourceView(BaseView):
from loops.media.browser.asset import MediaAssetView
view = MediaAssetView(context, self.request)
return view.show(useAttachment)
else:
response.setHeader('Cache-Control', '')
response.setHeader('Pragma', '')
ti = IType(context).typeInterface
if ti is not None:
context = ti(context)
data = context.data
if useAttachment:
if filename is None:
filename = (adapted(self.context).localFilename or
getName(self.context))
filename = adapted(self.context).localFilename or getName(self.context)
if self.typeOptions('use_title_for_download_filename'):
base, ext = os.path.splitext(filename)
filename = context.title
@ -254,21 +264,15 @@ class ResourceView(BaseView):
#wp = wiki.createPage(getName(self.context))
wp = wiki.addPage(LoopsWikiPage(self.context))
wp.text = text
#print(wp.wiki.getManager())
#print wp.wiki.getManager()
#return util.toUnicode(wp.render(self.request))
return super(ResourceView, self).renderText(text, contentType)
showMore = True
def renderShortText(self):
return self.renderDescription() or self.createShortText(self.render())
def createShortText(self, text=None):
text = (text or self.render()).strip()
shortText = extractFirstPart(text)
if shortText == text:
self.showMore = False
return shortText
return extractFirstPart(text or self.render())
def download(self):
""" Force download, e.g. of a PDF file """
@ -449,7 +453,7 @@ class ExternalEditorView(ExternalEditorView, BaseView):
r.append('meta_type:' + '.'.join((context.__module__, context.__class__.__name__)))
auth = self.request.get('_auth')
if auth:
print('ExternalEditorView: auth = ', auth)
print 'ExternalEditorView: auth = ', auth
if auth.endswith('\n'):
auth = auth[:-1]
r.append('auth:' + auth)
@ -473,3 +477,4 @@ class NoteView(DocumentView):
def linkUrl(self):
ad = self.typeAdapter
return ad and ad.linkUrl or ''

View file

@ -10,7 +10,7 @@
<div metal:use-macro="views/node_macros/object_actions" />
</tal:actions>
<h1><a tal:omit-tag="python: level > 1"
tal:attributes="href view/requestUrl"
tal:attributes="href request/URL"
tal:content="item/title">Title</a></h1>
<tal:desc define="description description|item/renderedDescription"
condition="description">
@ -51,7 +51,7 @@
<div tal:attributes="ondblclick python: item.openEditWindow('edit.html')">
<div metal:use-macro="views/node_macros/object_actions" />
<h1><a tal:omit-tag="python: level > 1"
tal:attributes="href view/requestUrl"
tal:attributes="href request/URL"
tal:content="item/title">Title</a></h1><br />
<img tal:attributes="src
string:${view/url}/.${view/targetId}/view?version=this" />
@ -72,7 +72,6 @@
<div>
<span class="button">
<a i18n:translate=""
target="_blank"
tal:attributes="href
string:${view/virtualTargetUrl}/download.html?version=this">
Download

View file

@ -1,4 +1,6 @@
# package loops.browser.skin
"""
$Id$
"""
from cybertools.browser.liquid import Liquid
from cybertools.browser.blue import Blue

View file

@ -18,11 +18,6 @@
permission="zope.View"
layer="loops.browser.skin.Loopy" />
<page for="*"
name="loopy.body_macros"
template="loopy/body.pt"
permission="zope.Public" />
<resource name="loops.css" file="loopy/loops.css"
layer="loops.browser.skin.Loopy" />
<resource name="custom.css" file="loopy/custom.css"

View file

@ -25,7 +25,6 @@
</div>
<div id="content" class="span-6"
tal:attributes="class content_class|string:span-6"
metal:define-macro="content">
<metal:breadcrumbs define-slot="breadcrumbs">
<metal:tabs use-macro="views/node_macros/breadcrumbs" />
@ -38,9 +37,7 @@
tal:condition="msg"
tal:content="msg" />
</metal:message>
<metal:tabs define-slot="view_modes">
<metal:tabs use-macro="views/node_macros/view_modes" />
</metal:tabs>
<metal:content define-slot="content">
<tal:content define="item nocall:view/item;
level level|python: 1;
@ -51,26 +48,21 @@
</metal:content>
</div>
<div id="portlets" class="span-2 last"
tal:attributes="class portlets_class|string:span-2 last">
<metal:portlet define-slot="portlet-left" >
<div id="portlets" class="span-2 last">
<tal:portlet repeat="macro controller/macros/portlet_left">
<metal:portlet use-macro="macro" />
</tal:portlet>
</metal:portlet>
<metal:portlet define-slot="portlet-right" >
<tal:portlet repeat="macro controller/macros/portlet_right">
<metal:portlet use-macro="macro" />
</tal:portlet>
</metal:portlet>
</div>
<div id="footer" class="footer clear"
metal:define-macro="footer">
<metal:footer define-slot="footer">
&copy; Copyright <span tal:replace="view/currentYear" />,
<span tal:replace="view/topMenu/copyright" />
(<a i18n:translate=""
cyberconcepts IT-Consulting Dr. Helmut Merz
(<a href="#"
tal:attributes="href string:${view/topMenu/url}/impressum">Impressum</a>)
<br />
Powered by

View file

@ -262,26 +262,10 @@ table.records th, table.records td {
border: 1px solid lightgrey;
}
table.report {
position: relative;
z-index: 99;
background: white;
}
table.report th {
border-bottom: 1px solid #bbbbbb;
font-weight: bold;
}
table.report td {
border-bottom: 1px dotted #dddddd;
vertical-align: top;
}
.report-meta table {
width: auto;
}
dl.docutils dt {
font-weight: bold;
margin-top: 0.3em;

View file

@ -11,10 +11,6 @@ body {
display: none;
}
.breadcrumbs {
display: none;
}
.container {
width: auto;
margin: 0;

View file

Before

Width:  |  Height:  |  Size: 580 B

After

Width:  |  Height:  |  Size: 580 B

View file

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -1,5 +1,4 @@
<tal:block i18n:domain="loops">
<div metal:define-macro="body">
<div metal:use-macro="views/cybertools.body_macros/body">
@ -34,6 +33,12 @@
<metal:footer fill-slot="footer">
<div xtal:condition="view/editable"
tal:condition="nothing">
<span i18n:translate="">For quick creation of notes/links bookmark this link</span>:
<a href="#"
tal:attributes="href view/popupCreateObjectForm">Create loops Note</a>
</div>
Powered by
<b><a href="http://loops.cy55.de">loops</a></b> &middot;
<b><a href="http://wiki.zope.org/zope3">Zope 3</a></b> &middot;
@ -42,6 +47,5 @@
</metal:footer>
</div>
</div>
</tal:block>

View file

@ -82,15 +82,6 @@ table {
margin-top: 0.5em;
}
/* elements taken from blueprint (Lobo) skin */
.span-1, .span-2, .span-3, .span-4, .span-5, .span-6 {float: left;}
.span-1 {width: 105px;}
.span-2 {width: 230px;}
.span-3 {width: 355px;}
.span-4 {width: 480px;}
.span-5 {width: 605px;}
.span-6 {width: 730px;}
tr.even td {
background-color: transparent;
}
@ -494,8 +485,8 @@ img.notselected {
/* view modes (tabs) */
ul.view-modes {
padding: 0 0 1px 0.5em;
margin: 1.5em 0 0 0;
padding: 0 0 0 2em;
margin: 0.7em 0 0 0;
white-space: nowrap;
list-style-type: none;
border-bottom: #ccc 1px solid;
@ -503,11 +494,11 @@ ul.view-modes {
}
ul.view-modes li {
display: inline;
display: inline
}
ul.view-modes li a {
padding: 3px 1.25em 3px 1.25em;
padding: 0.15em 1.25em 0.15em 1.25em;
margin: 0 0.5em 0 0;
text-decoration: none;
border: #ccc 1px solid;

View file

@ -10,7 +10,7 @@
method="post" name="listing" action="."
tal:define="target nocall:view/target"
tal:condition="python: target or items"
tal:attributes="action view/requestUrl">
tal:attributes="action request/URL">
<input type="hidden" name="action" value="assign"
tal:attributes="value action" />
<table class="listing" summary="Currently assigned"
@ -45,16 +45,17 @@
checked python: targetToken == token" />
</td>
<td>
<a tal:omit-tag="not:nocall:item"
<a href="#"
tal:omit-tag="not:nocall:item"
tal:content="title"
tal:define="baseUrl item/url|string:"
tal:attributes="href
string:$baseUrl/@@SelectedManagementView.html">
string:${item/url|nothing}/@@SelectedManagementView.html">
Title
</a>
</td>
<td>
<a tal:condition="type"
href="#"
tal:attributes="href
string:${item/typeUrl}/@@SelectedManagementView.html"
tal:omit-tag="not:item/typeUrl">
@ -82,7 +83,7 @@
<fieldset>
<legend i18n:translate="">Create Target</legend>
<form method="post" name="listing" action="."
tal:attributes="action view/requestUrl">
tal:attributes="action request/URL">
<input type="hidden" name="action" value="create" />
<div class="row">
<span i18n:translate="">Name</span>
@ -113,7 +114,7 @@
<metal:search define-macro="search">
<form method="post" name="listing" action="."
tal:attributes="action view/requestUrl">
tal:attributes="action request/URL">
<input type="hidden" name="action" value="search" />
<div class="row"
tal:define="searchTerm request/searchTerm | nothing;

View file

@ -1,13 +1,29 @@
# loops.browser.util
#
# 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
#
""" Utilities.
"""
Utilities.
"""
import re
import urllib.parse
from zope.browserpage import ViewPageTemplateFile
from zope.browsermenu.menu import BrowserMenu
from zope.browsermenu.interfaces import IBrowserSubMenuItem
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 import component
from zope.formlib.namedtemplate import NamedTemplateImplementation
@ -52,4 +68,4 @@ def html_quote(text, character_entities=((u'&', u'&amp;'), (u'<', u'&lt;' ),
pattern = re.compile(r'[ /\?\+\|%]')
def normalizeForUrl(text):
return urllib.parse.quote(pattern.sub('-', text).encode('UTF-8'))
return urllib.quote(pattern.sub('-', text).encode('UTF-8'))

View file

@ -4,6 +4,8 @@ loops - Linked Objects for Organization and Processing Services
Automatic classification of resources.
($Id$)
Setting up a loops Site and Utilities
=====================================
@ -37,7 +39,7 @@ from external files so we have something to work with.
>>> tExternalCollection = concepts['extcollection']
>>> coll01 = addObject(concepts, Concept, 'coll01',
... title='Collection One', conceptType=tExternalCollection)
... title=u'Collection One', conceptType=tExternalCollection)
>>> aColl01 = adapted(coll01)
>>> aColl01.baseAddress = dataDir
>>> aColl01.address = ''
@ -47,7 +49,7 @@ from external files so we have something to work with.
7
>>> rnames = list(sorted(resources.keys()))
>>> rnames[0]
'cust_im_contract_webbg_20071015.txt'
u'cust_im_contract_webbg_20071015.txt'
Filename-based Classification
@ -74,7 +76,7 @@ and follow the classifier step by step.
>>> from loops.classifier.interfaces import IExtractor, IAnalyzer
>>> infoSet = InformationSet()
>>> for name in classifier.extractors.split():
... print('extractor:', name)
... print 'extractor:', name
... extractor = component.getAdapter(adapted(r1), IExtractor, name=name)
... infoSet.update(extractor.extractInformationSet())
extractor: filename
@ -94,32 +96,32 @@ So there seems to be something missing - we have to create concepts
that may be identified as being candidates for classification.
>>> tInstitution = addObject(concepts, Concept, 'institution',
... title='Institution', conceptType=concepts['type'])
... title=u'Institution', conceptType=concepts['type'])
>>> cust_im = addObject(concepts, Concept, 'im_editors',
... title='im Editors', conceptType=tInstitution)
... title=u'im Editors', conceptType=tInstitution)
>>> cust_mc = addObject(concepts, Concept, 'mc_consulting',
... title='MC Management Consulting', conceptType=tInstitution)
... title=u'MC Management Consulting', conceptType=tInstitution)
>>> tDoctype = addObject(concepts, Concept, 'doctype',
... title='Document Type', conceptType=concepts['type'])
... title=u'Document Type', conceptType=concepts['type'])
>>> dt_note = addObject(concepts, Concept, 'dt_note',
... title='Note', conceptType=tDoctype)
... title=u'Note', conceptType=tDoctype)
>>> dt_contract = addObject(concepts, Concept, 'dt_contract',
... title='Contract', conceptType=tDoctype)
... title=u'Contract', conceptType=tDoctype)
>>> tPerson = concepts['person']
>>> webbg = addObject(concepts, Concept, 'webbg',
... title='Gerald Webb', conceptType=tPerson)
... title=u'Gerald Webb', conceptType=tPerson)
>>> smitha = addObject(concepts, Concept, 'smitha',
... title='Angelina Smith', conceptType=tPerson)
... title=u'Angelina Smith', conceptType=tPerson)
>>> watersj = addObject(concepts, Concept, 'watersj',
... title='Jerry Waters', conceptType=tPerson)
... title=u'Jerry Waters', conceptType=tPerson)
>>> millerj = addObject(concepts, Concept, 'millerj',
... title='Jeannie Miller', conceptType=tPerson)
... title=u'Jeannie Miller', conceptType=tPerson)
>>> t.indexAll(concepts, resources)
>>> from zope.catalog.interfaces import ICatalog
>>> from zope.app.catalog.interfaces import ICatalog
>>> cat = component.getUtility(ICatalog)
>>> statements = analyzer.extractStatements(infoSet)
@ -133,7 +135,7 @@ So we are now ready to have the whole stuff run in one call.
Classifier fileclassifier: Assigning: ...
>>> list(sorted([c.title for c in r1.getConcepts()]))
['Collection One', 'Contract', 'External File', 'Gerald Webb', 'im Editors']
[u'Collection One', u'Contract', u'External File', u'Gerald Webb', u'im Editors']
>>> for name in rnames[1:]:
... classifier.process(resources[name])

View file

@ -1,6 +1,23 @@
# looops.classifier.base
#
# 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
#
""" Adapters and others classes for analyzing resources.
"""
Adapters and others classes for analyzing resources.
"""
from itertools import tee
@ -9,7 +26,7 @@ from zope.cachedescriptors.property import Lazy
from zope import component
from zope.component import adapts
from zope.event import notify
from zope.interface import implementer
from zope.interface import implements
from zope.traversing.api import getName, getParent
from cybertools.typology.interfaces import IType
@ -27,11 +44,11 @@ logger = getLogger('Classifier')
TypeInterfaceSourceList.typeInterfaces += (IClassifier,)
@implementer(IClassifier)
class Classifier(AdapterBase):
""" A concept adapter for analyzing resources.
"""
implements(IClassifier)
adapts(IConcept)
_contextAttributes = list(IClassifier) + list(IConcept)
@ -95,9 +112,9 @@ class Classifier(AdapterBase):
logger.info(u'%s: %s' % (getName(self.context), message))
@implementer(IExtractor)
class Extractor(object):
implements(IExtractor)
adapts(IResource)
def __init__(self, context):
@ -107,9 +124,9 @@ class Extractor(object):
return InformationSet()
@implementer(IAnalyzer)
class Analyzer(object):
implements(IAnalyzer)
adapts(IClassifier)
def __init__(self, context):
@ -130,15 +147,15 @@ class Analyzer(object):
return r1
@implementer(IInformationSet)
class InformationSet(dict):
pass
implements(IInformationSet)
@implementer(IStatement)
class Statement(object):
implements(IStatement)
def __init__(self, object=None, predicate=None, subject=None, relevance=100):
self.subject = subject
self.predicate = predicate

View file

@ -1,11 +1,30 @@
# loops.classifier.sample
#
# 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
#
""" Sample classifier implementation.
"""
Sample classifier implementation.
$Id$
"""
from zope import component
from zope.app.catalog.interfaces import ICatalog
from zope.cachedescriptors.property import Lazy
from zope.catalog.interfaces import ICatalog
from zope.component import adapts
from zope.traversing.api import getName

Some files were not shown because too many files have changed in this diff Show more