Compare commits

..

192 commits

Author SHA1 Message Date
a72553c2de config.py: support multiple base_url values via server_id 2025-05-05 19:06:46 +02:00
1d264fc54f allow usage of OIDC authentication (via py-scopes) where appropriate, and provide corresponding views in loops/server/auth.zcml 2025-05-05 09:58:38 +02:00
80c83d5c9f setUserId, query principal: fall back to virtual principal possibly provided by oidc 2025-05-04 22:40:44 +02:00
77fedaaeaa auth: remove obsolete definistions 2025-04-15 18:32:12 +02:00
636b209e9a work in progress: login with zitadel (Open ID Connect) 2025-04-06 22:33:16 +02:00
520c89f4b2 fix I18NValue: PersistentMapping.values() returns a generator, not a list 2025-03-31 17:23:45 +02:00
b6c93302b9 fix settings for loops and bluebream example instances 2025-03-29 16:05:22 +01:00
0340992932 fix sort values (Py3) 2025-03-09 17:12:37 +01:00
ed5e560ba4 fix sort concept relations (Py3): use '' instead of None 2025-03-09 11:26:48 +01:00
aa0443d0b5 psu: commit transaction when closing connection 2025-03-09 09:35:38 +01:00
0de7ef2550 Python3 fix: avoid None when sorting strings 2025-02-23 10:29:25 +01:00
bbc277e81d fix email handling: remove encode() 2025-02-11 15:59:56 +01:00
c3efe7a6f9 one more monkey patch for handling bytes array in text index 2025-02-03 11:11:29 +01:00
8d2f185d17 fix loops.repair: psu moved to loops.server 2025-01-11 11:46:04 +01:00
ec88df3405 remove old psu module; + minor Py3 fixes 2025-01-11 11:43:30 +01:00
678ed53ab5 add patches for ZODB.broken and zope.intid (provide _uid_int as object property) 2025-01-07 08:45:17 +01:00
72d7fdd05f add zope.sendmail patch (bug fix) 2024-12-31 15:07:08 +01:00
eddb58c794 fix email generation: remove 'encode()' 2024-12-31 14:44:12 +01:00
8a578b46a8 some more Python3 fixes 2024-12-30 09:52:02 +01:00
78c8b196bf loops instance settings: fixes and improvements 2024-12-18 19:15:08 +01:00
2b5a5ec65a add SERVER_ID to instance config 2024-12-18 11:33:23 +01:00
b8ce799b12 minor fix in organize.comment set up 2024-11-24 10:13:04 +01:00
c0fc7fd464 fix auth utility set-up in psu 2024-11-24 09:17:10 +01:00
e01a7362f9 work in progress: authentication: provide config parameter 2024-11-22 11:33:24 +01:00
74a3f9210b server.auth: register authentication utility 2024-11-16 09:31:33 +01:00
95ed826629 register JWT authentication utility defined in py-scopes (work in progress) 2024-11-15 08:43:22 +01:00
22efffa11b add monkey patch for zope.index; constraint zope.publisher to version 5.2.1 2024-10-26 19:21:23 +02:00
0f2232648c psu improvments for db and conn usage; fix table.py 2024-10-25 15:08:08 +02:00
14ea59a307 more Python3 fixes: remove basestring, unicode 2024-10-23 10:32:58 +02:00
8d7cda5ec0 fix psu.reindex_all(): additional keyword arguments, esp start 2024-10-22 22:27:46 +02:00
cfa079de0d fixes for server start functionality and inst examples 2024-10-06 11:24:00 +02:00
dc87e32a63 move psu from loops.common to loops.server; provide server.main for easy starting as server 2024-10-06 09:36:40 +02:00
2a689a871b fix overrides.zcml: set skin; fix resource indexing: avoid error when principal not found 2024-10-04 14:45:40 +02:00
76e189ac45 fix common overrides.zcml 2024-10-02 17:02:47 +02:00
0e180bb229 example instances: put all common stuff in loops package 2024-10-02 16:57:36 +02:00
98f023195b avoid testing deprecation errors with Python3.12 2024-10-02 12:54:34 +02:00
5dcc7d2fc6 more Python3 fixes and minor changes 2024-10-01 16:12:30 +02:00
2cf3569fcb loops site setup OK 2024-09-29 12:05:53 +02:00
6169a2d728 plain bluebream server basically working 2024-09-29 09:48:37 +02:00
ffa5c8701e work in progress: loops / bluebream instances (app installations) 2024-09-29 08:47:05 +02:00
48a51c54b1 use zope.testrunner for running all tests => additional Python3 fixes 2024-09-28 10:30:30 +02:00
74ce78dae9 removed xmlrpc package 2024-09-28 09:12:13 +02:00
f2a737e0a8 storage: tests with Python3 OK 2024-09-28 09:03:56 +02:00
6ae2590f50 Python3 fixes: constraint, media, system 2024-09-28 08:14:49 +02:00
1da71c72a9 more Python3 fixes: compound, knowledge, versioning 2024-09-27 09:37:54 +02:00
010106406d more Python3 fixes 2024-09-27 08:40:20 +02:00
992b5c012d classifier: Python3 fixes 2024-09-26 22:30:52 +02:00
bf1fda008c integrator: Python3 fixes 2024-09-26 19:59:51 +02:00
5e5e9aedfc organize (tracking, work): Python3 fixes 2024-09-26 16:25:00 +02:00
956a6d01b3 organize: Python3 fixes for: comment, job, stateful 2024-09-25 23:37:00 +02:00
52ac41a82f organize: more Python3 fixes 2024-09-25 18:49:21 +02:00
04a9d9ced0 Work in progress: Python3 fixes for loops.organize 2024-09-25 17:05:57 +02:00
509000a996 Python3 fixes for expert tests (+dependencies) 2024-09-25 10:58:27 +02:00
ce7015b224 Python3 fixes: standard tests OK 2024-09-25 09:30:37 +02:00
53be77b5b9 work in progress: Python3 fixes 2024-09-24 19:23:54 +02:00
42d24a5c3f Python3 fixes: test setup OK, starting with fixes in README.txt 2024-09-24 17:38:15 +02:00
494612235e more Python3 fixes: imports for setting up TestSite 2024-09-24 16:33:00 +02:00
ca70050bec more Python3 fixes: basic imports OK, starting with doctests 2024-09-24 15:30:46 +02:00
7a9be5d79b more Python3 fixes (browser, resource) 2024-09-24 11:47:59 +02:00
98bd9b0a46 work in progress: Python3 fixes 2024-09-24 11:13:49 +02:00
7b3c7c182e fix pyproject.toml - start testing / fixing with Python3 2024-09-23 17:41:38 +02:00
77aaad1aa0 start working on Python3-compatible version 2024-09-23 17:30:48 +02:00
4041841c65 storage.compat: provide __name__ property for tracks 2024-05-03 13:47:07 +02:00
cb994dc238 minor fix on util.records(): fail immediately if tracks folder is missing 2024-03-20 09:36:04 +01:00
baedf02d78 make compatibility method saveUserTrack() compatible with customized head fields arrangements 2024-03-19 08:59:46 +01:00
6efe2b7a46 remove obsolete import 2024-03-17 09:25:22 +01:00
d5a8068261 fix and improve setup of storage factory and storage 2024-03-10 08:04:18 +01:00
73ac0af54e fix storage creation: util.makeStorage() 2024-03-09 21:26:06 +01:00
3c82ec9fdc fix storage creation according to new implementation of storage setup with StorageFactory 2024-03-09 21:13:25 +01:00
81fef0e1d5 provide util.getStorage() functionality 2024-02-26 12:49:29 +01:00
31886012ce fix handling of migrated UIDs, add test 2024-02-19 11:02:00 +01:00
086868d5b7 improve migration: use list of ids for access to source collection 2024-02-19 09:13:25 +01:00
f51f3d4f25 migration: improve uid mapping by using separate columns for prefix and id 2024-02-19 08:55:54 +01:00
536903f3d8 improve / fix tracking migration 2024-02-19 08:33:50 +01:00
75fdced678 tests: check for correct DB engine settings 2024-02-15 11:27:03 +01:00
a739aed66a use new py-scopes package instead of cco.storage 2024-02-15 10:45:58 +01:00
92bc00e134 ensure correct initialization of db engine for testing 2024-02-09 16:44:24 +01:00
fb9d6991cd getItem(): use uid_mapping for legacy UIDs when appropriate 2024-02-07 17:53:27 +01:00
bea7ed0254 migration: allow source list, source list slicing, commit after step records 2024-02-06 17:26:08 +01:00
bece8863ef use now module-global engine and session factory 2024-02-01 15:21:36 +01:00
16a8bcf5c3 provide table for UID mappings, fill during migration 2024-02-01 11:43:37 +01:00
2dbbf977d4 storage test: drop table; reset trackid sequence after migration 2024-01-25 09:16:51 +01:00
23623aeb3d provide compat.Storage subclass; minor immprovements 2024-01-19 08:48:22 +01:00
70a93f56d9 move storage-related stuff from organize/tracking/storage to loops.storage package 2024-01-17 11:01:34 +01:00
ec87bcd682 fixes according to new cco.storage version 2024-01-17 10:28:58 +01:00
3d3e221a5c storage test: set correct db password 2024-01-05 18:50:04 +01:00
4635a00caf fix portlet selection; simplify options retrieval 2024-01-03 17:33:21 +01:00
64ad40fb96 clean-up source files 2024-01-03 16:57:47 +01:00
b4b93b122e move access to records container (in ZODB or SQL DB) to util.records() function 2024-01-03 15:44:08 +01:00
5c4b7fd730 use new IUid interface for showing the presence of the uid property 2024-01-03 13:00:36 +01:00
380d7e7b59 use SQL-based storage (cco.storage) for favorites 2024-01-03 11:09:50 +01:00
1cd9908afe RecordsTable: allow sorting of rows via type option 'table.sort' 2023-12-29 13:56:51 +01:00
6994d923a2 use new RecordsTable type for configurable records table 2023-12-29 13:26:46 +01:00
997bffc286 add IRecordsTable to interfaces list 2023-12-27 10:52:08 +01:00
81aa9d4e1e add RecordsTable to configure.zcml 2023-12-27 10:50:31 +01:00
45d568b77b assign widget to IRecordsTable definition 2023-12-27 10:40:41 +01:00
4343dc517f add RecordsTable type implemented as a simple list of records 2023-12-26 22:09:12 +01:00
a2ccb8a35b remove/hide print statements for clean tests 2023-12-26 22:08:16 +01:00
8e81bab3bf access to SQL-based implementation of favorites record basically working 2023-12-20 15:39:10 +01:00
466044eb77 work in progress: tests for adding and querying favorites 2023-12-19 15:31:35 +01:00
a65c138949 favorites: tests: minor improvements 2023-12-18 20:02:07 +01:00
2c22df9c1d test for migration of favorites basically working 2023-12-18 15:10:06 +01:00
506e539c2d work in progress: autommatic tests for SQL-based storage implementation 2023-12-18 13:53:37 +01:00
781521ca88 move storage migration from cybertools to loops, + updates from cco.storage 2023-12-15 14:13:47 +01:00
6d39f9f354 recreate (obsolete) QualificationRecord stuff to avoid crashing of records listing on old ZODBs 2023-12-08 11:54:20 +01:00
e3359db4ca avoid security check when retrieving skin name from loops root 2022-10-09 22:29:21 +02:00
c9c537af23 fix: add missing import 2021-12-06 15:56:29 +01:00
Hannes Plattner
1cf6bd3c06 allow adapterindexattributes title getter 2021-11-12 10:42:35 +01:00
zope
d4edd8ee7a add parameter usePredicateIndex to getRelations() 2021-08-07 11:53:04 +02:00
Hannes Plattner
4b54cadcac mark new object form adapter as __dummy__ 2021-07-09 11:34:45 +02:00
8e94c5971e fix doctests for loops.external (resources contentType) 2021-06-11 10:23:19 +02:00
Hannes Plattner
aab2d6c955 check if principal exists before adding to group 2021-06-11 10:02:13 +02:00
Hannes Plattner
34c9d24ffa add target blank to resource download button 2021-05-27 11:57:28 +02:00
1504375a25 add CSS classes to book display elements 2021-03-14 09:22:39 +01:00
346d041be4 remove upper limit for data field 2021-01-06 11:09:02 +01:00
4b4a804cb7 change upper limit for data field 2021-01-06 10:40:19 +01:00
2ddf3e03e7 add (dummy) parameter to 'getColumnRenderer()' 2020-12-14 08:43:23 +01:00
fecac89a84 fix breadcrumb check: view may be None because of restricted permissions 2020-08-18 10:23:15 +02:00
4f710c15d6 fix favorite display 2020-05-06 08:23:28 +02:00
522e40f242 fix import: stop deprecation warning 2020-05-04 16:30:34 +02:00
61a146a43d suppress checking for options in security check 2020-05-01 08:17:41 +02:00
31b81d10e3 re-activate redirect on state change 2020-04-29 10:54:01 +02:00
347e19a2c6 unquote form value only if string 2020-04-29 10:43:31 +02:00
ebcbab1a6c fix DateField (missing return) 2020-04-25 12:00:40 +02:00
ebacd14134 re-implement missing multiline field method 2020-04-15 10:40:49 +02:00
d07f3b85a9 don't suppress additional excess values in table rows 2020-04-08 14:51:50 +02:00
9d9cfd6fc7 Merge branch '2master' of ssh://git.cy55.de/home/git/loops into 2master 2020-03-23 11:23:24 +01:00
dd23787d27 avoid error when list value is None 2020-03-23 11:20:45 +01:00
0cd2e1f47d fix doctests, avoid deprecation warnings 2020-03-07 13:31:29 +01:00
8723289204 set version number to 2.3.0 2020-03-07 12:51:06 +01:00
b471deef43 merge bbmaster2 into bbmaster resulting in branch 2master 2020-03-04 18:05:37 +01:00
c3079472de fix typo and doctest after JSON fix 2019-12-06 07:56:30 +01:00
hplattner
b59248ee31 Merge branch 'bbmaster' of ssh://git.cy55.de/home/git/loops into bbmaster 2019-12-05 20:48:10 +01:00
hplattner
1b0f0e4859 add concept search filtering select fix 2019-12-05 20:47:57 +01:00
082acf3fbc add loopsRoot to module global variables 2019-12-01 16:22:40 +01:00
9ab3e61f9a make loopsRoot path and config module configurable in setup() function 2019-12-01 16:05:21 +01:00
1854903d0f conditional commit for deletion of objects 2019-12-01 11:31:52 +01:00
b4a8ae1ed1 some more improvements 2019-12-01 10:12:00 +01:00
c5fe2044b5 add common psu utilities, esp generic loop 2019-12-01 09:58:24 +01:00
9c2517a518 take psu.py from bbmaster2 branch, with some improvements 2019-12-01 09:42:45 +01:00
dc5d913b99 user account: add principal title if given 2019-11-28 08:33:24 +01:00
814f10ae51 unify naming: concept_macros means macros, not template 2019-11-27 13:20:42 +01:00
dae9610e2e allow hiding title, e.g. if shown already in another macro 2019-11-27 13:19:17 +01:00
7567374e50 fix page templates: avoid errors with new ZPT version 2019-04-28 16:55:49 +02:00
2012181382 fix tests/doctests according to current ZTK and BlueBream versions 2019-04-26 17:13:59 +02:00
e1b1a82ee1 allow suppressing of ObjectModifiedEvent (if done later by caller) 2019-04-26 10:33:33 +02:00
233d146587 remove obslete ugly hack from loops container 2019-04-10 08:23:19 +02:00
aa263eb635 avoid error because of error in title field of view 2018-08-03 15:07:12 +02:00
hplattner
66b616e5e1 add subviewmode feature 2018-05-16 12:04:14 +02:00
7dcf4a9f4e unit tests: move creation of externalIdentifier index to general setup 2017-10-13 11:07:40 +02:00
181846b29a fix indexing stuff (externalIdentifier) 2017-10-08 12:56:31 +02:00
37d508cec2 remove obsolete 'jocy' registration 2017-08-20 15:55:33 +02:00
f8bca9e7d6 add utility functions for reindexing of objects 2017-08-10 10:37:25 +02:00
7a3cfbc175 avoid error when no groups folder is found (e.g. in doctests) 2017-08-02 08:53:12 +02:00
63220a656f always show all unread messages 2017-07-20 11:16:48 +02:00
hplattner
fb924fdc03 add mail image to notifications portlet 2017-07-20 10:48:56 +02:00
dc75d56951 process optional selection criteria (dateFrom) for notifications list 2017-07-19 15:41:49 +02:00
8f12304074 node traverser: add logging for NotFound exception 2017-04-10 11:47:17 +02:00
30c13d57d7 use special property 'favTitle' for favorites portlet 2016-12-19 14:45:09 +01:00
f9ba9d2115 make sure virtualTargetObject is not adapted 2016-10-12 08:55:21 +02:00
4339f25ca3 backport from bbmaster2: check 'visible' property in display views 2016-10-08 14:14:17 +02:00
ccc9886113 object creation: take type options from object to be created 2016-10-04 15:12:00 +02:00
hplattner
e27f4000f7 add uid to notifications listing items 2016-08-30 12:47:36 +02:00
hplattner
c64f067d6e Merge branch 'bbmaster' of ssh://git.cy55.de/home/git/loops into bbmaster 2016-08-11 17:01:28 +02:00
hplattner
fbacdd7465 add structure for target report column renderer 2016-08-11 17:01:20 +02:00
0b5c50c100 avoid loading of all subchildren in check for nested listing 2016-07-17 09:58:05 +02:00
4a18c42283 fix datatable source list; add generic IOptions adapter 2016-04-22 11:17:45 +02:00
0915d04e30 log 'Unauthorized' warning 2016-02-10 09:32:54 +01:00
24cd81e267 backport changes from bbmaster2 2016-02-04 15:45:33 +01:00
2e096a3682 backport changes from bbmaster2 2016-02-04 15:07:20 +01:00
f69a43699c avoid error for notifications whose target has been deleted 2016-01-15 11:59:18 +01:00
970775f847 make body template configurable: additional slots, variables controlling layout (CSS classes) 2015-12-06 12:43:34 +01:00
hplattner
1f4d246994 Merge branch 'bbmaster' of ssh://git.cy55.de/home/git/loops into bbmaster 2015-11-19 14:18:58 +01:00
hplattner
fbf4efc40c add assign/ deassignParent wrapper for resources; add adapterIndexAttributes + keywords index adapter lookup for resources 2015-11-19 14:18:48 +01:00
bd33c28d08 move login form (NodeView) to cco.member; + helper methods in NodeView 2015-11-13 16:25:34 +01:00
babb222868 starting to move authentication to cco.member package; allow special characters in logout's nextUrl 2015-11-12 15:45:29 +01:00
hplattner
9ec755ecd3 Merge branch 'bbmaster' of ssh://git.cy55.de/home/git/loops into bbmaster 2015-11-06 08:44:53 +01:00
a3b649582c add states definition for person 2015-11-04 14:59:35 +01:00
0b4fdef7b5 show link to notifications page also for 'no new notifications' 2015-11-04 09:27:50 +01:00
hplattner
16b5665bfa send report params form with get request 2015-11-03 17:59:12 +01:00
357c660659 notification: avoid error because of user without person 2015-11-03 17:26:34 +01:00
5c90a19859 notification: minor fixes 2015-10-31 10:56:09 +01:00
a2dca10e15 show portlet only if there are any notifications 2015-10-31 10:05:13 +01:00
ec28357ba7 notifications: portlet, translations 2015-10-30 12:52:42 +01:00
4a0b31b34d notifications listing fully operative with marking notifications as read and filtering 2015-10-30 10:50:05 +01:00
d09e2a9d0a work in progress: notifications listing 2015-10-25 15:42:36 +01:00
019eef29a6 work in progress: notification listing; improve 'unauthorized' view 2015-10-23 10:36:46 +02:00
c029cb2356 provide login.html as concept view (for use with a query), + a similar unauthorized view 2015-10-19 16:26:41 +02:00
e929a3154e avoid UnicodeEncodeError when object name contains special characters 2015-10-19 09:31:34 +02:00
57588f1e19 fix access to manage_workspace page 2015-10-19 09:30:23 +02:00
853782fb35 work in progress: notifications listing 2015-10-17 12:17:56 +02:00
9f9df6599f set up basic notification functionality 2015-10-16 09:26:49 +02:00
556 changed files with 4070 additions and 4873 deletions

6
.gitignore vendored
View file

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

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
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.

View file

@ -1,222 +0,0 @@
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,61 +0,0 @@
#
# 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)

19
config.py Normal file
View file

@ -0,0 +1,19 @@
# loops/config.py
# (used for testing only)
from dotenv import load_dotenv
from os import getenv
load_dotenv()
server_port = getenv('SERVER_PORT', '8099')
app_factory = zope_app_factory
# storage settings
dbengine = 'postgresql+psycopg'
dbname = getenv('DBNAME', 'demo')
dbuser = getenv('DBUSER', 'demo')
dbpassword = getenv('DBPASSWORD', 'secret')
dbschema = getenv('DBSCHEMA', 'demo')

View file

@ -1,44 +0,0 @@
#
# 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
#
"""
Filter query results.
$Id$
"""
from zope.interface import implements
from loops.expert.interfaces import IQueryInstance
class QueryInstance(object):
implements(IQueryInstance)
def __init__(self, query, *filters, **kw):
self.query = query
self.filters = filters
self.filterQueries = {}
for k, v in kw.items():
setattr(self, k, v)
def apply(self, uidsOnly=False):
result = self.query.apply()
return result

View file

@ -0,0 +1,14 @@
<configure xmlns="http://namespaces.zope.org/zope">
<principal
id="zope.manager"
title="Manager"
login="admin"
password="admin"
password_manager="Plain Text" />
<grant
role="zope.Manager"
principal="zope.manager" />
</configure>

View file

@ -0,0 +1,11 @@
<configure xmlns="http://namespaces.zope.org/zope">
<include package="loops" file="bluebream.zcml" />
<include package="loops" file="securitypolicy.zcml" />
<include file="adminuser.zcml" />
<includeOverrides file="overrides.zcml" />
</configure>

9
inst/bluebream/config.py Normal file
View file

@ -0,0 +1,9 @@
# loops/inst/bluebream/config.py
from dotenv import load_dotenv
from os import getenv
load_dotenv()
server_port = getenv('SERVER_PORT', '8099')

8
inst/bluebream/env.in Normal file
View file

@ -0,0 +1,8 @@
# loops/inst/bluebream/.env
SERVER_PORT=8800
DBNAME=ccotest
DBUSER=ccotest
DBPASSWORD=cco
DBSCHEMA=testing

15
inst/bluebream/main.py Normal file
View file

@ -0,0 +1,15 @@
# loops/inst/bluebream/main.py
import waitress
from zope.app.wsgi import config, getWSGIApplication
def run(app, config):
port = int(config.server_port)
#print(f'Serving on port {port}.')
waitress.serve(app, port=port)
if __name__ == '__main__':
import config
app = getWSGIApplication('zope.conf')
run(app, config)

View file

@ -0,0 +1,4 @@
<configure xmlns="http://namespaces.zope.org/zope">
</configure>

39
inst/bluebream/zope.conf Normal file
View file

@ -0,0 +1,39 @@
# loops/inst/bluebream/zope.conf
# main zope configuration file for deployment
# Identify the component configuration used to define the site:
site-definition application.zcml
<zodb>
<filestorage>
path var/filestorage/Data.fs
blob-dir var/blob
</filestorage>
# Uncomment this if you want to connect to a ZEO server instead:
# <zeoclient>
# server localhost:8100
# storage 1
# # ZEO client cache, in bytes
# cache-size 20MB
# # Uncomment to have a persistent disk cache
# #client zeo1
# </zeoclient>
</zodb>
<eventlog>
# This sets up logging to both a file and to standard output (STDOUT).
# The "path" setting can be a relative or absolute filesystem path or
# the tokens STDOUT or STDERR.
<logfile>
path var/log/bluebream.log
formatter zope.exceptions.log.Formatter
</logfile>
<logfile>
path STDOUT
formatter zope.exceptions.log.Formatter
</logfile>
</eventlog>

View file

@ -0,0 +1,14 @@
<configure xmlns="http://namespaces.zope.org/zope">
<principal
id="zope.manager"
title="Manager"
login="admin"
password="admin"
password_manager="Plain Text" />
<grant
role="zope.Manager"
principal="zope.manager" />
</configure>

View file

@ -0,0 +1,33 @@
<configure xmlns="http://namespaces.zope.org/zope">
<include package="loops" file="bluebream.zcml" />
<include package="loops" file="securitypolicy.zcml" />
<module module="loops.patch" />
<include file="adminuser.zcml" />
<include package="cybertools" />
<include package="cybertools.ajax.dojo" />
<include package="cybertools.catalog" />
<include package="cybertools.composer.layout" />
<include package="cybertools.container" />
<!--<include package="cybertools.pyscript" />-->
<!--<include package="cybertools.xedit" />-->
<include package="loops" />
<!--<include package="cco.schema" />
<include package="cco.skin.r2" />
<include package="cco.webapi" />
<include package="cyberapps.ccmkg" />
<include package="cyberapps.knowledge" />-->
<include package="loops.server" file="auth.zcml" />
<!-- Override registrations -->
<includeOverrides package="loops" file="overrides.zcml" />
<includeOverrides file="overrides.zcml" />
</configure>

41
inst/loops/config.py Normal file
View file

@ -0,0 +1,41 @@
# loops/inst/loops/config.py
from dotenv import load_dotenv
from os import getenv
load_dotenv()
server_id = getenv('SERVER_ID')
zope_conf = getenv('ZOPE_CONF', 'zope.conf')
server_port = getenv('SERVER_PORT',
server_id and getenv(f'SERVER_PORT_{server_id}')) or '8080'
base_url = getenv('BASE_URL',
server_id and getenv(f'BASE_URL_{server_id}')) or 'https://test.example.com'
shell_pw = (getenv('SHELL_PW', 'dummy'))
loops_path = (getenv('LOOPS_PATH', 'loops/demo'))
# storage settings
from scopes.storage.db.postgres import StorageFactory
dbengine = getenv('DBENGINE', 'postgresql+psycopg')
dbname = getenv('DBNAME', 'demo')
dbuser = getenv('DBUSER', 'demo')
dbpassword = getenv('DBPASSWORD', 'secret')
dbschema = getenv('DBSCHEMA', 'demo')
# OpenID Connect (OIDC, e.g. via zitadel) authentication settings
oidc_provider = getenv('OIDC_PROVIDER', '') #'https://instance1-abcdef.zitadel.cloud')
oidc_client_id = getenv('OIDC_CLIENT_ID', '12345')
oidc_params = dict(
op_config_url=oidc_provider + '/.well-known/openid-configuration',
op_uris=None,
op_keys=None,
callback_url=getenv('OIDC_CALLBACK_URL', base_url + '/auth_callback'),
client_id=oidc_client_id,
principal_prefix=getenv('OIDC_PRINCIPAL_PREFIX', 'loops.'),
cookie_name=getenv('OIDC_COOKIE_NAME', 'oidc_' + oidc_client_id),
cookie_domain=getenv('OIDC_COOKIE_DOMAIN', None),
cookie_lifetime=getenv('OIDC_COOKIE_LIFETIME', '86400'),
cookie_crypt=getenv('OIDC_COOKIE_CRYPT', None)
)

12
inst/loops/env.in Normal file
View file

@ -0,0 +1,12 @@
# loops/inst/loops/.env
SERVER_ID=0
SERVER_PORT_0=8800
SHELL_PW=dummy
LOOPS_PATH=demo
DBNAME=loops
DBUSER=demouser
DBPASSWORD=dummy
DBSCHEMA=demo

View file

@ -0,0 +1,4 @@
<configure xmlns="http://namespaces.zope.org/zope">
</configure>

7
inst/loops/runserver.sh Executable file
View file

@ -0,0 +1,7 @@
# inst/loops/runserver.sh
set -a
# use environment variables for instance-specific configuration:
#SERVER_ID=0
python -c "from loops.server.main import main; main()"

39
inst/loops/zope.conf Normal file
View file

@ -0,0 +1,39 @@
# loops/inst/bluebream/zope.conf
# main zope configuration file for deployment
# Identify the component configuration used to define the site:
site-definition application.zcml
<zodb>
<filestorage>
path var/filestorage/Data.$(SERVER_ID).fs
blob-dir var/blob.$(SERVER_ID)
</filestorage>
# Uncomment this if you want to connect to a ZEO server instead:
# <zeoclient>
# server $(ZEO_SERVER)
# storage 1
# # ZEO client cache, in bytes
# cache-size $(ZEO_CACHE_SIZE)
# # Uncomment to have a persistent disk cache
# #client zeo1$(SERVER_ID)
# </zeoclient>
</zodb>
<eventlog>
# This sets up logging to both a file and to standard output (STDOUT).
# The "path" setting can be a relative or absolute filesystem path or
# the tokens STDOUT or STDERR.
<logfile>
path var/log/loops-$(SERVER_ID).log
formatter zope.exceptions.log.Formatter
</logfile>
<logfile>
path STDOUT
formatter zope.exceptions.log.Formatter
</logfile>
</eventlog>

9
inst/loops/zshell.sh Executable file
View file

@ -0,0 +1,9 @@
# inst/loops/zshell.sh
set -a
# use environment variables for instance-specific configuration:
#SERVER_ID=0
#LOOPS_PATH=sites/mysite
python -ic "from loops.server import psu; psu.setup()"

View file

@ -1,54 +0,0 @@
#
# 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
#
"""
Access to external objects.
$Id$
"""
import os, re
from zope import component
from zope.cachedescriptors.property import Lazy
from zope.component import adapts
from zope.interface import implements
from cybertools.integrator.interfaces import IContainerFactory
from loops.common import AdapterBase, adapted
from loops.integrator.content.interfaces import IExternalAccess
from loops.interfaces import IConcept
from loops.type import TypeInterfaceSourceList
TypeInterfaceSourceList.typeInterfaces += (IExternalAccess,)
class ExternalAccess(AdapterBase):
""" A concept adapter for accessing external collection.
"""
implements(IExternalAccess)
adapts(IConcept)
_contextAttributes = list(IExternalAccess) + list(IConcept)
def __call__(self):
factory = component.getUtility(IContainerFactory, self.providerName)
address = os.path.join(self.baseAddress, self.address or '')
return factory(address, __parent__=self.context)

View file

@ -1,45 +0,0 @@
#
# Copyright (c) 2009 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
#
"""
Adapter for mail resources.
$Id$
"""
from zope.cachedescriptors.property import Lazy
from zope import component
from zope.component import adapts
from zope.interface import implements
from loops.integrator.mail.interfaces import IMailResource
from loops.resource import TextDocumentAdapter
from loops.type import TypeInterfaceSourceList
TypeInterfaceSourceList.typeInterfaces += (IMailResource,)
class MailResource(TextDocumentAdapter):
""" A concept adapter for accessing a mail collection.
May delegate access to a named utility.
"""
implements(IMailResource)
_contextAttributes = list(IMailResource)

View file

@ -1 +0,0 @@
'''package loops.knowledge.qualification'''

View file

@ -1,43 +0,0 @@
#
# 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
#
"""
Controlling qualification activities of persons.
Central part of CCM competence and certification management framework.
"""
from zope.component import adapts
from zope.interface import implementer, implements
from loops.common import AdapterBase
from loops.interfaces import IConcept
from loops.knowledge.qualification.interfaces import ICompetence
from loops.type import TypeInterfaceSourceList
TypeInterfaceSourceList.typeInterfaces += (ICompetence,)
class Competence(AdapterBase):
implements(ICompetence)
_contextAttributes = list(ICompetence)

Binary file not shown.

View file

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

View file

@ -1,30 +1,13 @@
# # loops.base
# Copyright (c) 2017 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.
Implementation of loops root object.
""" """
from zope.app.container.btree import BTreeContainer from zope.container.btree import BTreeContainer
from zope.app.folder.folder import Folder from zope.site.folder import Folder
from zope.app.folder.interfaces import IFolder from zope.site.interfaces import IFolder
from zope.traversing.api import getPath, traverse from zope.traversing.api import getPath, traverse
from zope.interface import implements from zope.interface import implementer
from cybertools.util.jeep import Jeep from cybertools.util.jeep import Jeep
from loops.interfaces import ILoops from loops.interfaces import ILoops
@ -32,17 +15,8 @@ from loops.interfaces import ILoops
loopsPrefix = '.loops' loopsPrefix = '.loops'
@implementer(ILoops)
class Loops(Folder): class Loops(Folder):
#class Loops(BTreeContainer):
implements(ILoops)
#def getSiteManager(self):
# return self.__parent__.getSiteManager()
#@property
#def _SampleContainer__data(self):
# return self.data
_skinName = '' _skinName = ''
def getSkinName(self): return self._skinName def getSkinName(self): return self._skinName
@ -72,7 +46,8 @@ class Loops(Folder):
return self.get('records') return self.get('records')
def getLoopsUri(self, obj): def getLoopsUri(self, obj):
return str(loopsPrefix + getPath(obj)[len(getPath(self)):]) uri = loopsPrefix + getPath(obj)[len(getPath(self)):]
return uri
def loopsTraverse(self, uri): def loopsTraverse(self, uri):
prefix = loopsPrefix + '/' prefix = loopsPrefix + '/'

68
loops/bluebream.zcml Normal file
View file

@ -0,0 +1,68 @@
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser">
<include package="zope.component" file="meta.zcml" />
<include package="zope.security" file="meta.zcml" />
<include package="zope.publisher" file="meta.zcml" />
<include package="zope.i18n" file="meta.zcml" />
<include package="zope.browserresource" file="meta.zcml" />
<include package="zope.browsermenu" file="meta.zcml" />
<include package="zope.browserpage" file="meta.zcml" />
<include package="zope.securitypolicy" file="meta.zcml" />
<include package="zope.principalregistry" file="meta.zcml" />
<include package="zope.app.publication" file="meta.zcml" />
<include package="zope.app.form.browser" file="meta.zcml" />
<include package="zope.app.container.browser" file="meta.zcml" />
<include package="zope.browserresource" />
<include package="zope.copypastemove" />
<include package="zope.publisher" />
<include package="zope.component" />
<include package="zope.traversing" />
<include package="zope.location" />
<include package="zope.site" />
<include package="zope.annotation" />
<include package="zope.principalregistry" />
<include package="zope.container" />
<include package="zope.componentvocabulary" />
<include package="zope.formlib" />
<include package="zope.app.appsetup" />
<include package="zope.app.security" />
<include package="zope.app.publication" />
<include package="zope.app.form.browser" />
<include package="zope.app.basicskin" />
<include package="zope.browsermenu" />
<include package="zope.authentication" />
<include package="zope.securitypolicy" />
<include package="zope.login" />
<include package="zope.session" />
<include package="zope.error" />
<include package="zope.app.zcmlfiles" file="menus.zcml" />
<include package="zope.app.authentication" />
<include package="zope.app.security.browser" />
<include package="zope.app.catalog" />
<include package="zope.traversing.browser" />
<include package="zope.browserpage" />
<include package="zope.app.schema" />
<include package="zope.app.http" />
<include package="zope.keyreference" />
<include package="zope.intid" />
<include package="zope.contentprovider" />
<include package="zope.i18n" />
<include package="zope.catalog" />
<include package="zope.dublincore.browser" />
<include package="zope.app.zcmlfiles" />
<include package="zope.app.i18n" />
<include package="zope.app.intid" />
<include package="zope.app.renderer" />
<include package="zope.app.session" />
<include package="zope.sendmail" file="meta.zcml" />
<browser:defaultView
for="zope.container.interfaces.IContainer"
name="index.html" />
</configure>

View file

@ -1,26 +1,9 @@
# # 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 import urlencode from urllib.parse import urlencode
from zope import component from zope import component
from zope.app.pagetemplate import ViewPageTemplateFile from zope.app.pagetemplate import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy from zope.cachedescriptors.property import Lazy

78
loops/browser/auth.py Normal file
View file

@ -0,0 +1,78 @@
# loops.browser.auth
""" Login, logout, unauthorized stuff.
"""
from zope.app.exception.browser.unauthorized import Unauthorized as DefaultUnauth
from zope.authentication.interfaces import IAuthentication
from zope.authentication.interfaces import ILogout, IUnauthenticatedPrincipal
from zope.browserpage import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy
from zope import component
from zope.interface import implementer
from loops.browser.concept import ConceptView
from loops.browser.node import NodeView
template = ViewPageTemplateFile('auth.pt')
class LoginConcept(ConceptView):
template = template
@Lazy
def macro(self):
return self.template.macros['login_form']
class LoginForm(NodeView):
template = template
@Lazy
def macro(self):
return self.template.macros['login_form']
@Lazy
def item(self):
return self
@implementer(ILogout)
class Logout(object):
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)
class Unauthorized(ConceptView):
isTopLevel = True
def __init__(self, context, request):
self.context = context
self.request = request
def __call__(self):
response = self.request.response
response.setStatus(403)
# make sure that squid does not keep the response in the cache
response.setHeader('Expires', 'Mon, 26 Jul 1997 05:00:00 GMT')
response.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate')
response.setHeader('Pragma', 'no-cache')
if self.nodeView is None:
v = DefaultUnauth(self.context, self.request)
return v()
url = self.nodeView.topMenu.url
response.redirect(url + '/unauthorized')

View file

@ -1,45 +1,28 @@
# # loops.browser.common
# Copyright (c) 2016 Helmut Merz helmutm@cy55.de
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
""" """ 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 #import mimetypes # use more specific assignments from cybertools.text
from datetime import date, datetime from datetime import date, datetime
from logging import getLogger
import re import re
from time import strptime from time import strptime
from urllib import urlencode from urllib.parse import parse_qs, parse_qsl, urlencode
from zope import component from zope import component
from zope.app.form.browser.interfaces import ITerms from zope.authentication.interfaces import IAuthentication, IUnauthenticatedPrincipal
from zope.app.i18n.interfaces import ITranslationDomain from zope.authentication.interfaces import IUnauthenticatedPrincipal
from zope.app.security.interfaces import IAuthentication, IUnauthenticatedPrincipal from zope.authentication.interfaces import PrincipalLookupError
from zope.app.pagetemplate import ViewPageTemplateFile from zope.browser.interfaces import ITerms
from zope.app.security.interfaces import IUnauthenticatedPrincipal from zope.browserpage import ViewPageTemplateFile
from zope.app.security.interfaces import PrincipalLookupError
from zope.cachedescriptors.property import Lazy from zope.cachedescriptors.property import Lazy
from zope.dottedname.resolve import resolve from zope.dottedname.resolve import resolve
from zope.dublincore.interfaces import IZopeDublinCore from zope.dublincore.interfaces import IZopeDublinCore
from zope.formlib import form from zope.formlib import form
from zope.formlib.form import FormFields from zope.formlib.form import FormFields
from zope.formlib.namedtemplate import NamedTemplate from zope.formlib.namedtemplate import NamedTemplate
from zope.interface import Interface, implements from zope.i18n.interfaces import ITranslationDomain
from zope.interface import Interface, implementer
from zope.proxy import removeAllProxies from zope.proxy import removeAllProxies
from zope.publisher.browser import applySkin from zope.publisher.browser import applySkin
from zope.publisher.interfaces.browser import IBrowserSkinType, IBrowserView from zope.publisher.interfaces.browser import IBrowserSkinType, IBrowserView
@ -98,16 +81,20 @@ class NameField(schema.ASCIILine):
class ViewMode(object): class ViewMode(object):
def __init__(self, name='view', title=None, url=None, active=False, def __init__(self, name='view', title=None, url=None, active=False,
description=u''): description=u'', subViewModes=Jeep()):
self.name = name self.name = name
self.title = title self.title = title
self.url = url self.url = url
self.active = active self.active = active
self.description = description self.description = description
self.subViewModes = subViewModes
@property @property
def cssClass(self): def cssClass(self):
return self.active and u'active' or u'inactive' result = self.active and u'active' or u'inactive'
if self.subViewModes:
result += u' sub-modes'
return result
class IAddForm(Interface): class IAddForm(Interface):
@ -205,6 +192,10 @@ class BaseView(GenericView, I18NView, SortableMixin):
self.context = removeSecurityProxy(context) self.context = removeSecurityProxy(context)
try: try:
if not self.checkPermissions(): 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)) raise Unauthorized(str(self.contextInfo))
except ForbiddenAttribute: # ignore when testing except ForbiddenAttribute: # ignore when testing
pass pass
@ -904,6 +895,7 @@ class BaseView(GenericView, I18NView, SortableMixin):
@Lazy @Lazy
def xeditable(self): def xeditable(self):
return False
if self.typeOptions('no_external_edit'): if self.typeOptions('no_external_edit'):
return False return False
ct = getattr(self.context, 'contentType', '') ct = getattr(self.context, 'contentType', '')
@ -952,9 +944,9 @@ class BaseView(GenericView, I18NView, SortableMixin):
djConfig='parseOnLoad: true, usePlainJson: true, ' djConfig='parseOnLoad: true, usePlainJson: true, '
#'isDebug: true, ' #'isDebug: true, '
'locale: "%s"' % self.languageInfo.language) 'locale: "%s"' % self.languageInfo.language)
jsCall = ('dojo.require("dojo.parser"); ' jsCall = ('dojo.require("dojo.parser"); ')
'dojo.registerModulePath("jocy", "/@@/cybertools.jocy"); ' #'dojo.registerModulePath("jocy", "/@@/cybertools.jocy"); '
'dojo.require("jocy.data");') #'dojo.require("jocy.data");')
cm.register('js-execute', 'dojo_registration', jsCall=jsCall) cm.register('js-execute', 'dojo_registration', jsCall=jsCall)
cm.register('css', identifier='Lightbox.css', position=0, cm.register('css', identifier='Lightbox.css', position=0,
resourceName='ajax.dojo/dojox/image/resources/Lightbox.css', resourceName='ajax.dojo/dojox/image/resources/Lightbox.css',
@ -1082,13 +1074,12 @@ class LoggedIn(object):
# vocabulary stuff # vocabulary stuff
@implementer(ITerms)
class SimpleTerms(object): class SimpleTerms(object):
""" Provide the ITerms interface, e.g. for usage in selection """ Provide the ITerms interface, e.g. for usage in selection
lists. lists.
""" """
implements(ITerms)
def __init__(self, source, request): def __init__(self, source, request):
# the source parameter is a list of tuples (token, title). # the source parameter is a list of tuples (token, title).
self.source = source self.source = source
@ -1103,13 +1094,12 @@ class SimpleTerms(object):
return (token, self.terms[token]) return (token, self.terms[token])
@implementer(ITerms)
class LoopsTerms(object): class LoopsTerms(object):
""" Provide the ITerms interface, e.g. for usage in selection """ Provide the ITerms interface, e.g. for usage in selection
lists. lists.
""" """
implements(ITerms)
def __init__(self, source, request): def __init__(self, source, request):
# the source parameter is a view or adapter of a real context object: # the source parameter is a view or adapter of a real context object:
self.source = source self.source = source
@ -1131,12 +1121,11 @@ class LoopsTerms(object):
return self.loopsRoot.loopsTraverse(token) return self.loopsRoot.loopsTraverse(token)
@implementer(ITerms)
class InterfaceTerms(object): class InterfaceTerms(object):
""" Provide the ITerms interface for source list of interfaces. """ Provide the ITerms interface for source list of interfaces.
""" """
implements(ITerms)
def __init__(self, source, request): def __init__(self, source, request):
self.source = source self.source = source
self.request = request self.request = request

View file

@ -1,40 +1,22 @@
# # loops.browser.concept
# Copyright (c) 2016 Helmut Merz helmutm@cy55.de
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
""" """ Definition of the concept view classes.
Definition of the concept view classes.
""" """
from itertools import groupby from itertools import groupby
from zope import interface, component, schema from zope import interface, component, schema
from zope.app.catalog.interfaces import ICatalog from zope.authentication.interfaces import IUnauthenticatedPrincipal
from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent from zope.browser.interfaces import ITerms
from zope.app.container.contained import ObjectRemovedEvent from zope.browserpage import ViewPageTemplateFile
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.cachedescriptors.property import Lazy
from zope.catalog.interfaces import ICatalog
from zope.container.contained import ObjectRemovedEvent
from zope.dottedname.resolve import resolve from zope.dottedname.resolve import resolve
from zope.event import notify from zope.event import notify
from zope.formlib.form import EditForm, FormFields, setUpEditWidgets from zope.formlib.form import EditForm, FormFields, setUpEditWidgets
from zope.formlib.interfaces import IDisplayWidget
from zope.formlib.namedtemplate import NamedTemplate from zope.formlib.namedtemplate import NamedTemplate
from zope.interface import implements from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
from zope.publisher.interfaces import BadRequest from zope.publisher.interfaces import BadRequest
from zope.publisher.interfaces.browser import IBrowserRequest from zope.publisher.interfaces.browser import IBrowserRequest
from zope.schema.interfaces import IIterableSource from zope.schema.interfaces import IIterableSource
@ -308,7 +290,7 @@ class ConceptView(BaseView):
def breadcrumbsParent(self): def breadcrumbsParent(self):
for p in self.context.getParents([self.defaultPredicate]): for p in self.context.getParents([self.defaultPredicate]):
view = self.nodeView.getViewForTarget(p) view = self.nodeView.getViewForTarget(p)
if view.showInBreadcrumbs: if view is not None and view.showInBreadcrumbs:
return view return view
return None return None
@ -463,7 +445,7 @@ class ConceptView(BaseView):
def parents(self): def parents(self):
rels = sorted(self.context.getParentRelations(), rels = sorted(self.context.getParentRelations(),
key=(lambda x: x.first.title and x.first.title.lower())) key=(lambda x: x.first.title and x.first.title.lower() or ''))
for r in rels: for r in rels:
yield self.childViewFactory(r, self.request) yield self.childViewFactory(r, self.request)

View file

@ -10,7 +10,9 @@
<metal:data define-macro="conceptdata"> <metal:data define-macro="conceptdata">
<div tal:attributes="class string:content-$level;"> <div tal:attributes="class string:content-$level;">
<metal:block use-macro="view/concept_macros/concepttitle" /> <tal:block condition="not:title_shown|python:False">
<metal:block use-macro="view/concept_macros/concepttitle"/>
</tal:block>
<metal:slot define-slot="fields"> <metal:slot define-slot="fields">
<metal:block use-macro="view/concept_macros/conceptfields" /> <metal:block use-macro="view/concept_macros/conceptfields" />
</metal:slot> </metal:slot>
@ -185,7 +187,7 @@
</tr> </tr>
<tal:nested condition="list_nested"> <tal:nested condition="list_nested">
<tr tal:define="children python: <tr tal:define="children python:
list(related.unique(related.children())); list(related.unique(related.children()));
resources python:list(related.resources())" resources python:list(related.resources())"
tal:condition="python:children or resources"> tal:condition="python:children or resources">
<td tal:condition="item/showCheckboxes|nothing" /> <td tal:condition="item/showCheckboxes|nothing" />

View file

@ -34,16 +34,18 @@
<!-- login/logout --> <!-- login/logout -->
<page for="loops.interfaces.INode" <!--<page for="loops.interfaces.INode"
name="login.html" name="login.html"
class="loops.browser.auth.LoginForm" class="loops.browser.auth.LoginForm"
permission="zope.View" /> permission="zope.View" />-->
<page for="loops.interfaces.INode" <page for="loops.interfaces.INode"
name="logout.html" name="logout.html"
class="loops.browser.auth.Logout" class="loops.browser.auth.Logout"
permission="zope.View" /> permission="zope.View" />
<!-- see also view/adapter "login.html" in section "query views" -->
<!-- macros --> <!-- macros -->
<page <page
@ -537,6 +539,14 @@
<!-- query views --> <!-- 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 <zope:adapter
name="list_children.html" name="list_children.html"
for="loops.interfaces.IConcept for="loops.interfaces.IConcept

View file

@ -1,28 +1,8 @@
# # 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 import zapi
from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy 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,35 +1,18 @@
# # loops.browser.form
# Copyright (c) 2017 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 import urlencode, unquote_plus from urllib.parse import urlencode, unquote_plus
from zope.app.container.contained import ObjectRemovedEvent
from zope import component, interface, schema from zope import component, interface, schema
from zope.component import adapts from zope.component import adapts
from zope.container.contained import ObjectRemovedEvent
from zope.event import notify from zope.event import notify
from zope.interface import Interface from zope.interface import Interface
from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
from zope.app.container.interfaces import INameChooser from zope.container.interfaces import INameChooser
from zope.app.container.contained import ObjectAddedEvent from zope.lifecycleevent import ObjectAddedEvent
from zope.app.pagetemplate import ViewPageTemplateFile from zope.browserpage import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy from zope.cachedescriptors.property import Lazy
from zope.contenttype import guess_content_type from zope.contenttype import guess_content_type
from zope.publisher.browser import FileUpload from zope.publisher.browser import FileUpload
@ -182,7 +165,9 @@ class ObjectForm(NodeView):
field = self.schema.fields.get(k) field = self.schema.fields.get(k)
if field: if field:
fi = field.getFieldInstance(self.instance) fi = field.getFieldInstance(self.instance)
input = unquote_plus(form[k]) input = form[k]
if isinstance(input, str):
input = unquote_plus(input)
data[k] = fi.marshall(fi.unmarshall(input)) data[k] = fi.marshall(fi.unmarshall(input))
#data[k] = toUnicode(form[k]) #data[k] = toUnicode(form[k])
return data return data
@ -364,6 +349,7 @@ class CreateObjectForm(ObjectForm):
def adapted(self): def adapted(self):
ad = self.typeInterface(Resource()) ad = self.typeInterface(Resource())
ad.storageName = 'unknown' # hack for file objects: don't try to retrieve data ad.storageName = 'unknown' # hack for file objects: don't try to retrieve data
ad.__is_dummy__ = True
ad.__type__ = adapted(self.typeConcept) ad.__type__ = adapted(self.typeConcept)
return ad return ad

View file

@ -1,26 +1,9 @@
# # 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 cgi import parse_qs from urllib.parse import parse_qs
from zope import interface, component from zope import interface, component
from zope.app.pagetemplate import ViewPageTemplateFile from zope.app.pagetemplate import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy from zope.cachedescriptors.property import Lazy

View file

@ -1,3 +1,4 @@
# loops.browser.lobo.tests
import unittest, doctest import unittest, doctest
from zope.interface.verify import verifyClass from zope.interface.verify import verifyClass
@ -12,9 +13,9 @@ class Test(unittest.TestCase):
def test_suite(): def test_suite():
flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
return unittest.TestSuite(( return unittest.TestSuite((
unittest.makeSuite(Test), unittest.TestLoader().loadTestsFromTestCase(Test),
doctest.DocFileSuite('README.txt', optionflags=flags), doctest.DocFileSuite('README.txt', optionflags=flags),
)) ))
if __name__ == '__main__': if __name__ == '__main__':
unittest.main(defaultTest='test_suite') unittest.main(defaultTest='test_suite')

View file

Before

Width:  |  Height:  |  Size: 942 B

After

Width:  |  Height:  |  Size: 942 B

View file

@ -1,31 +1,11 @@
# # 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.app.pagetemplate import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy from zope.cachedescriptors.property import Lazy
from zope import component from zope import component
from zope.interface import implements
from cybertools.browser.renderer import RendererFactory from cybertools.browser.renderer import RendererFactory
from cybertools.composer.layout.base import Layout from cybertools.composer.layout.base import Layout

View file

@ -1,35 +1,20 @@
# # loops.browser.node
# Copyright (c) 2017 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 urlparse import urlparse, urlunparse from logging import getLogger
from zope import component, interface, schema from urllib.parse import urlencode, urlparse, urlunparse
from zope.cachedescriptors.property import Lazy #from urlparse import urlparse, urlunparse
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.contents import JustContents
from zope.app.container.browser.adding import Adding from zope.app.container.browser.adding import Adding
from zope.app.container.traversal import ItemTraverser from zope import component, interface, schema
from zope.app.pagetemplate import ViewPageTemplateFile from zope.annotation.interfaces import IAnnotations
from zope.app.security.interfaces import IUnauthenticatedPrincipal from zope.authentication.interfaces import IUnauthenticatedPrincipal
from zope.browserpage import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy
from zope.catalog.interfaces import ICatalog
from zope.container.traversal import ItemTraverser
from zope.dottedname.resolve import resolve from zope.dottedname.resolve import resolve
from zope.event import notify from zope.event import notify
from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
@ -37,6 +22,7 @@ from zope.lifecycleevent import Attributes
from zope.formlib.form import Form, FormFields from zope.formlib.form import Form, FormFields
from zope.proxy import removeAllProxies from zope.proxy import removeAllProxies
from zope.publisher.defaultview import getDefaultViewName from zope.publisher.defaultview import getDefaultViewName
from zope.publisher.interfaces import NotFound
from zope.security import canAccess, canWrite, checkPermission from zope.security import canAccess, canWrite, checkPermission
from zope.security.proxy import removeSecurityProxy from zope.security.proxy import removeSecurityProxy
from zope.traversing.api import getParent, getParents, getPath from zope.traversing.api import getParent, getParents, getPath
@ -65,6 +51,7 @@ from loops import util
from loops.util import _ from loops.util import _
from loops.versioning.util import getVersion from loops.versioning.util import getVersion
logger = getLogger('loops.browser.node')
node_macros = ViewPageTemplateFile('node_macros.pt') node_macros = ViewPageTemplateFile('node_macros.pt')
info_macros = ViewPageTemplateFile('info.pt') info_macros = ViewPageTemplateFile('info.pt')
@ -473,6 +460,11 @@ class NodeView(BaseView):
def active(self, item): def active(self, item):
return item.context == self.context or item.context in self.parents 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 @Lazy
def authenticationMethod(self): def authenticationMethod(self):
return self.viewAnnotations.get('auth_method') or 'standard' return self.viewAnnotations.get('auth_method') or 'standard'
@ -589,7 +581,7 @@ class NodeView(BaseView):
@Lazy @Lazy
def typeProvider(self): def typeProvider(self):
if self.virtualTargetObject is not None: if self.virtualTargetObject is not None:
return IType(self.virtualTargetObject).typeProvider return IType(baseObject(self.virtualTargetObject)).typeProvider
return None return None
# target viewing and editing support # target viewing and editing support
@ -777,11 +769,11 @@ class InlineEdit(NodeView):
if ti is not None: if ti is not None:
target = ti(target) target = ti(target)
data = self.request.form['editorContent'] data = self.request.form['editorContent']
if type(data) != unicode: if not isinstance(data, str):
try: try:
data = data.decode('ISO-8859-15') # IE hack data = data.decode('ISO-8859-15') # IE hack
except UnicodeDecodeError: except UnicodeDecodeError:
print 'loops.browser.node.InlineEdit.save():', data print('loops.browser.node.InlineEdit.save():', data)
return return
# data = data.decode('UTF-8') # data = data.decode('UTF-8')
target.data = data target.data = data
@ -953,9 +945,9 @@ class NodeAdding(Adding):
return info return info
@interface.implementer(IViewConfiguratorSchema)
class ViewPropertiesConfigurator(object): class ViewPropertiesConfigurator(object):
interface.implements(IViewConfiguratorSchema)
component.adapts(INode) component.adapts(INode)
def __init__(self, context): def __init__(self, context):
@ -1041,7 +1033,12 @@ class NodeTraverser(ItemTraverser):
viewAnnotations['targetView'] = view viewAnnotations['targetView'] = view
view.logInfo('NodeTraverser:targetView = %r' % view) view.logInfo('NodeTraverser:targetView = %r' % view)
return self.context return self.context
obj = super(NodeTraverser, self).publishTraverse(request, name) try:
obj = super(NodeTraverser, self).publishTraverse(request, name)
except NotFound:
logger.warn('NodeTraverser: NotFound: URL = %s, name = %r' %
(request.URL, name))
raise
return obj return obj
def getTargetUid(self, request): def getTargetUid(self, request):
@ -1089,7 +1086,9 @@ def setViewConfiguration(context, request):
config = IViewConfiguratorSchema(context) config = IViewConfiguratorSchema(context)
skinName = config.skinName skinName = config.skinName
if not skinName: if not skinName:
skinName = context.getLoopsRoot().skinName root = removeSecurityProxy(context.getLoopsRoot())
skinName = root.skinName
#skinName = context.getLoopsRoot().skinName
if skinName: if skinName:
viewAnnotations['skinName'] = skinName viewAnnotations['skinName'] = skinName
if config.options: if config.options:
@ -1108,12 +1107,12 @@ def getViewConfiguration(context, request):
class TestView(NodeView): class TestView(NodeView):
def __call__(self): def __call__(self):
print '*** begin' print( '*** begin')
for i in range(500): for i in range(500):
#x = util.getObjectForUid('1994729849') #x = util.getObjectForUid('1994729849')
x = util.getObjectForUid('2018653366') x = util.getObjectForUid('2018653366')
self.c = list(x.getChildren()) self.c = list(x.getChildren())
#self.c = list(x.getChildren([self.defaultPredicate])) #self.c = list(x.getChildren([self.defaultPredicate]))
print '*** end', len(self.c) print('*** end', len(self.c))
return 'done' return 'done'

View file

Before

Width:  |  Height:  |  Size: 942 B

After

Width:  |  Height:  |  Size: 942 B

View file

@ -267,19 +267,28 @@
<metal:actions define-macro="view_modes"> <metal:actions define-macro="view_modes">
<div style="margin-bottom: 10px" <ul class="view-modes"
tal:define="viewModes view/viewModes" tal:define="viewModes view/viewModes"
tal:condition="viewModes"> tal:condition="viewModes">
<ul class="view-modes"> <li tal:repeat="mode viewModes"
<li tal:repeat="mode viewModes" tal:attributes="class mode/cssClass">
tal:attributes="class mode/cssClass"> <a tal:attributes="href mode/url;
<a tal:attributes="href mode/url; title mode/description"
title mode/description" tal:content="mode/title"
tal:content="mode/title" i18n:translate="" />
i18n:translate="" /> <ul class="sub-view-modes"
</li> tal:define="subModes mode/subViewModes|nothing"
</ul> tal:condition="subModes">
</div> <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> </metal:actions>
@ -335,8 +344,7 @@
<metal:actions define-macro="personal"> <metal:actions define-macro="personal">
<div><a href="logout.html?nextURL=login.html" <div><a tal:attributes="href view/logoutUrl"
tal:attributes="href string:logout.html?nextURL=${view/menu/url}"
i18n:translate="">Log out</a></div> i18n:translate="">Log out</a></div>
<tal:actions repeat="action python:view.getAllowedActions('personal')"> <tal:actions repeat="action python:view.getAllowedActions('personal')">
<metal:action use-macro="action/macro" /> <metal:action use-macro="action/macro" />

View file

@ -18,10 +18,6 @@
<a href="#" <a href="#"
tal:attributes="href string:${target/url}/@@configure.html" tal:attributes="href string:${target/url}/@@configure.html"
tal:content="target/title">Document xy</a> 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> </tal:target>
</div> </div>

View file

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

View file

@ -1,36 +1,18 @@
# # 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 import os.path
import urllib from zope.authentication.interfaces import IUnauthenticatedPrincipal
from zope.browserpage import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy from zope.cachedescriptors.property import Lazy
from zope import component from zope import component
from zope.app.catalog.interfaces import ICatalog from zope.catalog.interfaces import ICatalog
from zope.app.container.interfaces import INameChooser from zope.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.form import FormFields
from zope.formlib.interfaces import DISPLAY_UNWRITEABLE from zope.formlib.interfaces import DISPLAY_UNWRITEABLE
from zope.formlib.textwidgets import FileWidget
from zope.proxy import removeAllProxies from zope.proxy import removeAllProxies
from zope.schema.interfaces import IBytes from zope.schema.interfaces import IBytes
from zope.security import canAccess, canWrite from zope.security import canAccess, canWrite
@ -272,7 +254,7 @@ class ResourceView(BaseView):
#wp = wiki.createPage(getName(self.context)) #wp = wiki.createPage(getName(self.context))
wp = wiki.addPage(LoopsWikiPage(self.context)) wp = wiki.addPage(LoopsWikiPage(self.context))
wp.text = text wp.text = text
#print wp.wiki.getManager() #print(wp.wiki.getManager())
#return util.toUnicode(wp.render(self.request)) #return util.toUnicode(wp.render(self.request))
return super(ResourceView, self).renderText(text, contentType) return super(ResourceView, self).renderText(text, contentType)
@ -467,7 +449,7 @@ class ExternalEditorView(ExternalEditorView, BaseView):
r.append('meta_type:' + '.'.join((context.__module__, context.__class__.__name__))) r.append('meta_type:' + '.'.join((context.__module__, context.__class__.__name__)))
auth = self.request.get('_auth') auth = self.request.get('_auth')
if auth: if auth:
print 'ExternalEditorView: auth = ', auth print('ExternalEditorView: auth = ', auth)
if auth.endswith('\n'): if auth.endswith('\n'):
auth = auth[:-1] auth = auth[:-1]
r.append('auth:' + auth) r.append('auth:' + auth)

View file

@ -72,6 +72,7 @@
<div> <div>
<span class="button"> <span class="button">
<a i18n:translate="" <a i18n:translate=""
target="_blank"
tal:attributes="href tal:attributes="href
string:${view/virtualTargetUrl}/download.html?version=this"> string:${view/virtualTargetUrl}/download.html?version=this">
Download Download

View file

@ -25,6 +25,7 @@
</div> </div>
<div id="content" class="span-6" <div id="content" class="span-6"
tal:attributes="class content_class|string:span-6"
metal:define-macro="content"> metal:define-macro="content">
<metal:breadcrumbs define-slot="breadcrumbs"> <metal:breadcrumbs define-slot="breadcrumbs">
<metal:tabs use-macro="views/node_macros/breadcrumbs" /> <metal:tabs use-macro="views/node_macros/breadcrumbs" />
@ -37,7 +38,9 @@
tal:condition="msg" tal:condition="msg"
tal:content="msg" /> tal:content="msg" />
</metal:message> </metal:message>
<metal:tabs use-macro="views/node_macros/view_modes" /> <metal:tabs define-slot="view_modes">
<metal:tabs use-macro="views/node_macros/view_modes" />
</metal:tabs>
<metal:content define-slot="content"> <metal:content define-slot="content">
<tal:content define="item nocall:view/item; <tal:content define="item nocall:view/item;
level level|python: 1; level level|python: 1;
@ -48,13 +51,18 @@
</metal:content> </metal:content>
</div> </div>
<div id="portlets" class="span-2 last"> <div id="portlets" class="span-2 last"
<tal:portlet repeat="macro controller/macros/portlet_left"> tal:attributes="class portlets_class|string:span-2 last">
<metal:portlet use-macro="macro" /> <metal:portlet define-slot="portlet-left" >
</tal:portlet> <tal:portlet repeat="macro controller/macros/portlet_left">
<tal:portlet repeat="macro controller/macros/portlet_right"> <metal:portlet use-macro="macro" />
<metal:portlet use-macro="macro" /> </tal:portlet>
</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>
<div id="footer" class="footer clear" <div id="footer" class="footer clear"

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