Compare commits

...
Sign in to create a new pull request.

404 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
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
3263672934 extend/improve paster shell utilities 2020-03-04 16:55:53 +01:00
bbd1d06792 update version number 2019-03-09 13:26:55 +01:00
8bd69b12fd use targetItem to access also directly assigned concepts or resources 2018-10-17 08:17:52 +02:00
fb94aacaa8 add method for checking for top-level page (e.g. data protection stmt) 2018-05-19 11:24:29 +02:00
ff0e19a72c remove obsolete code 2018-05-19 11:23:42 +02:00
937f2ee8b0 work statement report: add selection field for person/party 2018-05-14 12:42:30 +02:00
afd4416eaa fix typos in JavaScript file 2018-05-04 16:13:42 +02:00
a296370484 when rendering .odt/.xlsx: use field names instead of title 2018-03-04 09:51:22 +01:00
4b5680d969 fix (after using dict instead of jeep): sort glossary correctly 2018-02-21 16:03:04 +01:00
590cffaa35 sort letters in headline 2018-02-19 14:37:10 +01:00
d282e3c93a avoid unicode error if keyword starts with special characters.
In addition show only starting characters appearing in word list
in headline.
2018-02-19 14:30:53 +01:00
e35fafad81 activate calling of script; remove print statements 2018-01-07 11:17:40 +01:00
de43ab03ca take file names/paths from appropriate settings 2017-12-31 15:56:05 +01:00
a13784e93e use markdown import and content type only when available 2017-12-29 10:37:13 +01:00
8c09a0e73d add tab title for book_topic_view; rearrange section methods 2017-12-08 12:17:02 +01:00
4b49fda269 make sure we always get numeric values 2017-12-07 18:26:27 +01:00
8c29a3e7d4 duration field: use 0 as default value; allow specification of a factor 2017-12-07 12:01:45 +01:00
3b37723cd4 show text 'more...' only if there is really (probably) more to see 2017-12-05 13:23:20 +01:00
cfc32d7ebd fix breadcrumbs for book topic view 2017-12-05 12:59:21 +01:00
ef8a47c1a1 column headers: use default translation domain if appropriate 2017-11-14 22:42:08 +01:00
516eda33d1 allow conversion/processing of CSV file to XLSX upon download 2017-11-14 17:41:56 +01:00
georgm
7f7a2af25f add markdown support 2017-10-14 16:04:53 +02:00
d863305b94 fix check for adapter in 'externalIdentifier' 2017-10-08 12:44:04 +02:00
6a685187d5 fix import file (double 'options' parameter for 'topic' type) 2017-10-08 09:00:23 +02:00
6ca85b21db add option for creating and editing topics to topic type 2017-10-01 08:33:49 +02:00
6fd47572f2 control editing of work items via global option (persmission setting) 2017-08-29 14:06:21 +02:00
5d02a5ee03 fix URL for state change via portlet 2017-07-16 16:33:42 +02:00
536f204a03 use empty workitem_dayfrom_default option for empty start date 2017-05-09 12:07:37 +02:00
efb2c8ba71 fix GET params quoting on input fields 2017-03-13 10:43:35 +01:00
34926dde8c fix acces to view properties; optional use global copyright option 2017-01-12 11:34:15 +01:00
b7c702f17d add node type raw to be able to emit e.g. robots.txt and similar stuff 2017-01-12 10:28:06 +01:00
b3677ea635 fix initid monkey patch 2017-01-12 10:15:31 +01:00
421c21cdec make ZTK-compatible package that is installable as a Python egg 2017-01-02 16:06:14 +01:00
e51ebdb28c de-activate (obsolete) role for Flash (XML-RPC) concept map editor 2017-01-01 17:27:56 +01:00
4cfdebec77 fix language negotiation: use language defined via options 2017-01-01 17:26:47 +01:00
dd96f01a32 improve/fix settings for import of basic loops site structure 2017-01-01 17:25:41 +01:00
5083b2edc2 dirty fix: monkey patch in IntIds to avoid ForbiddenAttribute error 2017-01-01 17:24:00 +01:00
9670ace28c remove obsolete property from Loops root, ensuring compatibility with ZTK 2016-12-26 18:19:28 +01:00
96e7c20a56 remove deprecated import from zope.testing 2016-12-26 10:46:22 +01:00
a91e1b0c4c remove deprecated import from zope.testing 2016-12-26 10:37:00 +01:00
3115b4f19e fix description field in recent changes report 2016-11-18 12:48:13 +01:00
6261831340 indexing: gracefully fall back to ISO8859 if UTF-8 gives an error 2016-10-26 16:20:44 +02:00
5f78698e06 check for 'visible' field property 2016-10-08 11:18:45 +02:00
7b0fe5aaf2 object creation: take type options from object to be created 2016-10-04 15:12:50 +02:00
5ffb1edfbc hide breadcrumbs in print 2016-08-30 08:19:07 +02:00
hplattner
214fb2dc0f Merge branch 'bbmaster2' of http://git.cy55.de/loops into bbmaster2 2016-08-25 12:17:21 +02:00
hplattner
df3ae0179b add canonicalURl to Layout TextView 2016-08-25 12:17:14 +02:00
0dc20ea52f allow more control for NodeView subclasses, e.g. login view 2016-07-19 12:17:32 +02:00
3b11a72a81 avoid loading all subchildren in check for nested children 2016-07-17 10:13:21 +02:00
2720b0444c use workspace assignment also for 'create form' action 2016-07-14 09:52:25 +02:00
hplattner
040997b322 use options to determin x robots tag 2016-07-13 16:06:58 +02:00
hplattner
4fb3fa54ec add x robots tag to resources 2016-07-13 15:02:17 +02:00
949a543aa9 set state of survey data when saving 2016-06-28 08:14:58 +02:00
597a8d48ef add missing definition of requestUrl property 2016-06-24 07:56:57 +02:00
65ad427413 use absolute URLs for login and selfservice registration 2016-06-16 15:38:03 +02:00
9bf69e88b3 avoid duplication of data if context is cyclically assigned to itself 2016-06-16 13:51:20 +02:00
7cb0cf0d95 re-activate 'request/URL' on management page 2016-06-12 09:03:28 +02:00
d4e22b7f79 revert request/URL stuff for management views 2016-06-09 12:20:44 +02:00
9e9390cff3 use new 'view/requestUrl' to avoid '/@@index.html' in URLs 2016-06-09 10:22:30 +02:00
a8f70fd498 move new 'view/requestUrl' to cybertools/browser 2016-06-09 09:01:20 +02:00
17eafd7895 provide 'view/requestUrl' method to be used instead of 'request/URL' 2016-06-09 08:30:19 +02:00
97e7537c4c link consistency: limit URL tweaking to 'page' nodes 2016-06-08 10:34:28 +02:00
4ddddc0933 link consistency: take URL from menu item if target is assigned to one 2016-06-07 20:42:41 +02:00
01ab785c0d include 'done' work items in plan report 2016-06-05 15:59:22 +02:00
85dfd815dc make sure indirectly called reports (like CSV export) use the correct report name 2016-05-13 08:20:22 +02:00
428c772ea9 provide new report for planned work 2016-05-12 15:44:09 +02:00
6c21581c7e fix loop() function; make root folder globally accessible 2016-05-05 08:16:46 +02:00
b14e59e29c fix type 2016-05-02 09:53:12 +02:00
fa1170444d add paster shell utilities and repair base modules 2016-05-02 09:45:56 +02:00
e27556b98b provide additional helper function to get really all question groups 2016-04-28 10:37:27 +02:00
6c6c0791fa update existing survey data without overwriting them 2016-04-25 18:38:47 +02:00
dda87b0e79 profide method for loading a range of response tracks by wildcard query 2016-04-25 12:28:29 +02:00
b0139ccb13 Use target URL for tabs (better than request.URL) 2016-04-25 09:37:19 +02:00
89863c9fde avoid errors when directly opening report concept 2016-04-24 17:22:28 +02:00
22d1d44560 dynamically create tabs on concept views via options 2016-04-24 10:49:58 +02:00
665c0271fd (1) provide ILocation adapter for WSI; (2) activate security handlers.
(1) ILocation adapter seems to be required by absolute_url in
BlueBream.

(2) security-related event handlers had been commented out in
bbmaster for performance reasons; are now re-activated.
2016-04-17 14:05:59 +02:00
f12361daa4 delegate action: set date, start to current, clear other fields 2016-04-17 10:00:18 +02:00
635a4e2568 show candidates list for task only if there are any 2016-04-16 08:57:23 +02:00
8e9b4267f2 remove obsolete security declaration 2016-04-13 19:49:14 +02:00
1d84f5e509 use correct base interface for avoiding ForbiddenAttribute errors 2016-04-13 19:43:31 +02:00
8eab09cd44 revert previous change (was other error in cyberapps.knowledge) 2016-04-07 09:36:22 +02:00
f5731c1e3f avoid error because of deleted data 2016-04-07 09:17:14 +02:00
5f8ed47165 add person-based questionnaire
control team- and person-based questionnaires via questionnaireType;
person-based questionnaire: refer to (answer questions for) other person.
2016-04-07 08:24:27 +02:00
5b2b28da19 CSV export: store duration/effort as minutes 2016-03-29 18:17:44 +02:00
608b75f1c8 table-based vocabularies: avoid error when used directly with a Concept as instance 2016-03-29 18:09:06 +02:00
689bd983bc define default headTitle in BaseView 2016-03-28 11:37:18 +02:00
acef9b683e gracefully keep favorites on top that have been promoted manually 2016-03-24 17:11:52 +01:00
1d7f01dccb allow re-ordering of favorites by drag-and-drop 2016-03-24 16:24:45 +01:00
841eb204eb edit work item: reset times on work, finish 2016-03-24 16:24:21 +01:00
17f15429c8 add activity field to report, is also used as query field with vocabulary 2016-03-18 15:57:26 +01:00
354dfeda7f Merge branch 'master' into bbmaster2 2016-03-17 16:15:23 +01:00
db4f7d15a9 move JS generation to Python; reset date/time when selecting 'finish' in state 'done' 2016-03-17 15:24:09 +01:00
hplattner
e2c1e67843 check headTitle property in targetview to generate headTitle 2016-03-17 14:11:00 +01:00
b87dee5c35 workstatement report: the default for start date (day from) can now be set via type option 'workitem_dayfrom_default' 2016-02-21 11:52:29 +01:00
711488a412 populate dayFrom field with a sensible default value; use form action for providing CSV export in order respect query input 2016-02-13 22:19:57 +01:00
f4a5b78700 CSV export: format duration and effort fields correctly 2016-01-26 13:32:04 +01:00
7d979a5749 make default work item action configurable via option 'organize.work.default_actions' 2016-01-24 11:04:10 +01:00
396d4727dc provide a collection of sub-parts of a page that may be placed separately on the page 2016-01-17 14:16:24 +01:00
cd65a3f7b5 improve reporting CSS, person work statement report 2016-01-10 18:23:04 +01:00
e272675498 new report 'person work statement', + minor improvements for generic concept-based reports 2016-01-09 17:34:47 +01:00
4051c2f9a5 fix report template 2016-01-09 12:03:02 +01:00
b05c9c5851 allow setting of main template via controller (which is found via skin) 2016-01-09 12:02:14 +01:00
8080342620 don't try to get a view for an object the user does not have access to 2015-12-01 20:47:26 +01:00
f1b7e01cbc avoid error because of missing renderer macro 2015-11-25 11:17:30 +01:00
3990b710c6 avoid prindipal IDs with special characters, skipping existing ones if necessary 2015-10-25 10:00:26 +01:00
1800fe7c9e merge branch master 2015-10-10 11:48:52 +02:00
2c512a1ff7 make indexing more fault-tolerant 2015-10-10 11:46:54 +02:00
c7c98b12db revert doc test changes because of reverted change in cybertools.composer 2015-10-10 11:28:50 +02:00
11f859b71e provide BCC to sender, controlled by class variable 2015-10-10 11:20:02 +02:00
b70b522137 work item list: show description of task as link title; fix docttests: changed handling of field defaults 2015-10-10 10:38:31 +02:00
6db71366c6 send link by mail: translations, allow specifying mail subject in global or type option 2015-09-22 07:59:09 +02:00
4f481ce79b handle adding or removal of columns correctly 2015-09-22 07:25:06 +02:00
773ad4e1eb add commits during processing 2015-09-06 15:40:39 +02:00
bd1f12ffa0 add logging to find hot spots for tuning 2015-09-06 15:08:33 +02:00
f64b60f3b5 Merge branch 'bbmaster' into bbmaster2 2015-08-29 11:13:05 +02:00
07bb68ae9d merge branch master 2015-08-29 11:12:58 +02:00
2ae8a60b18 Merge branch 'bbmaster' of ssh://git.cy55.de/home/git/loops into bbmaster 2015-08-29 11:08:21 +02:00
b8f485d2d3 allow usage of part of mail form and make it somewhat configurable 2015-08-17 10:17:56 +02:00
0cec511a9f take personId into account in all relevant places 2015-08-13 16:12:53 +02:00
f2cf265d0c handle boolean values 2015-08-13 16:12:10 +02:00
48caf96670 allow name-based linking e.g. via .loops/resources/... 2015-08-13 16:11:53 +02:00
48e45941b8 provide import file with some stuff missing in older loops sites 2015-08-13 16:10:48 +02:00
fc81b49d44 fix typo (additional email adresses) in send mail action 2015-08-04 12:50:11 +02:00
9df059e77a avoid indexing error because of corrupt files 2015-08-04 12:49:10 +02:00
9587a86f32 avoid error on catalog reindex because of missing or corrupt ZIP files 2015-07-27 10:17:28 +02:00
a45bf52fe7 check for result of update() function 2015-07-25 13:51:01 +02:00
085192de21 put event handling before real deletion 2015-07-25 13:41:41 +02:00
bdb10523a4 allow suppression of page output via no_show_page request parameter 2015-07-25 13:32:46 +02:00
11123b16fa more intermediate commits + logging 2015-07-25 13:23:10 +02:00
0ec131e1a8 fix count-based commit; use set for collection of found objects 2015-07-25 11:27:55 +02:00
08d8993e5a provide commits and logging during update of external collection 2015-07-25 11:16:26 +02:00
69e529ce39 log before deleting to be able to detect errors when deleting 2015-07-25 10:37:57 +02:00
e308b8b34f add event handling to object removal and log event 2015-07-25 09:45:12 +02:00
bbd87c5707 fix typo 2015-07-24 11:18:00 +02:00
d834ec2e16 allow selection of question groups by person also in ungrouped presentation 2015-07-20 08:28:45 +02:00
8256a4efea allow selection of question groups via personId 2015-07-16 20:01:34 +02:00
146b1c78aa allow control of question group selection in subclass; provide principal/person also if request is not given 2015-07-16 15:58:33 +02:00
dbc91c7e6f allow easy retrieval of more than one questionnaire per question group 2015-06-29 16:17:57 +02:00
hplattner
9b74079bca url encoding fix 2015-06-25 16:21:22 +02:00
e56b0c9863 show views for loops container and view manager only for site managers 2015-06-23 14:52:47 +02:00
5fd52269af no version number when object is not versioned 2015-06-15 12:49:00 +02:00
70e53daadf send email form: only accessible for logged-in users 2015-06-15 07:33:27 +02:00
3c1a5ccdf4 merge branch master 2015-06-12 08:35:07 +02:00
5bf2906c51 avoid error when no report is found 2015-06-12 07:20:01 +02:00
a3716050e9 confirm resetting all response data entered 2015-06-06 09:53:55 +02:00
acc104cf95 directly reference questionnaire as question may be assigned to more than one questionnaire 2015-06-05 13:54:35 +02:00
2e76910134 make sure management views of records/tracks can only accessed by managers 2015-06-01 08:39:46 +02:00
a97ea1b5d1 allow fine-tuning of answer option header cells via colspan and CSS class 2015-05-28 15:01:27 +02:00
4833ebd54b fix setting of date and time fields on 'start' action 2015-05-21 08:17:02 +02:00
4c13744a0c set date/time fields correctly when selecting 'start' action 2015-05-18 10:54:58 +02:00
b34b5705e0 correctly position div element for headline and body of concept subpage 2015-05-14 14:57:45 +02:00
77bf66996a additional check for restricted editing permission 2015-05-06 10:29:25 +02:00
0555329e86 provide slot on download page for customization 2015-04-27 10:26:04 +02:00
070cb50a54 show combo box only for new work items not when editing existing ones 2015-04-26 15:44:32 +02:00
aef3d2e139 change translation for 'activity' 2015-04-26 13:23:15 +02:00
c970507c85 provide selectable standard texts for work items via data table 'organize.work.texts' 2015-04-26 13:16:33 +02:00
9c71572be8 handle strange case when user account references a person that has been deleted 2015-04-24 10:08:25 +02:00
a02ccde5ff remove group headers for preferences questionnaire 2015-04-23 19:13:28 +02:00
86421f8cc4 add evaluation for preferences questionnaire 2015-04-23 14:24:39 +02:00
144e6e5412 preferences questionnaire basically working 2015-04-23 14:03:12 +02:00
38d74bcb21 provide column for standard deviation on single question report - but keep inactive at the moment 2015-04-21 20:27:30 +02:00
ae789e117b take reversed polarity into account, normalize to 100% 2015-04-21 13:42:48 +02:00
f935a0efb7 single question report basically working 2015-04-21 12:42:18 +02:00
2ea0689a05 make team reports callable independent of filling a questionnaire 2015-04-21 10:06:28 +02:00
57aee009b6 store response data of team-based surveys under person + institution key 2015-04-21 07:40:49 +02:00
328c1fbaaf provide button for saving data without trying an evaluation 2015-04-20 10:30:49 +02:00
5d4a74c528 allow suppression for listing all institutions for workgroup admin, e.g. for surveys 2015-04-20 10:12:29 +02:00
adbdb2840b provide institution selection for team-based surveys 2015-04-19 15:16:49 +02:00
09b75367a7 more control on propagation of role permissions via option 2015-04-19 09:28:30 +02:00
2c548a3df6 revert some changes; provide optional logging of acquired security settings 2015-04-18 15:40:43 +02:00
8d66ee3830 allow blocking of security acquisition by option; try to keep current role permission setting on object if any 2015-04-18 13:43:44 +02:00
c3b9e1b665 show column description also in tooltip of header 2015-04-18 09:42:40 +02:00
7a0ddb1e0d show column description also in tooltip of header 2015-04-18 09:41:25 +02:00
0936181bf6 avoid error because of missing description field 2015-04-18 09:33:21 +02:00
67fe55056f show resources (other than text) in book_topic_view 2015-04-17 11:17:37 +02:00
05b13e154f fix check for show_in_breadcrumbs option 2015-04-17 10:50:21 +02:00
58b4db66cc work in progress: handling of question types 2015-04-17 09:14:33 +02:00
3470750fc7 use separate field for questionnaire header text 2015-04-17 07:13:54 +02:00
0e28cefc2d use setting on type for getting container for object creation 2015-04-17 06:56:47 +02:00
e871eafcaf avoid error because of old favorite tems 2015-04-16 16:54:35 +02:00
7ba0bec7f7 allow overriding of version view class by controller 2015-04-16 08:02:38 +02:00
6a96d72b22 allow specification of default resource type via global option 2015-04-14 16:23:47 +02:00
2567347f62 add utility method for getting current date e.g. for input fields 2015-04-13 10:49:46 +02:00
c49f87aef5 remove permission restriction from configure.zcml - is checked in form class 2015-04-12 09:01:22 +02:00
396e17c0dc allow entering of survey data for other person; prerequisite: keep URL params in title and breadcrumbs links 2015-04-05 10:40:07 +02:00
60604cd38b allow overriding of default resource relation views and sorting 2015-03-30 13:47:04 +02:00
c06785b98b avoid error when no task is assigned 2015-03-30 09:10:13 +02:00
0bac3ecc13 do not show ugly lines below minor headlines 2015-03-29 15:23:56 +02:00
e70bbb941b move institution selection macro from cyberapps.knowledge to more central place in loops 2015-03-28 09:48:42 +01:00
335217081c base functionality for storing selected institution of user/person in favorites 2015-03-25 17:39:51 +01:00
a3e0cac006 store sort infos (by task/concept) in user's favorite data 2015-03-25 15:09:52 +01:00
9c09222762 strip leading spaces from title for sort criteria 2015-03-25 08:01:13 +01:00
3bbda09f84 improvements of column-based sorting 2015-03-24 20:05:27 +01:00
7113369fca breadcrumbs improvements 2015-03-23 13:49:53 +01:00
0316bafffc add title to sortable columns; + title on sort link 2015-03-22 07:10:16 +01:00
d317612187 sorting by link on column header on reporting results 2015-03-21 17:12:16 +01:00
ef61a836a4 allow viewing report 'as such' (without results, but children) 2015-03-21 10:42:25 +01:00
4c449d5dc4 fix start action: only for work; repair IFTA qualification work items: done -> running 2015-03-21 09:18:53 +01:00
82645fb574 consequently use reporting view base class instead of specialized class; + other improvements 2015-03-20 11:38:47 +01:00
74eb054bc9 filter qualification reports by contact state 2015-03-20 08:03:14 +01:00
4a67756cf8 fix wrong closing div tag 2015-03-18 20:50:47 +01:00
81dde67338 provide party state field for use in work and qualification reports 2015-03-18 07:34:31 +01:00
f6f266acb5 make encoding configurable 2015-03-17 10:08:20 +01:00
a565d750e3 don't show priority and activity if corresponding data tables do not exist; translations 2015-03-17 08:50:53 +01:00
c824c0bdb5 provide new 'contact' states definition to be used for persons or other kinds of parties 2015-03-16 16:31:52 +01:00
c4ce28e239 add utility property and function for accessing data table data as records/rows 2015-03-16 12:10:34 +01:00
beff6ef2dd new fields: priority, activity 2015-03-16 11:17:38 +01:00
2c112703a3 optionally allow time part for deadline 2015-03-14 11:41:10 +01:00
d05b3cd544 CSV download improvements: button, file name, ... 2015-03-13 15:14:29 +01:00
e16f3e5ccf provide CSV download view for reports; use for work and qualification listings 2015-03-13 14:27:30 +01:00
1716990d26 avoid error because of missing field on answer options 2015-03-10 09:12:22 +01:00
bc0949842e provide utility view for fixing person roles (set to 'loops.Person') 2015-03-07 09:40:17 +01:00
1cec746423 two type option for restricting qualifier 'assign' settings 2015-03-02 13:36:22 +01:00
a13f2218db provide type option for using resource title for download filename 2015-02-12 12:09:54 +01:00
075badc55a make delete permission configurable via global option 2015-01-15 10:11:57 +01:00
0ca62cbe91 tolerate missing (probably deleted) task when editing work item 2014-11-30 13:24:32 +01:00
f3d595a974 provide 'noprint' CSS class that exludes elements from print output 2014-11-28 16:28:28 +01:00
b1df34621e fix: add missing import 2014-11-24 10:45:04 +01:00
bd85bfbcdc allow re-open (retract to draft state) of active task 2014-11-17 09:08:45 +01:00
40d368602d fix state check in search 2014-11-05 08:37:18 +01:00
53d9a5b3a4 store salutation and academic title upon registration; do not show these fields in self-service registration 2014-11-02 14:10:02 +01:00
5c0d46193f provide separate function for generating object names from title 2014-10-27 14:45:01 +01:00
7e975dadfb give portlet background color in order to hide too wide content regions (tables) 2014-10-27 14:44:30 +01:00
71cdde4308 avoid erroneous use of target view 2014-09-09 17:32:54 +02:00
7e9a68bde1 allow specification of columns for data table in type object 2014-08-27 10:53:04 +02:00
e5a2cfafa7 remove messages moved to cyberapps.knowledge 2014-08-27 10:49:33 +02:00
93dcb301f9 move new knowledge.qualification stuff to cyberapps 2014-08-08 08:33:35 +02:00
f0248b2ec8 work in progress: job positions and competences 2014-07-31 18:45:07 +02:00
11a7546506 avoid error because of deleted task 2014-07-17 16:29:18 +02:00
58b43637d1 avoid error because of deleted task 2014-07-17 11:34:44 +02:00
c3922747bb fix work item form: missing div end tag 2014-07-12 16:25:08 +02:00
e5b4e9800e report view variant to be used as a part of a compound view 2014-07-12 08:42:41 +02:00
d5c851a5de add qualification report for person; sorting; add action 'start' to work item type 'checkup' 2014-07-11 11:23:06 +02:00
50b45b2c56 provide report for listing qualifications for a competence 2014-07-11 08:38:31 +02:00
e675a95871 have a fixed batch size of 12 2014-07-08 16:27:06 +02:00
527c2f445d include fields for start and end dates on work statement report 2014-07-08 07:59:28 +02:00
e20feb5851 allow end date different from start date for appropriate work item types 2014-07-07 14:20:03 +02:00
1a8550995b allow editing of workitems on report 2014-07-07 12:41:58 +02:00
6dde146189 provide a 'password reset' form to change/set a forgotten password 2014-06-29 14:08:14 +02:00
2f74920692 survey CSV export: fix encoding, add institution column 2014-06-17 17:14:17 +02:00
17af65c461 allow editing of track data in management interface 2014-06-17 14:40:33 +02:00
ed996977f9 highlight missing questions; show team size 2014-06-04 08:57:34 +02:00
95a510c759 allow for presentation of questions without grouping by question groups 2014-06-03 18:14:57 +02:00
391bc395f0 fix printer settings 2014-06-01 11:06:13 +02:00
0c043434e2 take mouseover title from answer option description 2014-05-29 11:38:41 +02:00
3a6d6a7ddd make answer options configurable 2014-05-29 11:33:35 +02:00
5f81ecfed2 handle deleted tasks gracefully 2014-05-26 10:17:39 +02:00
49b7ef0382 top vertical alignment for table cells 2014-05-25 12:29:27 +02:00
61484aea70 top vertical alignment for table cells 2014-05-25 12:26:55 +02:00
cb2ac10524 specify columns of feedback table on questionnaire edit form 2014-05-25 10:37:43 +02:00
6ff8e8211a refactor result data structure, fix rank calculation, add team rank 2014-05-24 14:49:52 +02:00
11f3218ea4 allow for hiding of resources from listing via state; suppress this state checking for admin 2014-05-17 08:43:40 +02:00
ba0fc064d0 allow content manager to edit work items 2014-05-17 08:12:16 +02:00
30a7d430d7 fix title for user work items: use query title 2014-05-14 09:31:58 +02:00
33687724bd use precalculated scores per question group for team result 2014-05-08 13:25:04 +02:00
f9bce78d81 provide translations 2014-05-07 09:03:40 +02:00
a306e11cec allow actions also in previous work items of a run 2014-05-03 10:45:41 +02:00
66bf217ee8 provide team evaluation of survey 2014-05-02 14:45:58 +02:00
2fc7709c56 add boolean fields for controlling feedback/evaluation variations 2014-04-29 17:06:19 +02:00
0dee3c28aa provide report and view for all qualifications 2014-04-29 10:36:22 +02:00
21ac74f764 add 'delete' action to comments 2014-04-27 22:38:51 +02:00
8ab637c402 allow for additional access control (without acquisition/inheritance) on queries and types via 'access_permission' option 2014-04-26 15:52:28 +02:00
fab93d8ceb work in progress: qualification overview 2014-04-25 13:28:19 +02:00
2a532dba86 descending sort order on time stamp 2014-04-19 11:46:19 +02:00
2d2240244e provide a comments overview listing, e.g. for moderation of comments 2014-04-19 10:48:45 +02:00
f34dc4a59c show comment state only if corresponding option is set 2014-04-14 19:08:21 +02:00
6a91788d9e no processing if file does not exist (yet), thus avoiding erroneous value for documentPropertiesAccessible attribute 2014-04-14 19:05:40 +02:00
a47b6a02a0 provide state icon with link to state transition form for comments 2014-04-09 13:07:39 +02:00
61cfff0f91 show comment state for admin user 2014-04-09 10:37:23 +02:00
e0f30d7a96 handle office files that are no valid ZIP file (e.g. because of password protection) 2014-03-18 08:04:46 +01:00
8ce1eb7222 comment system extensions: make comments stateful as basis for moderated comments 2014-02-28 17:11:22 +01:00
6e901de066 allow anonymous posters 2014-02-11 11:44:21 +01:00
f8849ee393 allow restriction of comments to certain types via type option 'organize.allowcomments' 2014-02-11 10:47:14 +01:00
8495eea8ad avoid retrieving a list of all concepts for filtering select widget 2013-12-17 09:43:25 +01:00
563 changed files with 7476 additions and 5594 deletions

7
.gitignore vendored
View file

@ -1,7 +1,14 @@
*.pyc
*.pyo
*.swp
dist/
var/
*.egg-info
*.project
*.pydevproject
*.ropeproject
*.sublime-project
*.sublime-workspace
.env
.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

11
MANIFEST.in Normal file
View file

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

7
README.md Normal file
View file

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

View file

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

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)

View file

@ -1,18 +0,0 @@
<configure
xmlns:zope="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"
i18n_domain="loops">
<zope:adapter
factory="loops.knowledge.qualification.base.Competence"
trusted="True" />
<zope:class class="loops.knowledge.qualification.base.Competence">
<require permission="zope.View"
interface="loops.knowledge.qualification.interfaces.ICompetence" />
<require permission="zope.ManageContent"
set_schema="loops.knowledge.qualification.interfaces.ICompetence" />
</zope:class>
<!-- views -->
</configure>

View file

@ -1,179 +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
#
"""
Definition of view classes and other browser related stuff for
surveys and self-assessments.
"""
import csv
from cStringIO import StringIO
from zope.app.pagetemplate import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy
from zope.i18n import translate
from cybertools.knowledge.survey.questionnaire import Response
from cybertools.util.date import formatTimeStamp
from loops.browser.concept import ConceptView
from loops.browser.node import NodeView
from loops.common import adapted
from loops.knowledge.survey.response import Responses
from loops.organize.party import getPersonForUser
from loops.util import getObjectForUid
from loops.util import _
template = ViewPageTemplateFile('view_macros.pt')
class SurveyView(ConceptView):
data = None
errors = None
@Lazy
def macro(self):
self.registerDojo()
return template.macros['survey']
@Lazy
def tabview(self):
if self.editable:
return 'index.html'
def results(self):
result = []
response = None
form = self.request.form
if 'submit' in form:
self.data = {}
response = Response(self.adapted, None)
for key, value in form.items():
if key.startswith('question_'):
uid = key[len('question_'):]
question = adapted(self.getObjectForUid(uid))
if value != 'none':
value = int(value)
self.data[uid] = value
response.values[question] = value
Responses(self.context).save(self.data)
self.errors = self.check(response)
if self.errors:
return []
if response is not None:
result = response.getGroupedResult()
return [dict(category=r[0].title, text=r[1].text,
score=int(round(r[2] * 100)))
for r in result]
def check(self, response):
errors = []
values = response.values
for qu in self.adapted.questions:
if qu.required and qu not in values:
errors.append('Please answer the obligatory questions.')
break
qugroups = {}
for qugroup in self.adapted.questionGroups:
qugroups[qugroup] = 0
for qu in values:
qugroups[qu.questionGroup] += 1
for qugroup, count in qugroups.items():
minAnswers = qugroup.minAnswers
if minAnswers in (u'', None):
minAnswers = len(qugroup.questions)
if count < minAnswers:
errors.append('Please answer the minimum number of questions.')
break
return errors
def getInfoText(self, qugroup):
lang = self.languageInfo.language
text = qugroup.description
info = None
if qugroup.minAnswers in (u'', None):
info = translate(_(u'Please answer all questions.'), target_language=lang)
elif qugroup.minAnswers > 0:
info = translate(_(u'Please answer at least $minAnswers questions.',
mapping=dict(minAnswers=qugroup.minAnswers)),
target_language=lang)
if info:
text = u'<i>%s</i><br />(%s)' % (text, info)
return text
def getValues(self, question):
setting = None
if self.data is None:
self.data = Responses(self.context).load()
if self.data:
setting = self.data.get(question.uid)
noAnswer = [dict(value='none', checked=(setting == None),
radio=(not question.required))]
return noAnswer + [dict(value=i, checked=(setting == i), radio=True)
for i in reversed(range(question.answerRange))]
class SurveyCsvExport(NodeView):
encoding = 'ISO8859-15'
def encode(self, text):
text.encode(self.encoding)
@Lazy
def questions(self):
result = []
for idx1, qug in enumerate(adapted(self.virtualTargetObject).questionGroups):
for idx2, qu in enumerate(qug.questions):
result.append((idx1, idx2, qug, qu))
return result
@Lazy
def columns(self):
infoCols = ['Name', 'Timestamp']
dataCols = ['%02i-%02i' % (item[0], item[1]) for item in self.questions]
return infoCols + dataCols
def getRows(self):
for tr in Responses(self.virtualTargetObject).getAllTracks():
p = adapted(getObjectForUid(tr.userName))
name = p and p.title or u'???'
ts = formatTimeStamp(tr.timeStamp)
cells = [tr.data.get(qu.uid, -1)
for (idx1, idx2, qug, qu) in self.questions]
yield [name, ts] + cells
def __call__(self):
f = StringIO()
writer = csv.writer(f, delimiter=',')
writer.writerow(self.columns)
for row in self.getRows():
writer.writerow(row)
text = f.getvalue()
self.setDownloadHeader(text)
return text
def setDownloadHeader(self, text):
response = self.request.response
filename = 'survey_data.csv'
response.setHeader('Content-Disposition',
'attachment; filename=%s' % filename)
response.setHeader('Cache-Control', '')
response.setHeader('Pragma', '')
response.setHeader('Content-Length', len(text))
response.setHeader('Content-Type', 'text/csv')

View file

@ -1,100 +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
#
"""
Interfaces for surveys used in knowledge management.
"""
from zope.interface import Interface, Attribute
from zope import interface, component, schema
from cybertools.knowledge.survey import interfaces
from loops.interfaces import IConceptSchema, ILoopsAdapter
from loops.util import _
class IQuestionnaire(IConceptSchema, interfaces.IQuestionnaire):
""" A collection of questions for setting up a survey.
"""
defaultAnswerRange = schema.Int(
title=_(u'Answer Range'),
description=_(u'Number of items (answer options) to select from.'),
default=4,
required=True)
feedbackHeader = schema.Text(
title=_(u'Feedback Header'),
description=_(u'Text that will appear at the top of the feedback page.'),
default=u'',
missing_value=u'',
required=False)
feedbackFooter = schema.Text(
title=_(u'Feedback Footer'),
description=_(u'Text that will appear at the end of the feedback page.'),
default=u'',
missing_value=u'',
required=False)
class IQuestionGroup(IConceptSchema, interfaces.IQuestionGroup):
""" A group of questions within a questionnaire.
"""
minAnswers = schema.Int(
title=_(u'Minimum Number of Answers'),
description=_(u'Minumum number of questions that have to be answered. '
'Empty means all questions have to be answered.'),
default=None,
required=False)
class IQuestion(IConceptSchema, interfaces.IQuestion):
""" A single question within a questionnaire.
"""
required = schema.Bool(
title=_(u'Required'),
description=_(u'Question must be answered.'),
default=False,
required=False)
revertAnswerOptions = schema.Bool(
title=_(u'Negative'),
description=_(u'Value inversion: High selection means low value.'),
default=False,
required=False)
class IFeedbackItem(IConceptSchema, interfaces.IFeedbackItem):
""" Some text (e.g. a recommendation) or some other kind of information
that may be deduced from the res)ponses to a questionnaire.
"""
class IResponse(interfaces.IResponse):
""" A set of response values given to the questions of a questionnaire
by a single person or party.
"""
class IResponses(Interface):
""" A container or manager of survey responses.
"""

View file

@ -1,62 +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
#
"""
Handling survey responses.
"""
from zope.component import adapts
from zope.interface import implements
from cybertools.tracking.btree import Track
from cybertools.tracking.interfaces import ITrackingStorage
from loops.knowledge.survey.interfaces import IResponse, IResponses
from loops.organize.tracking.base import BaseRecordManager
class Responses(BaseRecordManager):
implements(IResponses)
storageName = 'survey_responses'
def __init__(self, context):
self.context = context
def save(self, data):
if self.personId:
self.storage.saveUserTrack(self.uid, 0, self.personId, data,
update=True, overwrite=True)
def load(self):
if self.personId:
tracks = self.storage.getUserTracks(self.uid, 0, self.personId)
if tracks:
return tracks[0].data
return {}
def getAllTracks(self):
return self.storage.query(taskId=self.uid)
class Response(Track):
implements(IResponse)
typeName = 'Response'

View file

@ -1,100 +0,0 @@
<!-- ZPT macros for loops.knowledge.survey views -->
<html i18n:domain="loops">
<metal:block define-macro="survey"
tal:define="feedback item/results;
errors item/errors">
<metal:title use-macro="item/conceptMacros/concepttitle_only" />
<tal:description condition="not:feedback">
<metal:title use-macro="item/conceptMacros/conceptdescription" />
</tal:description>
<div tal:condition="feedback">
<h3 i18n:translate="">Feedback</h3>
<div tal:define="header item/adapted/feedbackHeader"
tal:condition="header"
tal:content="structure python:item.renderText(header, 'text/restructured')" />
<table class="listing">
<tr>
<th i18n:translate="">Category</th>
<th i18n:translate="">Response</th>
<th i18n:translate="">%</th>
</tr>
<tr tal:repeat="fbitem feedback">
<td tal:content="fbitem/category" />
<td tal:content="fbitem/text" />
<td tal:content="fbitem/score" />
</tr>
</table>
<div class="button" id="show_questionnaire">
<a href="" onclick="back(); return false"
i18n:translate="">
Back to Questionnaire</a>
<br />
</div>
<div tal:define="footer item/adapted/feedbackFooter"
tal:condition="footer"
tal:content="structure python:item.renderText(footer, 'text/restructured')" />
</div>
<div id="questionnaire"
tal:condition="not:feedback">
<h3 i18n:translate="">Questionnaire</h3>
<div class="error"
tal:condition="errors">
<div tal:repeat="error errors">
<span i18n:translate=""
tal:content="error" />
</div>
</div>
<form method="post">
<table class="listing">
<tal:qugroup repeat="qugroup item/adapted/questionGroups">
<tr><td colspan="6">&nbsp;</td></tr>
<tr class="vpad">
<td tal:define="infoText python:item.getInfoText(qugroup)">
<b tal:content="qugroup/title" />
<div class="infotext"
tal:condition="infoText">
<span tal:content="structure infoText" />
</div>
</td>
<td style="text-align: center"
i18n:translate="">No answer</td>
<td colspan="2"
i18n:translate="">Fully applies</td>
<td colspan="2"
style="text-align: right"
i18n:translate="">Does not apply</td>
</tr>
<tr class="vpad"
tal:repeat="question qugroup/questions">
<td tal:content="question/text" />
<td style="white-space: nowrap; text-align: center"
tal:repeat="value python:item.getValues(question)">
<input type="radio"
i18n:attributes="title"
tal:condition="value/radio"
tal:attributes="
name string:question_${question/uid};
value value/value;
checked value/checked;
title string:survey_value_${value/value}" />
<span tal:condition="not:value/radio"
title="Obligatory question, must be answered"
i18n:attributes="title">***
</span>
</td>
</tr>
</tal:qugroup>
</table>
<input type="submit" name="submit" value="Evaluate Questionnaire"
i18n:attributes="value" />
<input type="button" name="reset_responses" value="Reset Responses Entered"
i18n:attributes="value"
onclick="setRadioButtons('none'); return false" />
</form>
</div>
</metal:block>
</html>

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

17
loops/__init__.py Normal file
View file

@ -0,0 +1,17 @@
# package loops
# intid monkey patch for avoiding ForbiddenAttribute error
from zope import component
from zope.intid.interfaces import IIntIds
from zope import intid
from zope.security.proxy import removeSecurityProxy
def queryId(self, ob, default=None):
try:
return self.getId(removeSecurityProxy(ob))
except KeyError:
return default
intid.IntIds.queryId = queryId

View file

@ -1,32 +1,13 @@
# -*- coding: UTF-8 -*-
# -*- Mode: Python; py-indent-offset: 4 -*-
#
# Copyright (c) 2019 Helmut Merz helmutm@cy55.de
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# loops.base
"""
The loops container class.
""" Implementation of loops root object.
"""
from zope.app.container.btree import BTreeContainer
from zope.app.folder.folder import Folder
from zope.app.folder.interfaces import IFolder
from zope.container.btree import BTreeContainer
from zope.site.folder import Folder
from zope.site.interfaces import IFolder
from zope.traversing.api import getPath, traverse
from zope.interface import implements
from zope.interface import implementer
from cybertools.util.jeep import Jeep
from loops.interfaces import ILoops
@ -34,17 +15,8 @@ from loops.interfaces import ILoops
loopsPrefix = '.loops'
@implementer(ILoops)
class Loops(Folder):
#class Loops(BTreeContainer):
implements(ILoops)
#def getSiteManager(self):
# return self.__parent__.getSiteManager()
#@property
#def _SampleContainer__data(self):
# return self.data
_skinName = ''
def getSkinName(self): return self._skinName
@ -74,10 +46,7 @@ class Loops(Folder):
return self.get('records')
def getLoopsUri(self, obj):
#return str(loopsPrefix + getPath(obj)[len(getPath(self)):])
uri = loopsPrefix + getPath(obj)[len(getPath(self)):]
#if isinstance(uri, unicode):
# uri = uri.encode('UTF-8')
return uri
def loopsTraverse(self, uri):

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 @@
#
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# loops.browser.action
"""
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.app.pagetemplate import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy
@ -92,6 +75,8 @@ class DialogAction(Action):
urlParams['fixed_type'] = 'yes'
if self.viewTitle:
urlParams['view_title'] = self.viewTitle
#for k, v in self.page.sortInfo.items():
# urlParams['sortinfo_' + k] = v['fparam']
urlParams.update(self.addParams)
if self.target is not None:
url = self.page.getUrlForTarget(self.target)

View file

@ -1,35 +1,18 @@
#
# Copyright (c) 2015 Helmut Merz helmutm@cy55.de
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# loops.browser.auth
"""
Login, logout, unauthorized stuff.
""" Login, logout, unauthorized stuff.
"""
from zope.app.exception.browser.unauthorized import Unauthorized as DefaultUnauth
from zope.app.security.interfaces import IAuthentication
from zope.app.security.interfaces import ILogout, IUnauthenticatedPrincipal
from zope.authentication.interfaces import IAuthentication
from zope.authentication.interfaces import ILogout, IUnauthenticatedPrincipal
from zope.browserpage import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy
from zope import component
from zope.interface import implements
from zope.interface import implementer
from loops.browser.concept import ConceptView
from loops.browser.node import NodeView
from zope.app.pagetemplate import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy
template = ViewPageTemplateFile('auth.pt')
@ -57,9 +40,9 @@ class LoginForm(NodeView):
return self
@implementer(ILogout)
class Logout(object):
implements(ILogout)
def __init__(self, context, request):
self.context = context

View file

@ -1,46 +1,28 @@
#
# Copyright (c) 2016 Helmut Merz helmutm@cy55.de
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# loops.browser.common
"""
Common base class for loops browser view classes.
""" Common base class for loops browser view classes.
"""
from cgi import parse_qs, parse_qsl
#import mimetypes # use more specific assignments from cybertools.text
from datetime import datetime
from datetime import date, datetime
from logging import getLogger
import re
from time import strptime
from urllib import urlencode
from urllib.parse import parse_qs, parse_qsl, urlencode
from zope import component
from zope.app.form.browser.interfaces import ITerms
from zope.app.i18n.interfaces import ITranslationDomain
from zope.app.security.interfaces import IAuthentication, IUnauthenticatedPrincipal
from zope.app.pagetemplate import ViewPageTemplateFile
from zope.app.security.interfaces import IUnauthenticatedPrincipal
from zope.app.security.interfaces import PrincipalLookupError
from zope.authentication.interfaces import IAuthentication, IUnauthenticatedPrincipal
from zope.authentication.interfaces import IUnauthenticatedPrincipal
from zope.authentication.interfaces import PrincipalLookupError
from zope.browser.interfaces import ITerms
from zope.browserpage import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy
from zope.dottedname.resolve import resolve
from zope.dublincore.interfaces import IZopeDublinCore
from zope.formlib import form
from zope.formlib.form import FormFields
from zope.formlib.namedtemplate import NamedTemplate
from zope.interface import Interface, implements
from zope.i18n.interfaces import ITranslationDomain
from zope.interface import Interface, implementer
from zope.proxy import removeAllProxies
from zope.publisher.browser import applySkin
from zope.publisher.interfaces.browser import IBrowserSkinType, IBrowserView
@ -62,17 +44,21 @@ from cybertools.stateful.interfaces import IStateful
from cybertools.text import mimetypes
from cybertools.typology.interfaces import IType, ITypeManager
from cybertools.util.date import toLocalTime
from cybertools.util.format import formatDate
from cybertools.util.jeep import Jeep
from loops.browser.util import normalizeForUrl
from loops.common import adapted, baseObject
from loops.config.base import DummyOptions
from loops.i18n.browser import I18NView
from loops.interfaces import IResource, IView, INode, ITypeConcept
from loops.organize.personal import favorite
from loops.organize.party import getPersonForUser
from loops.organize.tracking import access
from loops.organize.util import getRolesForPrincipal
from loops.resource import Resource
from loops.security.common import checkPermission
from loops.security.common import canAccessObject, canListObject, canWriteObject
from loops.security.common import canEditRestricted
from loops.type import ITypeConcept, LoopsTypeInfo
from loops import util
from loops.util import _, saveRequest
@ -137,7 +123,58 @@ class EditForm(form.EditForm):
return parentUrl + '/contents.html'
class BaseView(GenericView, I18NView):
class SortableMixin(object):
@Lazy
def sortInfo(self):
result = {}
for k, v in self.request.form.items():
if k.startswith('sortinfo_'):
tableName = k[len('sortinfo_'):]
if ',' in v:
fn, dir = v.split(',')
else:
fn = v
dir = 'asc'
result[tableName] = dict(
colName=fn, ascending=(dir=='asc'), fparam=v)
result = favorite.updateSortInfo(getPersonForUser(
self.context, self.request), self.target, result)
return result
def isSortableColumn(self, tableName, colName):
return False # overwrite in subclass
def getSortUrl(self, tableName, colName):
url = str(self.request.URL)
paramChar = '?' in url and '&' or '?'
si = self.sortInfo.get(tableName)
if si is not None and si.get('colName') == colName:
dir = si['ascending'] and 'desc' or 'asc'
else:
dir = 'asc'
return '%s%ssortinfo_%s=%s,%s' % (url, paramChar, tableName, colName, dir)
def getSortParams(self, tableName):
url = str(self.request.URL)
paramChar = '?' in url and '&' or '?'
si = self.sortInfo.get(tableName)
if si is not None:
colName = si['colName']
dir = si['ascending'] and 'asc' or 'desc'
return '%ssortinfo_%s=%s,%s' % (paramChar, tableName, colName, dir)
return ''
def getSortImage(self, tableName, colName):
si = self.sortInfo.get(tableName)
if si is not None and si.get('colName') == colName:
if si['ascending']:
return '/@@/cybertools.icons/arrowdown.gif'
else:
return '/@@/cybertools.icons/arrowup.gif'
class BaseView(GenericView, I18NView, SortableMixin):
actions = {}
portlet_actions = []
@ -146,6 +183,7 @@ class BaseView(GenericView, I18NView):
icon = None
modeName = 'view'
isToplevel = False
isVisible = True
def __init__(self, context, request):
context = baseObject(context)
@ -163,6 +201,10 @@ class BaseView(GenericView, I18NView):
pass
saveRequest(request)
def todayFormatted(self):
return formatDate(date.today(), 'date', 'short',
self.languageInfo.language)
def checkPermissions(self):
return canAccessObject(self.context)
@ -214,6 +256,16 @@ class BaseView(GenericView, I18NView):
result.append(view)
return result
@Lazy
def urlParamString(self):
return self.getUrlParamString()
def getUrlParamString(self):
qs = self.request.get('QUERY_STRING')
if qs:
return '?' + qs
return ''
@Lazy
def principalId(self):
principal = self.request.principal
@ -347,6 +399,10 @@ class BaseView(GenericView, I18NView):
def isPartOfPredicate(self):
return self.conceptManager.get('ispartof')
@Lazy
def queryTargetPredicate(self):
return self.conceptManager.get('querytarget')
@Lazy
def memberPredicate(self):
return self.conceptManager.get('ismember')
@ -395,6 +451,10 @@ class BaseView(GenericView, I18NView):
def description(self):
return self.adapted.description
@Lazy
def tabTitle(self):
return u'Info'
@Lazy
def additionalInfos(self):
return []
@ -747,6 +807,8 @@ class BaseView(GenericView, I18NView):
return result
def checkState(self):
if checkPermission('loops.ManageSite', self.context):
return True
if not self.allStates:
return True
for stf in self.allStates:
@ -821,6 +883,10 @@ class BaseView(GenericView, I18NView):
def canAccessRestricted(self):
return checkPermission('loops.ViewRestricted', self.context)
@Lazy
def canEditRestricted(self):
return canEditRestricted(self.context)
def openEditWindow(self, viewName='edit.html'):
if self.editable:
if checkPermission('loops.ManageSite', self.context):
@ -829,6 +895,7 @@ class BaseView(GenericView, I18NView):
@Lazy
def xeditable(self):
return False
if self.typeOptions('no_external_edit'):
return False
ct = getattr(self.context, 'contentType', '')
@ -943,6 +1010,12 @@ class BaseView(GenericView, I18NView):
jsCall = 'dojo.require("dojox.image.Lightbox");'
self.controller.macros.register('js-execute', jsCall, jsCall=jsCall)
def registerDojoComboBox(self):
self.registerDojo()
jsCall = ('dojo.require("dijit.form.ComboBox");')
self.controller.macros.register('js-execute',
'dojo.require.ComboBox', jsCall=jsCall)
def registerDojoFormAll(self):
self.registerDojo()
self.registerDojoEditor()
@ -996,17 +1069,17 @@ class LoggedIn(object):
params = parse_qsl(qs)
params = [(k, v) for k, v in params if k != 'loops.messages.top:record']
params.append(('loops.messages.top:record', message.encode('UTF-8')))
url = url.encode('utf-8')
return '%s?%s' % (url, urlencode(params))
# vocabulary stuff
@implementer(ITerms)
class SimpleTerms(object):
""" Provide the ITerms interface, e.g. for usage in selection
lists.
"""
implements(ITerms)
def __init__(self, source, request):
# the source parameter is a list of tuples (token, title).
self.source = source
@ -1021,13 +1094,12 @@ class SimpleTerms(object):
return (token, self.terms[token])
@implementer(ITerms)
class LoopsTerms(object):
""" Provide the ITerms interface, e.g. for usage in selection
lists.
"""
implements(ITerms)
def __init__(self, source, request):
# the source parameter is a view or adapter of a real context object:
self.source = source
@ -1049,12 +1121,11 @@ class LoopsTerms(object):
return self.loopsRoot.loopsTraverse(token)
@implementer(ITerms)
class InterfaceTerms(object):
""" Provide the ITerms interface for source list of interfaces.
"""
implements(ITerms)
def __init__(self, source, request):
self.source = source
self.request = request

View file

@ -1,40 +1,22 @@
#
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# loops.browser.concept
"""
Definition of the concept view classes.
""" Definition of the concept view classes.
"""
from itertools import groupby
from zope import interface, component, schema
from zope.app.catalog.interfaces import ICatalog
from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
from zope.app.container.contained import ObjectRemovedEvent
from zope.app.form.browser.interfaces import ITerms
from zope.app.form.interfaces import IDisplayWidget
from zope.app.pagetemplate import ViewPageTemplateFile
from zope.app.security.interfaces import IUnauthenticatedPrincipal
from zope.authentication.interfaces import IUnauthenticatedPrincipal
from zope.browser.interfaces import ITerms
from zope.browserpage import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy
from zope.catalog.interfaces import ICatalog
from zope.container.contained import ObjectRemovedEvent
from zope.dottedname.resolve import resolve
from zope.event import notify
from zope.formlib.form import EditForm, FormFields, setUpEditWidgets
from zope.formlib.interfaces import IDisplayWidget
from zope.formlib.namedtemplate import NamedTemplate
from zope.interface import implements
from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
from zope.publisher.interfaces import BadRequest
from zope.publisher.interfaces.browser import IBrowserRequest
from zope.schema.interfaces import IIterableSource
@ -254,18 +236,35 @@ class ConceptView(BaseView):
result.append(view)
return result
def viewModes(self):
modes = Jeep()
current = self.request.form.get('loops.viewName')
parts = (self.options('view_tabs') or
self.typeOptions('view_tabs') or [])
if not parts:
return modes
activeMode = None
for p in parts:
view = component.queryMultiAdapter(
(self.adapted, self.request), name=p)
if view is None:
view = component.queryMultiAdapter(
(self.context, self.request), name=p)
if view is None:
continue
active = (activeMode is None and p == current)
if active:
activeMode = p
url = '%s?loops.viewName=%s' % (self.targetUrl, p)
modes.append(ViewMode(p, view.tabTitle, url, active))
if activeMode is None:
modes[0].active = True
return modes
@Lazy
def adapted(self):
return adapted(self.context, self.languageInfo)
@Lazy
def title(self):
return self.adapted.title or getName(self.context)
@Lazy
def description(self):
return self.adapted.description
@Lazy
def targetUrl(self):
return self.nodeView.getUrlForTarget(self.context)
@ -282,8 +281,17 @@ class ConceptView(BaseView):
def breadcrumbsTitle(self):
return self.title
@Lazy
def showInBreadcrumbs(self):
return (self.options('show_in_breadcrumbs') or
self.typeOptions('show_in_breadcrumbs'))
@Lazy
def breadcrumbsParent(self):
for p in self.context.getParents([self.defaultPredicate]):
view = self.nodeView.getViewForTarget(p)
if view is not None and view.showInBreadcrumbs:
return view
return None
def getData(self, omit=('title', 'description')):
@ -389,7 +397,8 @@ class ConceptView(BaseView):
children = getChildren
def childrenAlphaGroups(self, predicates=None):
result = Jeep()
#result = Jeep()
result = {}
rels = self.getChildren(predicates=predicates or [self.defaultPredicate],
topLevelOnly=False, sort=False)
rels = sorted(rels, key=lambda r: r.title.lower())
@ -436,7 +445,7 @@ class ConceptView(BaseView):
def parents(self):
rels = sorted(self.context.getParentRelations(),
key=(lambda x: x.first.title and x.first.title.lower()))
key=(lambda x: x.first.title and x.first.title.lower() or ''))
for r in rels:
yield self.childViewFactory(r, self.request)
@ -449,7 +458,7 @@ class ConceptView(BaseView):
if r.order != pos:
r.order = pos
def getResources(self):
def getResources(self, relView=None, sort='default'):
form = self.request.form
#if form.get('loops.viewName') == 'index.html' and self.editable:
if self.editable:
@ -458,13 +467,17 @@ class ConceptView(BaseView):
tokens = form.get('resources_tokens')
if tokens:
self.reorderResources(tokens)
from loops.browser.resource import ResourceRelationView
if relView is None:
from loops.browser.resource import ResourceRelationView
relView = ResourceRelationView
from loops.organize.personal.browser.filter import FilterView
fv = FilterView(self.context, self.request)
rels = self.context.getResourceRelations()
rels = self.context.getResourceRelations(sort=sort)
for r in rels:
if fv.check(r.first):
yield ResourceRelationView(r, self.request, contextIsSecond=True)
view = relView(r, self.request, contextIsSecond=True)
if view.checkState():
yield view
def resources(self):
return self.getResources()

View file

@ -53,7 +53,7 @@
<h1 tal:define="tabview item/tabview|nothing"
tal:attributes="ondblclick item/openEditWindow">
<a tal:omit-tag="python: level > 1"
tal:attributes="href request/URL"
tal:attributes="href string:${view/requestUrl}${item/urlParamString}"
tal:content="item/title">Title</a>
<a title="Show tabular view"
i18n:attributes="title"
@ -367,4 +367,21 @@
</metal:actions>
<metal:sortable define-macro="sortable_column_header"
tal:define="tableName tableName|nothing">
<a title="tooltip_sort_column"
tal:define="colName col/name"
tal:omit-tag="python:not item.isSortableColumn(tableName, colName)"
tal:attributes="href python:item.getSortUrl(tableName, colName)"
i18n:attributes="title">
<span tal:content="col/title"
tal:attributes="class col/cssClass|nothing"
i18n:translate="" />
<img tal:define="src python:item.getSortImage(tableName, colName)"
tal:condition="src"
tal:attributes="src src" />
</a>
</metal:sortable>
</html>

View file

@ -125,7 +125,7 @@
<containerViews
for="loops.interfaces.ILoops"
index="zope.View"
index="zope.ManageSite"
contents="loops.ManageSite"
add="loops.ManageSite" />
@ -365,7 +365,7 @@
<containerViews
for="loops.interfaces.IViewManager"
index="zope.View"
index="zope.ManageSite"
add="loops.ManageSite" />
<menuItem
@ -571,6 +571,14 @@
factory="loops.browser.concept.TabbedPage"
permission="zope.View" />
<!-- delete object action -->
<page
name="delete_object"
for="loops.interfaces.INode"
class="loops.browser.form.DeleteObject"
permission="zope.ManageContent" />
<!-- dialogs/forms (end-user views) -->
<page

View file

@ -1,28 +1,8 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# loops.browser.external
"""
view class(es) for import/export.
$Id$
""" view class(es) for import/export.
"""
from zope.interface import Interface, implements
from zope.app import zapi
from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy

View file

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -1,41 +1,25 @@
#
# Copyright (c) 2012 Helmut Merz helmutm@cy55.de
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# loops.browser.form
"""
Classes for form presentation and processing.
""" Classes for form presentation and processing.
"""
from urllib.parse import urlencode, unquote_plus
from zope import component, interface, schema
from zope.component import adapts
from zope.container.contained import ObjectRemovedEvent
from zope.event import notify
from zope.interface import Interface
from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
from zope.app.container.interfaces import INameChooser
from zope.app.container.contained import ObjectAddedEvent
from zope.app.pagetemplate import ViewPageTemplateFile
from zope.container.interfaces import INameChooser
from zope.lifecycleevent import ObjectAddedEvent
from zope.browserpage import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy
from zope.contenttype import guess_content_type
from zope.publisher.browser import FileUpload
from zope.publisher.interfaces import BadRequest
from zope.security.interfaces import ForbiddenAttribute, Unauthorized
from zope.security.proxy import isinstance, removeSecurityProxy
from zope.traversing.api import getName
from zope.traversing.api import getName, getParent
from cybertools.ajax import innerHtml
from cybertools.browser.form import FormController
@ -68,6 +52,25 @@ from loops.util import _
from loops.versioning.interfaces import IVersionable
# delete object
class DeleteObject(NodeView):
isTopLevel = True
def __call__(self):
# todo: check permission; check security code
form = self.request.form
obj = util.getObjectForUid(form['uid'])
container = getParent(obj)
notify(ObjectRemovedEvent(obj))
del container[getName(obj)]
message = 'The object requested has been deleted.'
params = [('loops.message', message.encode('UTF-8'))]
nextUrl = '%s?%s' % (self.request.URL[-1], urlencode(params))
return self.request.response.redirect(nextUrl)
# forms
class ObjectForm(NodeView):
@ -162,7 +165,10 @@ class ObjectForm(NodeView):
field = self.schema.fields.get(k)
if field:
fi = field.getFieldInstance(self.instance)
data[k] = fi.marshall(fi.unmarshall(form[k]))
input = form[k]
if isinstance(input, str):
input = unquote_plus(input)
data[k] = fi.marshall(fi.unmarshall(input))
#data[k] = toUnicode(form[k])
return data
@ -196,15 +202,37 @@ class ObjectForm(NodeView):
def typeManager(self):
return ITypeManager(self.target)
@Lazy
def targetType(self):
return self.target.getType()
@Lazy
def presetTypesForAssignment(self):
types = list(self.typeManager.listTypes(include=('assign',)))
types = []
tn = getName(self.targetType)
for t in self.typeManager.listTypes(include=('assign',)):
# check if type is appropriate for the object to be created
opt = IOptions(adapted(t.context))('qualifier_assign_to')
#print '***', t.context.__name__, opt, tn
if not opt or tn in opt:
types.append(t)
assigned = [r.context.conceptType for r in self.assignments]
types = [t for t in types if t.typeProvider not in assigned]
return [dict(title=t.title, token=t.tokenForSearch) for t in types]
def conceptsForType(self, token):
result = ConceptQuery(self).query(type=token)
# check typeOption: include only matching instances
include = []
type = self.conceptManager[token.split(':')[-1]]
#print '###', token, repr(type)
opt = IOptions(adapted(type))('qualifier_assign_check_parents')
if opt:
for p in self.target.getAllParents([self.defaultPredicate]):
for c in p.object.getChildren([self.defaultPredicate]):
include.append(c)
if include:
result = [c for c in result if c in include]
fv = FilterView(self.context, self.request)
result = fv.apply(result)
result.sort(key=lambda x: x.title)
@ -288,8 +316,11 @@ class CreateObjectForm(ObjectForm):
@Lazy
def defaultTypeToken(self):
return (self.controller.params.get('form.create.defaultTypeToken')
or '.loops/concepts/textdocument')
setting = self.controller.params.get('form.create.defaultTypeToken')
if setting:
return setting
opt = self.globalOptions('form.create.default_type_token')
return opt and opt[0] or '.loops/concepts/textdocument'
@Lazy
def typeToken(self):
@ -310,10 +341,15 @@ class CreateObjectForm(ObjectForm):
if typeToken:
return self.loopsRoot.loopsTraverse(typeToken)
@Lazy
def targetType(self):
return self.typeConcept
@Lazy
def adapted(self):
ad = self.typeInterface(Resource())
ad.storageName = 'unknown' # hack for file objects: don't try to retrieve data
ad.__is_dummy__ = True
ad.__type__ = adapted(self.typeConcept)
return ad
@ -423,6 +459,7 @@ class CreateConceptForm(CreateObjectForm):
return c
ad = ti(c)
ad.__is_dummy__ = True
ad.__type__ = adapted(self.typeConcept)
return ad
@Lazy

View file

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

View file

@ -1,26 +1,9 @@
#
# Copyright (c) 2012 Helmut Merz helmutm@cy55.de
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# loops.browser.lobo.standard
"""
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.app.pagetemplate import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy

View file

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

View file

@ -238,18 +238,21 @@ fieldset.box td {
font-weight: bold;
color: #444;
padding-top: 0.4em;
border-bottom: none;
}
.content-4 h1, .content-3 h2, .content-2 h3, .content-1 h4, h4 {
font-size: 130%;
font-weight: normal;
padding-top: 0.3em;
border-bottom: none;
}
.content-5 h1, .content-4 h2, .content-3 h3, content-2 h4, h5 {
font-size: 120%;
/* border: none; */
padding-top: 0.2em;
border-bottom: none;
}
.box {

View file

@ -47,6 +47,35 @@ function showIfIn(node, conditions) {
})
}
function setIfIn(node, conditions) {
dojo.forEach(conditions, function(cond) {
if (node.value == cond[0]) {
target = dijit.byId(cond[1]);
target.setValue(cond[2]);
}
})
}
function setIf(node, cond, acts) {
if (node.value == cond) {
dojo.forEach(acts, function(act) {
target = dijit.byId(act[0]);
target.setValue(act[1]);
})
}
}
function setIfN(node, conds, acts) {
dojo.forEach(conds, function(cond) {
if (node.value == cond) {
dojo.forEach(acts, function(act) {
target = dijit.byId(act[0]);
target.setValue(act[1]);
})
}
})
}
function destroyWidgets(node) {
dojo.forEach(dojo.query('[widgetId]', node), function(n) {
w = dijit.byNode(n);
@ -103,7 +132,7 @@ function submitReplacing(targetId, formId, url) {
mimetype: "text/html",
load: function(response, ioArgs) {
replaceNode(response, targetId);
return resonse;
return response;
}
})
}
@ -115,7 +144,7 @@ function xhrSubmitPopup(formId, url) {
mimetype: "text/html",
load: function(response, ioArgs) {
window.close();
return resonse;
return response;
}
});
}

View file

Before

Width:  |  Height:  |  Size: 942 B

After

Width:  |  Height:  |  Size: 942 B

View file

@ -1,31 +1,11 @@
#
# Copyright (c) 2011 Helmut Merz helmutm@cy55.de
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# loops.browser.mobile.default
"""
Default layouts for the loops mobile skin.
$Id$
""" Default layouts for the loops mobile skin.
"""
from zope.app.pagetemplate import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy
from zope import component
from zope.interface import implements
from cybertools.browser.renderer import RendererFactory
from cybertools.composer.layout.base import Layout

View file

@ -1,37 +1,20 @@
#
# Copyright (c) 2016 Helmut Merz helmutm@cy55.de
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# loops.browser.node
"""
View class for Node objects.
""" View class for Node objects.
"""
from logging import getLogger
import urllib
from urlparse import urlparse, urlunparse
from zope import component, interface, schema
from zope.cachedescriptors.property import Lazy
from zope.annotation.interfaces import IAnnotations
from zope.app.catalog.interfaces import ICatalog
from urllib.parse import urlencode, urlparse, urlunparse
#from urlparse import urlparse, urlunparse
from zope.app.container.browser.contents import JustContents
from zope.app.container.browser.adding import Adding
from zope.app.container.traversal import ItemTraverser
from zope.app.pagetemplate import ViewPageTemplateFile
from zope.app.security.interfaces import IUnauthenticatedPrincipal
from zope import component, interface, schema
from zope.annotation.interfaces import IAnnotations
from zope.authentication.interfaces import IUnauthenticatedPrincipal
from zope.browserpage import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy
from zope.catalog.interfaces import ICatalog
from zope.container.traversal import ItemTraverser
from zope.dottedname.resolve import resolve
from zope.event import notify
from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
@ -86,10 +69,14 @@ class NodeView(BaseView):
super(NodeView, self).__init__(context, request)
self.viewAnnotations.setdefault('nodeView', self)
self.viewAnnotations.setdefault('node', self.context)
viewConfig = getViewConfiguration(context, request)
self.setSkin(viewConfig.get('skinName'))
self.setSkin(self.viewConfig.get('skinName'))
def __call__(self, *args, **kw):
if self.nodeType == 'raw':
vn = self.context.viewName
if vn:
self.request.response.setHeader('content-type', vn)
return self.context.body
tv = self.viewAnnotations.get('targetView')
if tv is not None:
if tv.isToplevel:
@ -98,6 +85,29 @@ class NodeView(BaseView):
self.controller.setMainPage()
return super(NodeView, self).__call__(*args, **kw)
@Lazy
def viewConfig(self):
return getViewConfiguration(self.context, self.request)
@Lazy
def viewConfigOptions(self):
result = {}
for opt in self.viewConfig.get('options') or []:
if ':' in opt:
k, v = opt.split(':', 1)
result[k] = v.split(',')
else:
result[opt] = True
return result
@Lazy
def copyright(self):
cr = self.viewConfigOptions.get('copyright')
if cr:
return cr[0]
cr = self.globalOptions('copyright')
return cr and cr[0] or 'cyberconcepts.org team'
@Lazy
def macro(self):
return self.template.macros['content']
@ -115,7 +125,9 @@ class NodeView(BaseView):
parts.extend(getParts(n))
return parts
def update(self):
def update(self, topLevel=True):
if topLevel and self.view != self:
return self.view.update(False)
result = super(NodeView, self).update()
self.recordAccess()
return result
@ -129,7 +141,7 @@ class NodeView(BaseView):
return []
menu = self.menu
data = [dict(label=menu.title, url=menu.url)]
menuItem = self.nearestMenuItem
menuItem = self.getNearestMenuItem(all=True)
if menuItem != menu.context:
data.append(dict(label=menuItem.title,
url=absoluteURL(menuItem, self.request)))
@ -140,6 +152,9 @@ class NodeView(BaseView):
url=absoluteURL(p, self.request)))
if self.virtualTarget:
data.extend(self.virtualTarget.breadcrumbs())
if data and not '?' in data[-1]['url']:
if self.urlParamString:
data[-1]['url'] += self.urlParamString
return data
def viewModes(self):
@ -366,6 +381,10 @@ class NodeView(BaseView):
def editable(self):
return canWrite(self.context, 'body')
def hasTopPage(self, name):
page = self.topMenu.context.get(name)
return page is not None
# menu stuff
@Lazy
@ -411,8 +430,9 @@ class NodeView(BaseView):
@Lazy
def menuItems(self):
return [NodeView(child, self.request)
items = [NodeView(child, self.request).view
for child in self.context.getMenuItems()]
return [item for item in items if item.isVisible]
@Lazy
def parents(self):
@ -420,10 +440,13 @@ class NodeView(BaseView):
@Lazy
def nearestMenuItem(self):
return self.getNearestMenuItem()
def getNearestMenuItem(self, all=False):
menu = self.menuObject
menuItem = None
for p in [self.context] + self.parents:
if not p.isMenuItem():
if not all and not p.isMenuItem():
menuItem = None
elif menuItem is None:
menuItem = p
@ -439,7 +462,7 @@ class NodeView(BaseView):
@Lazy
def logoutUrl(self):
nextUrl = urllib.urlencode(dict(nextUrl=self.menu.url))
nextUrl = urlencode(dict(nextUrl=self.menu.url))
return '%s/logout.html?%s' % (self.menu.url, nextUrl)
@Lazy
@ -469,7 +492,7 @@ class NodeView(BaseView):
def targetView(self, name='index.html', methodName='show'):
if name == 'index.html': # only when called for default view
tv = self.viewAnnotations.get('targetView')
if tv is not None:
if tv is not None and callable(tv):
return tv()
if '?' in name:
name, params = name.split('?', 1)
@ -567,12 +590,21 @@ class NodeView(BaseView):
""" Return URL of given target view given as .XXX URL.
"""
if isinstance(target, BaseView):
miu = self.getMenuItemUrlForTarget(target.context)
if miu is not None:
return miu
return self.makeTargetUrl(self.url, target.uniqueId, target.title)
else:
target = baseObject(target)
return self.makeTargetUrl(self.url, util.getUidForObject(target),
target.title)
def getMenuItemUrlForTarget(self, tobj):
for node in tobj.getClients():
if node.nodeType == 'page' and node.getMenu() == self.menuObject:
return absoluteURL(node, self.request)
def getActions(self, category='object', page=None, target=None):
actions = []
#self.registerDojo()
@ -737,11 +769,11 @@ class InlineEdit(NodeView):
if ti is not None:
target = ti(target)
data = self.request.form['editorContent']
if type(data) != unicode:
if not isinstance(data, str):
try:
data = data.decode('ISO-8859-15') # IE hack
except UnicodeDecodeError:
print 'loops.browser.node.InlineEdit.save():', data
print('loops.browser.node.InlineEdit.save():', data)
return
# data = data.decode('UTF-8')
target.data = data
@ -913,9 +945,9 @@ class NodeAdding(Adding):
return info
@interface.implementer(IViewConfiguratorSchema)
class ViewPropertiesConfigurator(object):
interface.implements(IViewConfiguratorSchema)
component.adapts(INode)
def __init__(self, context):
@ -976,7 +1008,8 @@ class NodeTraverser(ItemTraverser):
if context.nodeType == 'menu':
setViewConfiguration(context, request)
if name == '.loops':
return self.context.getLoopsRoot()
name = self.getTargetUid(request)
#return self.context.getLoopsRoot()
if name.startswith('.'):
name = self.cleanUpTraversalStack(request, name)[1:]
target = self.getTarget(name)
@ -1002,23 +1035,40 @@ class NodeTraverser(ItemTraverser):
return self.context
try:
obj = super(NodeTraverser, self).publishTraverse(request, name)
except NotFound, e:
except NotFound:
logger.warn('NodeTraverser: NotFound: URL = %s, name = %r' %
(request.URL, name))
raise
return obj
def getTargetUid(self, request):
parent = self.context.getLoopsRoot()
stack = request._traversal_stack
for i in range(2):
name = stack.pop()
obj = parent.get(name)
if not obj:
return name
parent = obj
return '.' + util.getUidForObject(obj)
def cleanUpTraversalStack(self, request, name):
traversalStack = request._traversal_stack
while traversalStack and traversalStack[0].startswith('.'):
#traversalStack = request._traversal_stack
#while traversalStack and traversalStack[0].startswith('.'):
# skip obsolete target references in the url
name = traversalStack.pop(0)
# name = traversalStack.pop(0)
traversedNames = request._traversed_names
if traversedNames:
lastTraversed = traversedNames[-1]
if lastTraversed.startswith('.') and lastTraversed != name:
for n in list(traversedNames):
if n.startswith('.'):
# remove obsolete target refs
traversedNames.remove(n)
#if traversedNames:
# lastTraversed = traversedNames[-1]
# if lastTraversed.startswith('.') and lastTraversed != name:
# let <base .../> tag show the current object
traversedNames[-1] = name
# traversedNames[-1] = name
# let <base .../> tag show the current object
traversedNames.append(name)
return name
def getTarget(self, name):
@ -1036,7 +1086,9 @@ def setViewConfiguration(context, request):
config = IViewConfiguratorSchema(context)
skinName = config.skinName
if not skinName:
skinName = context.getLoopsRoot().skinName
root = removeSecurityProxy(context.getLoopsRoot())
skinName = root.skinName
#skinName = context.getLoopsRoot().skinName
if skinName:
viewAnnotations['skinName'] = skinName
if config.options:
@ -1055,12 +1107,12 @@ def getViewConfiguration(context, request):
class TestView(NodeView):
def __call__(self):
print '*** begin'
print( '*** begin')
for i in range(500):
#x = util.getObjectForUid('1994729849')
x = util.getObjectForUid('2018653366')
self.c = list(x.getChildren())
#self.c = list(x.getChildren([self.defaultPredicate]))
print '*** end', len(self.c)
print('*** end', len(self.c))
return 'done'

View file

Before

Width:  |  Height:  |  Size: 942 B

After

Width:  |  Height:  |  Size: 942 B

View file

@ -30,7 +30,7 @@
item nocall:target"
tal:attributes="class string:content-$level;
id id;
ondblclick python: target.openEditWindow('configure.html')">
ondblclick python:target.openEditWindow('configure.html')">
<metal:body use-macro="item/macro">
The body
</metal:body>
@ -41,17 +41,22 @@
<metal:body define-macro="conceptbody">
<tal:body define="body item/body;">
<tal:body define="body item/body;
itemNum view/itemNum;
id string:$itemNum.body">
<div class="content-1" id="1"
tal:attributes="class string:content-$level;
id string:${view/itemNum}.body;
ondblclick python: item.openEditWindow('configure.html')">
ondblclick python:item.openEditWindow('configure.html')">
<span tal:content="structure body">Node Body</span>
</div>
<tal:concepts define="item nocall:item/targetObjectView;
macro item/macro">
<div metal:use-macro="macro" />
</tal:concepts>
<div tal:define="item nocall:item/targetObjectView;
macro item/macro">
<div tal:attributes="class string:content-$level;
id id;">
<div metal:use-macro="macro" />
</div>
</div>
</tal:body>
</metal:body>
@ -328,11 +333,12 @@
<metal:login define-macro="login">
<div>
<a href="login.html"
i18n:translate="">Log in</a></div>
tal:attributes="href string:${view/topMenu/url}/login.html"
i18n:translate="">Log in</a></div>
<div tal:define="register python:view.globalOptions('provideLogin')"
tal:condition="register">
<a tal:condition="python:register != True"
tal:attributes="href python:register[0]"
tal:condition="python:register and register != True">
<a tal:define="reg python:register[0]"
tal:attributes="href string:${view/topMenu/url}/$reg"
i18n:translate="">Register new member</a></div>
</metal:login>

View file

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

View file

@ -1,35 +1,18 @@
#
# Copyright (c) 2014 Helmut Merz helmutm@cy55.de
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# loops.browser.resource
"""
View class for resource objects.
""" View class for resource objects.
"""
import urllib
import os.path
from zope.authentication.interfaces import IUnauthenticatedPrincipal
from zope.browserpage import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy
from zope import component
from zope.app.catalog.interfaces import ICatalog
from zope.app.container.interfaces import INameChooser
from zope.app.form.browser.textwidgets import FileWidget
from zope.app.pagetemplate import ViewPageTemplateFile
from zope.app.security.interfaces import IUnauthenticatedPrincipal
from zope.catalog.interfaces import ICatalog
from zope.container.interfaces import INameChooser
from zope.formlib.form import FormFields
from zope.formlib.interfaces import DISPLAY_UNWRITEABLE
from zope.formlib.textwidgets import FileWidget
from zope.proxy import removeAllProxies
from zope.schema.interfaces import IBytes
from zope.security import canAccess, canWrite
@ -47,7 +30,7 @@ from loops.browser.common import EditForm, BaseView
from loops.browser.concept import BaseRelationView, ConceptRelationView
from loops.browser.concept import ConceptConfigureView
from loops.browser.node import NodeView, node_macros
from loops.common import adapted, NameChooser, normalizeName
from loops.common import adapted, baseObject, NameChooser, normalizeName
from loops.interfaces import IBaseResource, IDocument, ITextDocument
from loops.interfaces import IMediaAsset as legacy_IMediaAsset
from loops.interfaces import ITypeConcept
@ -196,6 +179,9 @@ class ResourceView(BaseView):
context = self.context
ct = context.contentType
response = self.request.response
if self.typeOptions('x_robots_tag_header', None) is not None:
tagVal = ', '.join(self.typeOptions('x_robots_tag_header'))
response.setHeader('X-Robots-Tag', tagVal)
self.recordAccess('show', target=self.uniqueId)
if ct.startswith('image/'):
#response.setHeader('Cache-Control', 'public,max-age=86400')
@ -214,8 +200,18 @@ class ResourceView(BaseView):
data = context.data
if useAttachment:
if filename is None:
filename = (adapted(self.context).localFilename or
filename = (adapted(self.context).localFilename or
getName(self.context))
if self.typeOptions('use_title_for_download_filename'):
base, ext = os.path.splitext(filename)
filename = context.title
vr = IVersionable(baseObject(context))
if len(vr.versions) > 0:
filename = vr.generateName(filename, ext, vr.versionId)
else:
if not filename.endswith(ext):
filename += ext
filename = filename.encode('UTF-8')
if self.typeOptions('no_normalize_download_filename'):
filename = '"%s"' % filename
else:
@ -258,15 +254,21 @@ class ResourceView(BaseView):
#wp = wiki.createPage(getName(self.context))
wp = wiki.addPage(LoopsWikiPage(self.context))
wp.text = text
#print wp.wiki.getManager()
#print(wp.wiki.getManager())
#return util.toUnicode(wp.render(self.request))
return super(ResourceView, self).renderText(text, contentType)
showMore = True
def renderShortText(self):
return self.renderDescription() or self.createShortText(self.render())
def createShortText(self, text=None):
return extractFirstPart(text or self.render())
text = (text or self.render()).strip()
shortText = extractFirstPart(text)
if shortText == text:
self.showMore = False
return shortText
def download(self):
""" Force download, e.g. of a PDF file """
@ -447,7 +449,7 @@ class ExternalEditorView(ExternalEditorView, BaseView):
r.append('meta_type:' + '.'.join((context.__module__, context.__class__.__name__)))
auth = self.request.get('_auth')
if auth:
print 'ExternalEditorView: auth = ', auth
print('ExternalEditorView: auth = ', auth)
if auth.endswith('\n'):
auth = auth[:-1]
r.append('auth:' + auth)
@ -471,4 +473,3 @@ class NoteView(DocumentView):
def linkUrl(self):
ad = self.typeAdapter
return ad and ad.linkUrl or ''

View file

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

View file

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

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